Use `export` instead of `pub`

52b9cccca36dd131b1b8033ae21973ebed025d1b72ab5bff40cde3bf8d39672e
Alexis Sellier committed ago 1 parent bfa128ab
lib/std.rad +11 -11
1 1
//! The Radiance Standard Library.
2 2
3 -
pub mod io;
4 -
pub mod collections;
5 -
pub mod lang;
6 -
pub mod sys;
7 -
pub mod arch;
8 -
pub mod fmt;
9 -
pub mod mem;
10 -
pub mod vec;
11 -
pub mod intrinsics;
3 +
export mod io;
4 +
export mod collections;
5 +
export mod lang;
6 +
export mod sys;
7 +
export mod arch;
8 +
export mod fmt;
9 +
export mod mem;
10 +
export mod vec;
11 +
export mod intrinsics;
12 12
13 13
// Test modules.
14 -
@test pub mod testing;
15 -
@test pub mod tests;
14 +
@test export mod testing;
15 +
@test export mod tests;
lib/std/arch.rad +1 -1
1 1
//! RISC-V instruction set architecture encodings.
2 -
pub mod rv64;
2 +
export mod rv64;
lib/std/arch/rv64.rad +59 -59
8 8
//! * decode: Instruction decoding (for disassembly/printing)
9 9
//! * emit: Binary emission context and branch patching
10 10
//! * isel: Instruction selection (IL to RV64 instructions)
11 11
//! * printer: Assembly text output
12 12
13 -
pub mod encode;
14 -
pub mod decode;
15 -
pub mod emit;
16 -
pub mod isel;
17 -
pub mod printer;
13 +
export mod encode;
14 +
export mod decode;
15 +
export mod emit;
16 +
export mod isel;
17 +
export mod printer;
18 18
19 19
@test mod tests;
20 20
21 21
use std::mem;
22 22
use std::collections::dict;
30 30
31 31
////////////////
32 32
// Registers  //
33 33
////////////////
34 34
35 -
pub const ZERO: gen::Reg = gen::Reg(0);   /// Hard-wired zero.
36 -
pub const RA:   gen::Reg = gen::Reg(1);   /// Return address.
37 -
pub const SP:   gen::Reg = gen::Reg(2);   /// Stack pointer.
38 -
pub const GP:   gen::Reg = gen::Reg(3);   /// Global pointer.
39 -
pub const TP:   gen::Reg = gen::Reg(4);   /// Thread pointer.
40 -
pub const T0:   gen::Reg = gen::Reg(5);   /// Temporary/alternate link register.
41 -
pub const T1:   gen::Reg = gen::Reg(6);   /// Temporary.
42 -
pub const T2:   gen::Reg = gen::Reg(7);   /// Temporary.
43 -
pub const S0:   gen::Reg = gen::Reg(8);   /// Saved register/frame pointer.
44 -
pub const FP:   gen::Reg = gen::Reg(8);   /// Frame pointer (alias for S0).
45 -
pub const S1:   gen::Reg = gen::Reg(9);   /// Saved register.
46 -
pub const A0:   gen::Reg = gen::Reg(10);  /// Function argument/return.
47 -
pub const A1:   gen::Reg = gen::Reg(11);  /// Function argument/return.
48 -
pub const A2:   gen::Reg = gen::Reg(12);  /// Function argument.
49 -
pub const A3:   gen::Reg = gen::Reg(13);  /// Function argument.
50 -
pub const A4:   gen::Reg = gen::Reg(14);  /// Function argument.
51 -
pub const A5:   gen::Reg = gen::Reg(15);  /// Function argument.
52 -
pub const A6:   gen::Reg = gen::Reg(16);  /// Function argument.
53 -
pub const A7:   gen::Reg = gen::Reg(17);  /// Function argument.
54 -
pub const S2:   gen::Reg = gen::Reg(18);  /// Saved register.
55 -
pub const S3:   gen::Reg = gen::Reg(19);  /// Saved register.
56 -
pub const S4:   gen::Reg = gen::Reg(20);  /// Saved register.
57 -
pub const S5:   gen::Reg = gen::Reg(21);  /// Saved register.
58 -
pub const S6:   gen::Reg = gen::Reg(22);  /// Saved register.
59 -
pub const S7:   gen::Reg = gen::Reg(23);  /// Saved register.
60 -
pub const S8:   gen::Reg = gen::Reg(24);  /// Saved register.
61 -
pub const S9:   gen::Reg = gen::Reg(25);  /// Saved register.
62 -
pub const S10:  gen::Reg = gen::Reg(26);  /// Saved register.
63 -
pub const S11:  gen::Reg = gen::Reg(27);  /// Saved register.
64 -
pub const T3:   gen::Reg = gen::Reg(28);  /// Temporary.
65 -
pub const T4:   gen::Reg = gen::Reg(29);  /// Temporary.
66 -
pub const T5:   gen::Reg = gen::Reg(30);  /// Temporary.
67 -
pub const T6:   gen::Reg = gen::Reg(31);  /// Temporary.
35 +
export const ZERO: gen::Reg = gen::Reg(0);   /// Hard-wired zero.
36 +
export const RA:   gen::Reg = gen::Reg(1);   /// Return address.
37 +
export const SP:   gen::Reg = gen::Reg(2);   /// Stack pointer.
38 +
export const GP:   gen::Reg = gen::Reg(3);   /// Global pointer.
39 +
export const TP:   gen::Reg = gen::Reg(4);   /// Thread pointer.
40 +
export const T0:   gen::Reg = gen::Reg(5);   /// Temporary/alternate link register.
41 +
export const T1:   gen::Reg = gen::Reg(6);   /// Temporary.
42 +
export const T2:   gen::Reg = gen::Reg(7);   /// Temporary.
43 +
export const S0:   gen::Reg = gen::Reg(8);   /// Saved register/frame pointer.
44 +
export const FP:   gen::Reg = gen::Reg(8);   /// Frame pointer (alias for S0).
45 +
export const S1:   gen::Reg = gen::Reg(9);   /// Saved register.
46 +
export const A0:   gen::Reg = gen::Reg(10);  /// Function argument/return.
47 +
export const A1:   gen::Reg = gen::Reg(11);  /// Function argument/return.
48 +
export const A2:   gen::Reg = gen::Reg(12);  /// Function argument.
49 +
export const A3:   gen::Reg = gen::Reg(13);  /// Function argument.
50 +
export const A4:   gen::Reg = gen::Reg(14);  /// Function argument.
51 +
export const A5:   gen::Reg = gen::Reg(15);  /// Function argument.
52 +
export const A6:   gen::Reg = gen::Reg(16);  /// Function argument.
53 +
export const A7:   gen::Reg = gen::Reg(17);  /// Function argument.
54 +
export const S2:   gen::Reg = gen::Reg(18);  /// Saved register.
55 +
export const S3:   gen::Reg = gen::Reg(19);  /// Saved register.
56 +
export const S4:   gen::Reg = gen::Reg(20);  /// Saved register.
57 +
export const S5:   gen::Reg = gen::Reg(21);  /// Saved register.
58 +
export const S6:   gen::Reg = gen::Reg(22);  /// Saved register.
59 +
export const S7:   gen::Reg = gen::Reg(23);  /// Saved register.
60 +
export const S8:   gen::Reg = gen::Reg(24);  /// Saved register.
61 +
export const S9:   gen::Reg = gen::Reg(25);  /// Saved register.
62 +
export const S10:  gen::Reg = gen::Reg(26);  /// Saved register.
63 +
export const S11:  gen::Reg = gen::Reg(27);  /// Saved register.
64 +
export const T3:   gen::Reg = gen::Reg(28);  /// Temporary.
65 +
export const T4:   gen::Reg = gen::Reg(29);  /// Temporary.
66 +
export const T5:   gen::Reg = gen::Reg(30);  /// Temporary.
67 +
export const T6:   gen::Reg = gen::Reg(31);  /// Temporary.
68 68
69 69
/// Create a register from a number. Panics if `n > 31`.
70 -
pub fn reg(n: u8) -> gen::Reg {
70 +
export fn reg(n: u8) -> gen::Reg {
71 71
    assert n < 32;
72 72
    return gen::Reg(n);
73 73
}
74 74
75 75
////////////////////////////
76 76
// Architecture constants //
77 77
////////////////////////////
78 78
79 79
/// Total number of general-purpose registers.
80 -
pub const NUM_REGISTERS: u8 = 32;
80 +
export const NUM_REGISTERS: u8 = 32;
81 81
/// Number of saved registers.
82 -
pub const NUM_SAVED_REGISTERS: u8 = 11;
82 +
export const NUM_SAVED_REGISTERS: u8 = 11;
83 83
/// Word size in bytes (32-bit).
84 -
pub const WORD_SIZE: i32 = 4;
84 +
export const WORD_SIZE: i32 = 4;
85 85
/// Doubleword size in bytes (64-bit).
86 -
pub const DWORD_SIZE: i32 = 8;
86 +
export const DWORD_SIZE: i32 = 8;
87 87
/// Instruction size in bytes.
88 -
pub const INSTR_SIZE: i32 = 4;
88 +
export const INSTR_SIZE: i32 = 4;
89 89
/// Stack alignment requirement in bytes.
90 -
pub const STACK_ALIGNMENT: i32 = 16;
90 +
export const STACK_ALIGNMENT: i32 = 16;
91 91
92 92
/// Minimum blit size (in bytes) to use a loop instead of inline copy.
93 93
/// Blits below this threshold are fully unrolled as LD/SD pairs.
94 -
pub const BLIT_LOOP_THRESHOLD: i32 = 256;
94 +
export const BLIT_LOOP_THRESHOLD: i32 = 256;
95 95
96 96
/////////////////////////
97 97
// Codegen Allocation  //
98 98
/////////////////////////
99 99
100 100
/// Argument registers for function calls.
101 -
pub const ARG_REGS: [gen::Reg; 8] = [A0, A1, A2, A3, A4, A5, A6, A7];
101 +
export const ARG_REGS: [gen::Reg; 8] = [A0, A1, A2, A3, A4, A5, A6, A7];
102 102
103 103
/// Scratch register for code gen. Never allocated to user values.
104 -
pub const SCRATCH1: gen::Reg = T5;
104 +
export const SCRATCH1: gen::Reg = T5;
105 105
106 106
/// Second scratch register for operations needing two temporaries.
107 -
pub const SCRATCH2: gen::Reg = T6;
107 +
export const SCRATCH2: gen::Reg = T6;
108 108
109 109
/// Dedicated scratch for address offset adjustment. Never allocated to user
110 110
/// values and never used for operand materialization, so it can never
111 111
/// conflict with `rd`, `rs`, or `base` in load/store helpers.
112 -
pub const ADDR_SCRATCH: gen::Reg = T4;
112 +
export const ADDR_SCRATCH: gen::Reg = T4;
113 113
114 114
/// Callee-saved registers that need save/restore if used.
115 -
pub const CALLEE_SAVED: [gen::Reg; NUM_SAVED_REGISTERS] = [S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11];
115 +
export const CALLEE_SAVED: [gen::Reg; NUM_SAVED_REGISTERS] = [S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11];
116 116
117 117
/// Maximum 12-bit signed immediate value.
118 -
pub const MAX_IMM: i32 = 2047;
118 +
export const MAX_IMM: i32 = 2047;
119 119
120 120
/// Minimum 12-bit signed immediate value.
121 -
pub const MIN_IMM: i32 = -2048;
121 +
export const MIN_IMM: i32 = -2048;
122 122
123 123
/// Allocatable registers for register allocation.
124 124
const ALLOCATABLE_REGS: [gen::Reg; 23] = [
125 125
    T0, T1, T2, T3,                             // Temporaries
126 126
    A0, A1, A2, A3, A4, A5, A6, A7,             // Arguments
127 127
    S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, // Saved
128 128
];
129 129
130 130
/// Get target configuration for register allocation.
131 131
// TODO: This should be a constant variable.
132 -
pub fn targetConfig() -> regalloc::TargetConfig {
132 +
export fn targetConfig() -> regalloc::TargetConfig {
133 133
    return regalloc::TargetConfig {
134 134
        allocatable: &ALLOCATABLE_REGS[..],
135 135
        argRegs: &ARG_REGS[..],
136 136
        calleeSaved: &CALLEE_SAVED[..],
137 137
        slotSize: DWORD_SIZE,
141 141
///////////////////////
142 142
// Codegen Constants //
143 143
///////////////////////
144 144
145 145
/// Base address where read-only data is loaded.
146 -
pub const RO_DATA_BASE: u32 = 0x10000;
146 +
export const RO_DATA_BASE: u32 = 0x10000;
147 147
148 148
/// Base address where read-write data is loaded.
149 -
pub const RW_DATA_BASE: u32 = 0xFFFFF0;
149 +
export const RW_DATA_BASE: u32 = 0xFFFFF0;
150 150
151 151
/// Storage buffers passed from driver for code generation.
152 -
pub record Storage {
152 +
export record Storage {
153 153
    /// Buffer for data symbols.
154 154
    dataSyms: *mut [data::DataSym],
155 155
    /// Hash table entries for data symbol lookup.
156 156
    dataSymEntries: *mut [dict::Entry],
157 157
}
158 158
159 159
/// Result of code generation.
160 -
pub record Program {
160 +
export record Program {
161 161
    /// Slice of emitted code.
162 162
    code: *[u32],
163 163
    /// Slice of function addresses (name + start index).
164 164
    funcs: *[types::FuncAddr],
165 165
    /// Number of read-only data bytes emitted.
169 169
    /// Debug entries mapping PCs to source locations. Empty when debug is off.
170 170
    debugEntries: *[types::DebugEntry],
171 171
}
172 172
173 173
/// Generate code for an IL program.
174 -
pub fn generate(
174 +
export fn generate(
175 175
    program: *il::Program,
176 176
    storage: Storage,
177 177
    roDataBuf: *mut [u8],
178 178
    rwDataBuf: *mut [u8],
179 179
    arena: *mut alloc::Arena,
lib/std/arch/rv64/decode.rad +13 -13
8 8
///////////////////////
9 9
// Field Extraction  //
10 10
///////////////////////
11 11
12 12
/// Extract opcode (bits 6:0).
13 -
pub fn opcode(instr: u32) -> u32 {
13 +
export fn opcode(instr: u32) -> u32 {
14 14
    return instr & 0x7F;
15 15
}
16 16
17 17
/// Extract rd (bits 11:7).
18 -
pub fn rd(instr: u32) -> u8 {
18 +
export fn rd(instr: u32) -> u8 {
19 19
    return ((instr >> 7) & 0x1F) as u8;
20 20
}
21 21
22 22
/// Extract funct3 (bits 14:12).
23 -
pub fn funct3(instr: u32) -> u32 {
23 +
export fn funct3(instr: u32) -> u32 {
24 24
    return (instr >> 12) & 0x07;
25 25
}
26 26
27 27
/// Extract rs1 (bits 19:15).
28 -
pub fn rs1(instr: u32) -> u8 {
28 +
export fn rs1(instr: u32) -> u8 {
29 29
    return ((instr >> 15) & 0x1F) as u8;
30 30
}
31 31
32 32
/// Extract rs2 (bits 24:20).
33 -
pub fn rs2(instr: u32) -> u8 {
33 +
export fn rs2(instr: u32) -> u8 {
34 34
    return ((instr >> 20) & 0x1F) as u8;
35 35
}
36 36
37 37
/// Extract funct7 (bits 31:25).
38 -
pub fn funct7(instr: u32) -> u32 {
38 +
export fn funct7(instr: u32) -> u32 {
39 39
    return instr >> 25;
40 40
}
41 41
42 42
//////////////////////////
43 43
// Immediate Extraction //
44 44
//////////////////////////
45 45
46 46
/// Extract I-type immediate (sign-extended).
47 -
pub fn immI(instr: u32) -> i32 {
47 +
export fn immI(instr: u32) -> i32 {
48 48
    let imm = instr >> 20;
49 49
    if (imm & 0x800) != 0 {
50 50
        return (imm | 0xFFFFF000) as i32;
51 51
    }
52 52
    return imm as i32;
53 53
}
54 54
55 55
/// Extract S-type immediate (sign-extended).
56 -
pub fn immS(instr: u32) -> i32 {
56 +
export fn immS(instr: u32) -> i32 {
57 57
    let lo = (instr >> 7) & 0x1F;
58 58
    let hi = (instr >> 25) & 0x7F;
59 59
    let imm = (hi << 5) | lo;
60 60
    if (imm & 0x800) != 0 {
61 61
        return (imm | 0xFFFFF000) as i32;
62 62
    }
63 63
    return imm as i32;
64 64
}
65 65
66 66
/// Extract B-type immediate (sign-extended).
67 -
pub fn immB(instr: u32) -> i32 {
67 +
export fn immB(instr: u32) -> i32 {
68 68
    let imm11 = (instr >> 7) & 0x1;
69 69
    let imm4_1 = (instr >> 8) & 0xF;
70 70
    let imm10_5 = (instr >> 25) & 0x3F;
71 71
    let imm12 = (instr >> 31) & 0x1;
72 72
    let imm = (imm12 << 12) | (imm11 << 11) | (imm10_5 << 5) | (imm4_1 << 1);
75 75
    }
76 76
    return imm as i32;
77 77
}
78 78
79 79
/// Extract J-type immediate (sign-extended).
80 -
pub fn immJ(instr: u32) -> i32 {
80 +
export fn immJ(instr: u32) -> i32 {
81 81
    let imm19_12 = (instr >> 12) & 0xFF;
82 82
    let imm11 = (instr >> 20) & 0x1;
83 83
    let imm10_1 = (instr >> 21) & 0x3FF;
84 84
    let imm20 = (instr >> 31) & 0x1;
85 85
    let imm = (imm20 << 20) | (imm19_12 << 12) | (imm11 << 11) | (imm10_1 << 1);
88 88
    }
89 89
    return imm as i32;
90 90
}
91 91
92 92
/// Extract U-type immediate (upper 20 bits, sign-extended).
93 -
pub fn immU(instr: u32) -> i32 {
93 +
export fn immU(instr: u32) -> i32 {
94 94
    let imm = instr >> 12;
95 95
    if (imm & 0x80000) != 0 {
96 96
        return (imm | 0xFFF00000) as i32;
97 97
    }
98 98
    return imm as i32;
101 101
/////////////////////////
102 102
// Decoded Instruction //
103 103
/////////////////////////
104 104
105 105
/// Decoded RV64 instruction.
106 -
pub union Instr {
106 +
export union Instr {
107 107
    // R-type ALU.
108 108
    Add  { rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg },
109 109
    Sub  { rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg },
110 110
    Sll  { rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg },
111 111
    Slt  { rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg },
193 193
    // Unknown/invalid instruction.
194 194
    Unknown { bits: u32 },
195 195
}
196 196
197 197
/// Decode a 32-bit instruction word into an [`Instr`].
198 -
pub fn decode(instr: u32) -> Instr {
198 +
export fn decode(instr: u32) -> Instr {
199 199
    let op = opcode(instr);
200 200
    let f3 = funct3(instr);
201 201
    let f7 = funct7(instr);
202 202
    let rd = super::reg(rd(instr));
203 203
    let rs1 = super::reg(rs1(instr));
lib/std/arch/rv64/emit.rad +42 -42
26 26
//////////////////////
27 27
// Emission Context //
28 28
//////////////////////
29 29
30 30
/// Branch/jump that needs offset patching after all blocks are emitted.
31 -
pub record PendingBranch {
31 +
export record PendingBranch {
32 32
    /// Index into code buffer where the branch instruction is.
33 33
    index: u32,
34 34
    /// Target block index.
35 35
    target: u32,
36 36
    /// Type of branch for re-encoding.
37 37
    kind: BranchKind,
38 38
}
39 39
40 40
/// Type of branch instruction.
41 -
pub union BranchKind {
41 +
export union BranchKind {
42 42
    /// Conditional branch (B-type encoding).
43 43
    Cond { op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg },
44 44
    /// Inverted conditional branch (B-type encoding with negated condition).
45 45
    InvertedCond { op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg },
46 46
    /// Unconditional jump (J-type encoding).
47 47
    Jump,
48 48
}
49 49
50 50
/// Function call that needs offset patching.
51 -
pub record PendingCall {
51 +
export record PendingCall {
52 52
    /// Index in code buffer where the call was emitted.
53 53
    index: u32,
54 54
    /// Target function name.
55 55
    target: *[u8],
56 56
}
57 57
58 58
/// Function address load that needs offset patching.
59 59
/// Used when taking a function's address as a value.
60 -
pub record PendingAddrLoad {
60 +
export record PendingAddrLoad {
61 61
    /// Index in code buffer where the load was emitted.
62 62
    index: u32,
63 63
    /// Target function name.
64 64
    target: *[u8],
65 65
    /// Destination register.
66 66
    rd: gen::Reg,
67 67
}
68 68
69 69
/// Adjusted base register and offset for addressing.
70 -
pub record AdjustedOffset {
70 +
export record AdjustedOffset {
71 71
    /// Base register.
72 72
    base: gen::Reg,
73 73
    /// Byte offset from register.
74 74
    offset: i32,
75 75
}
76 76
77 77
/// Callee-saved register with its stack offset.
78 -
pub record SavedReg {
78 +
export record SavedReg {
79 79
    /// Register to save/restore.
80 80
    reg: gen::Reg,
81 81
    /// Offset from SP.
82 82
    offset: i32,
83 83
}
84 84
85 85
/// Emission context. Tracks state during code generation.
86 -
pub record Emitter {
86 +
export record Emitter {
87 87
    /// Emitted instructions storage.
88 88
    code: *mut [u32],
89 89
    /// Current number of emitted instructions.
90 90
    codeLen: u32,
91 91
    /// Local branches needing offset patching.
111 111
    /// Number of debug entries recorded.
112 112
    debugEntriesLen: u32,
113 113
}
114 114
115 115
/// Computed stack frame layout for a function.
116 -
pub record Frame {
116 +
export record Frame {
117 117
    /// Total frame size in bytes (aligned).
118 118
    totalSize: i32,
119 119
    /// Callee-saved registers and their offsets.
120 120
    // TODO: Use constant length when language supports it.
121 121
    savedRegs: [SavedReg; super::NUM_SAVED_REGISTERS],
130 130
    /// When false, SP never changes after the prologue.
131 131
    isDynamic: bool,
132 132
}
133 133
134 134
/// Compute frame layout from local size and used callee-saved registers.
135 -
pub fn computeFrame(localSize: i32, usedCalleeSaved: u32, epilogueBlock: u32, isLeaf: bool, isDynamic: bool) -> Frame {
135 +
export fn computeFrame(localSize: i32, usedCalleeSaved: u32, epilogueBlock: u32, isLeaf: bool, isDynamic: bool) -> Frame {
136 136
    let mut frame = Frame {
137 137
        totalSize: 0,
138 138
        savedRegs: undefined,
139 139
        savedRegsLen: 0,
140 140
        epilogueBlock,
170 170
    }
171 171
    return frame;
172 172
}
173 173
174 174
/// Create a new emitter.
175 -
pub fn emitter(arena: *mut alloc::Arena, debug: bool) -> Emitter throws (alloc::AllocError) {
175 +
export fn emitter(arena: *mut alloc::Arena, debug: bool) -> Emitter throws (alloc::AllocError) {
176 176
    let code = try alloc::allocSlice(arena, @sizeOf(u32), @alignOf(u32), MAX_INSTRS);
177 177
    let pendingBranches = try alloc::allocSlice(arena, @sizeOf(PendingBranch), @alignOf(PendingBranch), MAX_PENDING);
178 178
    let pendingCalls = try alloc::allocSlice(arena, @sizeOf(PendingCall), @alignOf(PendingCall), MAX_PENDING);
179 179
    let pendingAddrLoads = try alloc::allocSlice(arena, @sizeOf(PendingAddrLoad), @alignOf(PendingAddrLoad), MAX_PENDING);
180 180
    let blockOffsets = try alloc::allocSlice(arena, @sizeOf(i32), @alignOf(i32), labels::MAX_BLOCKS_PER_FN);
207 207
///////////////////////
208 208
// Emission Helpers  //
209 209
///////////////////////
210 210
211 211
/// Emit a single instruction.
212 -
pub fn emit(e: *mut Emitter, instr: u32) {
212 +
export fn emit(e: *mut Emitter, instr: u32) {
213 213
    assert e.codeLen < e.code.len, "emit: code buffer full";
214 214
    e.code[e.codeLen] = instr;
215 215
    e.codeLen += 1;
216 216
}
217 217
218 218
/// Compute branch offset to a function by name.
219 -
pub fn branchOffsetToFunc(e: *Emitter, srcIndex: u32, name: *[u8]) -> i32 {
219 +
export fn branchOffsetToFunc(e: *Emitter, srcIndex: u32, name: *[u8]) -> i32 {
220 220
    return labels::branchToFunc(&e.labels, srcIndex, name, super::INSTR_SIZE);
221 221
}
222 222
223 223
/// Patch an instruction at a given index.
224 -
pub fn patch(e: *mut Emitter, index: u32, instr: u32) {
224 +
export fn patch(e: *mut Emitter, index: u32, instr: u32) {
225 225
    e.code[index] = instr;
226 226
}
227 227
228 228
/// Record a block's address for branch resolution.
229 -
pub fn recordBlock(e: *mut Emitter, blockIdx: u32) {
229 +
export fn recordBlock(e: *mut Emitter, blockIdx: u32) {
230 230
    assert e.codeLen <= MAX_CODE_LEN;
231 231
    labels::recordBlock(&mut e.labels, blockIdx, e.codeLen as i32 * super::INSTR_SIZE);
232 232
}
233 233
234 234
/// Record a function's code offset for call resolution.
235 -
pub fn recordFuncOffset(e: *mut Emitter, name: *[u8]) {
235 +
export fn recordFuncOffset(e: *mut Emitter, name: *[u8]) {
236 236
    assert e.codeLen <= MAX_CODE_LEN;
237 237
    dict::insert(&mut e.labels.funcs, name, e.codeLen as i32 * super::INSTR_SIZE);
238 238
}
239 239
240 240
/// Record a function's start position for printing.
241 -
pub fn recordFunc(e: *mut Emitter, name: *[u8]) {
241 +
export fn recordFunc(e: *mut Emitter, name: *[u8]) {
242 242
    assert e.funcsLen < e.funcs.len, "recordFunc: funcs buffer full";
243 243
    e.funcs[e.funcsLen] = types::FuncAddr { name, index: e.codeLen };
244 244
    e.funcsLen += 1;
245 245
}
246 246
247 247
/// Record a local branch needing later patching.
248 248
/// Unconditional jumps use a single slot (J-type, +-1MB range).
249 249
/// Conditional branches use two slots (B-type has only +-4KB range,
250 250
/// so large functions may need the inverted-branch + JAL fallback).
251 -
pub fn recordBranch(e: *mut Emitter, targetBlock: u32, kind: BranchKind) {
251 +
export fn recordBranch(e: *mut Emitter, targetBlock: u32, kind: BranchKind) {
252 252
    assert e.pendingBranchesLen < e.pendingBranches.len, "recordBranch: buffer full";
253 253
    e.pendingBranches[e.pendingBranchesLen] = PendingBranch {
254 254
        index: e.codeLen,
255 255
        target: targetBlock,
256 256
        kind: kind,
266 266
}
267 267
268 268
/// Record a function call needing later patching.
269 269
/// Emits placeholder instructions that will be patched later.
270 270
/// Uses two slots to support long-distance calls.
271 -
pub fn recordCall(e: *mut Emitter, target: *[u8]) {
271 +
export fn recordCall(e: *mut Emitter, target: *[u8]) {
272 272
    assert e.pendingCallsLen < e.pendingCalls.len, "recordCall: buffer full";
273 273
    e.pendingCalls[e.pendingCallsLen] = PendingCall {
274 274
        index: e.codeLen,
275 275
        target,
276 276
    };
281 281
}
282 282
283 283
/// Record a function address load needing later patching.
284 284
/// Emits placeholder instructions that will be patched to load the function's address.
285 285
/// Uses two slots to compute long-distance addresses.
286 -
pub fn recordAddrLoad(e: *mut Emitter, target: *[u8], rd: gen::Reg) {
286 +
export fn recordAddrLoad(e: *mut Emitter, target: *[u8], rd: gen::Reg) {
287 287
    assert e.pendingAddrLoadsLen < e.pendingAddrLoads.len, "recordAddrLoad: buffer full";
288 288
    e.pendingAddrLoads[e.pendingAddrLoadsLen] = PendingAddrLoad {
289 289
        index: e.codeLen,
290 290
        target,
291 291
        rd: rd,
300 300
///
301 301
/// Called after each function.
302 302
///
303 303
/// Uses two-instruction sequences: short branches use `branch` and `nop`,
304 304
/// long branches use inverted branch  and `jal` or `auipc` and `jalr`.
305 -
pub fn patchLocalBranches(e: *mut Emitter) {
305 +
export fn patchLocalBranches(e: *mut Emitter) {
306 306
    for i in 0..e.pendingBranchesLen {
307 307
        let p = e.pendingBranches[i];
308 308
        let offset = labels::branchToBlock(&e.labels, p.index, p.target, super::INSTR_SIZE);
309 309
        match p.kind {
310 310
            case BranchKind::Cond { op, rs1, rs2 } => {
357 357
    }
358 358
}
359 359
360 360
/// Patch all pending function calls.
361 361
/// Called after all functions have been generated.
362 -
pub fn patchCalls(e: *mut Emitter) {
362 +
export fn patchCalls(e: *mut Emitter) {
363 363
    for i in 0..e.pendingCallsLen {
364 364
        let p = e.pendingCalls[i];
365 365
        let offset = branchOffsetToFunc(e, p.index, p.target);
366 366
        let s = splitImm(offset);
367 367
373 373
}
374 374
375 375
/// Patch all pending function address loads.
376 376
/// Called after all functions have been generated.
377 377
/// Uses PC-relative addresses up to 2GB away.
378 -
pub fn patchAddrLoads(e: *mut Emitter) {
378 +
export fn patchAddrLoads(e: *mut Emitter) {
379 379
    for i in 0..e.pendingAddrLoadsLen {
380 380
        let p = e.pendingAddrLoads[i];
381 381
        let offset = branchOffsetToFunc(e, p.index, p.target);
382 382
        let s = splitImm(offset);
383 383
391 391
/////////////////////////
392 392
// Immediate Handling  //
393 393
/////////////////////////
394 394
395 395
/// Split immediate into `hi` and `lo` bits.
396 -
pub record SplitImm {
396 +
export record SplitImm {
397 397
    /// Upper 20 bits.
398 398
    hi: i32,
399 399
    /// Lower 12 bits.
400 400
    lo: i32,
401 401
}
402 402
403 403
/// Split a 32-bit immediate for `AUIPC, ADDI` / `JALR` sequences.
404 404
/// Handles sign extension: if *lo* is negative, increment *hi*.
405 -
pub fn splitImm(imm: i32) -> SplitImm {
405 +
export fn splitImm(imm: i32) -> SplitImm {
406 406
    let lo = imm & 0xFFF;
407 407
    let mut hi = (imm >> 12) & 0xFFFFF;
408 408
    // If `lo`'s sign bit is set, it will be sign-extended to negative.
409 409
    // Compensate by incrementing `hi`.
410 410
    if (lo & 0x800) != 0 {
433 433
/// Load an immediate value into a register.
434 434
/// Handles the full range of 64-bit immediates.
435 435
/// For values fitting in 12 bits, uses a single `ADDI`.
436 436
/// For values fitting in 32 bits, uses `LUI` + `ADDIW`.
437 437
/// For wider values, loads upper and lower halves then combines with shift and add.
438 -
pub fn loadImm(e: *mut Emitter, rd: gen::Reg, imm: i64) {
438 +
export fn loadImm(e: *mut Emitter, rd: gen::Reg, imm: i64) {
439 439
    let immMin = super::MIN_IMM as i64;
440 440
    let immMax = super::MAX_IMM as i64;
441 441
442 442
    if imm >= immMin and imm <= immMax {
443 443
        emit(e, encode::addi(rd, super::ZERO, imm as i32));
472 472
    emit(e, encode::slli(rd, rd, 10));
473 473
    emit(e, encode::addi(rd, rd, lower & 0x3FF));
474 474
}
475 475
476 476
/// Emit add-immediate, handling large immediates.
477 -
pub fn emitAddImm(e: *mut Emitter, rd: gen::Reg, rs: gen::Reg, imm: i32) {
477 +
export fn emitAddImm(e: *mut Emitter, rd: gen::Reg, rs: gen::Reg, imm: i32) {
478 478
    if imm >= super::MIN_IMM and imm <= super::MAX_IMM {
479 479
        emit(e, encode::addi(rd, rs, imm));
480 480
    } else {
481 481
        loadImm(e, super::SCRATCH1, imm as i64);
482 482
        emit(e, encode::add(rd, rs, super::SCRATCH1));
486 486
////////////////////////
487 487
// Load/Store Helpers //
488 488
////////////////////////
489 489
490 490
/// Emit unsigned load with automatic offset adjustment.
491 -
pub fn emitLoad(e: *mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) {
491 +
export fn emitLoad(e: *mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) {
492 492
    let adj = adjustOffset(e, base, offset);
493 493
    match typ {
494 494
        case il::Type::W8 => emit(e, encode::lbu(rd, adj.base, adj.offset)),
495 495
        case il::Type::W16 => emit(e, encode::lhu(rd, adj.base, adj.offset)),
496 496
        case il::Type::W32 => emit(e, encode::lwu(rd, adj.base, adj.offset)),
497 497
        case il::Type::W64 => emit(e, encode::ld(rd, adj.base, adj.offset)),
498 498
    }
499 499
}
500 500
501 501
/// Emit signed load with automatic offset adjustment.
502 -
pub fn emitSload(e: *mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) {
502 +
export fn emitSload(e: *mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) {
503 503
    let adj = adjustOffset(e, base, offset);
504 504
    match typ {
505 505
        case il::Type::W8 => emit(e, encode::lb(rd, adj.base, adj.offset)),
506 506
        case il::Type::W16 => emit(e, encode::lh(rd, adj.base, adj.offset)),
507 507
        case il::Type::W32 => emit(e, encode::lw(rd, adj.base, adj.offset)),
508 508
        case il::Type::W64 => emit(e, encode::ld(rd, adj.base, adj.offset)),
509 509
    }
510 510
}
511 511
512 512
/// Emit store with automatic offset adjustment.
513 -
pub fn emitStore(e: *mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) {
513 +
export fn emitStore(e: *mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) {
514 514
    let adj = adjustOffset(e, base, offset);
515 515
    match typ {
516 516
        case il::Type::W8 => emit(e, encode::sb(rs, adj.base, adj.offset)),
517 517
        case il::Type::W16 => emit(e, encode::sh(rs, adj.base, adj.offset)),
518 518
        case il::Type::W32 => emit(e, encode::sw(rs, adj.base, adj.offset)),
519 519
        case il::Type::W64 => emit(e, encode::sd(rs, adj.base, adj.offset)),
520 520
    }
521 521
}
522 522
523 523
/// Emit 64-bit load with automatic offset adjustment.
524 -
pub fn emitLd(e: *mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) {
524 +
export fn emitLd(e: *mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) {
525 525
    let adj = adjustOffset(e, base, offset);
526 526
    emit(e, encode::ld(rd, adj.base, adj.offset));
527 527
}
528 528
529 529
/// Emit 64-bit store with automatic offset adjustment.
530 -
pub fn emitSd(e: *mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) {
530 +
export fn emitSd(e: *mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) {
531 531
    let adj = adjustOffset(e, base, offset);
532 532
    emit(e, encode::sd(rs, adj.base, adj.offset));
533 533
}
534 534
535 535
/// Emit 32-bit load with automatic offset adjustment.
536 -
pub fn emitLw(e: *mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) {
536 +
export fn emitLw(e: *mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) {
537 537
    let adj = adjustOffset(e, base, offset);
538 538
    emit(e, encode::lw(rd, adj.base, adj.offset));
539 539
}
540 540
541 541
/// Emit 32-bit store with automatic offset adjustment.
542 -
pub fn emitSw(e: *mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) {
542 +
export fn emitSw(e: *mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) {
543 543
    let adj = adjustOffset(e, base, offset);
544 544
    emit(e, encode::sw(rs, adj.base, adj.offset));
545 545
}
546 546
547 547
/// Emit 8-bit load with automatic offset adjustment.
548 -
pub fn emitLb(e: *mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) {
548 +
export fn emitLb(e: *mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) {
549 549
    let adj = adjustOffset(e, base, offset);
550 550
    emit(e, encode::lb(rd, adj.base, adj.offset));
551 551
}
552 552
553 553
/// Emit 8-bit store with automatic offset adjustment.
554 -
pub fn emitSb(e: *mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) {
554 +
export fn emitSb(e: *mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) {
555 555
    let adj = adjustOffset(e, base, offset);
556 556
    emit(e, encode::sb(rs, adj.base, adj.offset));
557 557
}
558 558
559 559
//////////////////////////
560 560
// Prologue / Epilogue  //
561 561
//////////////////////////
562 562
563 563
/// Emit function prologue.
564 564
/// Allocates stack frame, saves RA/FP, saves callee-saved registers.
565 -
pub fn emitPrologue(e: *mut Emitter, frame: *Frame) {
565 +
export fn emitPrologue(e: *mut Emitter, frame: *Frame) {
566 566
    // Fast path: leaf function with no locals.
567 567
    if frame.totalSize == 0 {
568 568
        return;
569 569
    }
570 570
    let totalSize = frame.totalSize;
594 594
        emitSd(e, sr.reg, super::SP, sr.offset);
595 595
    }
596 596
}
597 597
598 598
/// Emit a return: jump to epilogue, or emit `ret` directly for leaf functions.
599 -
pub fn emitReturn(e: *mut Emitter, frame: *Frame) {
599 +
export fn emitReturn(e: *mut Emitter, frame: *Frame) {
600 600
    if frame.totalSize == 0 {
601 601
        // Leaf function: no frame to tear down, emit ret directly.
602 602
        emit(e, encode::ret());
603 603
        return;
604 604
    }
605 605
    recordBranch(e, frame.epilogueBlock, BranchKind::Jump);
606 606
}
607 607
608 608
/// Emit function epilogue.
609 609
/// Restores callee-saved registers, `RA/FP`, deallocates frame, returns.
610 -
pub fn emitEpilogue(e: *mut Emitter, frame: *Frame) {
610 +
export fn emitEpilogue(e: *mut Emitter, frame: *Frame) {
611 611
    // Record epilogue block address for return jumps.
612 612
    recordBlock(e, frame.epilogueBlock);
613 613
614 614
    // Fast path: leaf function with no locals.
615 615
    if frame.totalSize == 0 {
642 642
//////////////////
643 643
// Code Access  //
644 644
//////////////////
645 645
646 646
/// Get emitted code as a slice.
647 -
pub fn getCode(e: *Emitter) -> *[u32] {
647 +
export fn getCode(e: *Emitter) -> *[u32] {
648 648
    return &e.code[..e.codeLen];
649 649
}
650 650
651 651
/// Get function addresses for printing.
652 -
pub fn getFuncs(e: *Emitter) -> *[types::FuncAddr] {
652 +
export fn getFuncs(e: *Emitter) -> *[types::FuncAddr] {
653 653
    return &e.funcs[..e.funcsLen];
654 654
}
655 655
656 656
/// Record a debug entry mapping the current PC to a source location.
657 657
/// Deduplicates consecutive entries with the same location.
658 -
pub fn recordSrcLoc(e: *mut Emitter, loc: il::SrcLoc) {
658 +
export fn recordSrcLoc(e: *mut Emitter, loc: il::SrcLoc) {
659 659
    let pc = e.codeLen * super::INSTR_SIZE as u32;
660 660
661 661
    // Skip if this is the same location as the previous entry.
662 662
    if e.debugEntriesLen > 0 {
663 663
        let prev = &e.debugEntries[e.debugEntriesLen - 1];
673 673
    };
674 674
    e.debugEntriesLen += 1;
675 675
}
676 676
677 677
/// Get debug entries as a slice.
678 -
pub fn getDebugEntries(e: *Emitter) -> *[types::DebugEntry] {
678 +
export fn getDebugEntries(e: *Emitter) -> *[types::DebugEntry] {
679 679
    return &e.debugEntries[..e.debugEntriesLen];
680 680
}
lib/std/arch/rv64/encode.rad +118 -118
6 6
7 7
//////////////////////
8 8
// Opcode Constants //
9 9
//////////////////////
10 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
11 +
export const OP_LOAD:   u32 = 0x03;
12 +
export const OP_STORE:  u32 = 0x23;
13 +
export const OP_BRANCH: u32 = 0x63;
14 +
export const OP_JALR:   u32 = 0x67;
15 +
export const OP_JAL:    u32 = 0x6F;
16 +
export const OP_OP:     u32 = 0x33;
17 +
export const OP_IMM:    u32 = 0x13;
18 +
export const OP_AUIPC:  u32 = 0x17;
19 +
export const OP_LUI:    u32 = 0x37;
20 +
export const OP_SYSTEM: u32 = 0x73;
21 +
export const OP_OP32:   u32 = 0x3B;  // RV64: 32-bit operations
22 +
export const OP_IMM32:  u32 = 0x1B;  // RV64: 32-bit immediate operations
23 23
24 24
//////////////////////
25 25
// Funct3 Constants //
26 26
//////////////////////
27 27
28 28
// Memory operations
29 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)
30 +
export const F3_BYTE:   u32 = 0x0;  // LB/SB
31 +
export const F3_HALF:   u32 = 0x1;  // LH/SH
32 +
export const F3_WORD:   u32 = 0x2;  // LW/SW
33 +
export const F3_DWORD:  u32 = 0x3;  // LD/SD (RV64)
34 +
export const F3_BYTE_U: u32 = 0x4;  // LBU
35 +
export const F3_HALF_U: u32 = 0x5;  // LHU
36 +
export const F3_WORD_U: u32 = 0x6;  // LWU (RV64)
37 37
38 38
// ALU operations
39 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
40 +
export const F3_ADD:  u32 = 0x0;  // ADD/SUB/ADDI
41 +
export const F3_SLL:  u32 = 0x1;  // SLL/SLLI
42 +
export const F3_SLT:  u32 = 0x2;  // SLT/SLTI
43 +
export const F3_SLTU: u32 = 0x3;  // SLTU/SLTIU
44 +
export const F3_XOR:  u32 = 0x4;  // XOR/XORI
45 +
export const F3_SRL:  u32 = 0x5;  // SRL/SRA/SRLI/SRAI
46 +
export const F3_OR:   u32 = 0x6;  // OR/ORI
47 +
export const F3_AND:  u32 = 0x7;  // AND/ANDI
48 48
49 49
// Branch operations
50 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;
51 +
export const F3_BEQ:  u32 = 0x0;
52 +
export const F3_BNE:  u32 = 0x1;
53 +
export const F3_BLT:  u32 = 0x4;
54 +
export const F3_BGE:  u32 = 0x5;
55 +
export const F3_BLTU: u32 = 0x6;
56 +
export const F3_BGEU: u32 = 0x7;
57 57
58 58
//////////////////////
59 59
// Funct7 Constants //
60 60
//////////////////////
61 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
62 +
export const F7_NORMAL: u32 = 0b0000000;
63 +
export const F7_SUB:    u32 = 0b0100000;  // Bit 5 set
64 +
export const F7_SRA:    u32 = 0b0100000;  // Bit 5 set
65 +
export const F7_MUL:    u32 = 0b0000001;  // Bit 0 set
66 66
67 67
/////////////////////////
68 68
// Validation Helpers  //
69 69
/////////////////////////
70 70
71 71
/// Returns `true` if the value fits in a signed 12-bit immediate.
72 -
pub fn isSmallImm(value: i32) -> bool {
72 +
export fn isSmallImm(value: i32) -> bool {
73 73
    return value >= super::MIN_IMM and value <= super::MAX_IMM;
74 74
}
75 75
76 76
/// Returns `true` if a 64-bit value fits in a 12-bit signed immediate.
77 -
pub fn isSmallImm64(value: i64) -> bool {
77 +
export fn isSmallImm64(value: i64) -> bool {
78 78
    return value >= (super::MIN_IMM as i64) and value <= (super::MAX_IMM as i64);
79 79
}
80 80
81 81
/// Returns `true` if the value is valid for branch immediates.
82 82
/// Branch immediates are 13-bit signed, even aligned.
83 -
pub fn isBranchImm(value: i32) -> bool {
83 +
export fn isBranchImm(value: i32) -> bool {
84 84
    return value >= -(1 << 12) and value <= ((1 << 12) - 2) and (value & 1) == 0;
85 85
}
86 86
87 87
/// Returns `true` if the value is valid for jump immediates (JAL).
88 88
/// Jump immediates are 21-bit signed, even aligned.
89 -
pub fn isJumpImm(value: i32) -> bool {
89 +
export fn isJumpImm(value: i32) -> bool {
90 90
    return value >= -(1 << 20) and value <= ((1 << 20) - 2) and (value & 1) == 0;
91 91
}
92 92
93 93
//////////////////////
94 94
// Format Encoders  //
173 173
////////////////////////////
174 174
// ALU Immediate (I-type) //
175 175
////////////////////////////
176 176
177 177
/// Add immediate: `rd = rs1 + imm`.
178 -
pub fn addi(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
178 +
export fn addi(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
179 179
    return encodeI(OP_IMM, rd, rs1, F3_ADD, imm);
180 180
}
181 181
182 182
/// Set less than immediate (signed): `rd = (rs1 < imm) ? 1 : 0`.
183 -
pub fn slti(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
183 +
export fn slti(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
184 184
    return encodeI(OP_IMM, rd, rs1, F3_SLT, imm);
185 185
}
186 186
187 187
/// Set less than immediate unsigned: `rd = (rs1 < imm) ? 1 : 0`.
188 -
pub fn sltiu(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
188 +
export fn sltiu(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
189 189
    return encodeI(OP_IMM, rd, rs1, F3_SLTU, imm);
190 190
}
191 191
192 192
/// XOR immediate: `rd = rs1 ^ imm`.
193 -
pub fn xori(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
193 +
export fn xori(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
194 194
    return encodeI(OP_IMM, rd, rs1, F3_XOR, imm);
195 195
}
196 196
197 197
/// OR immediate: `rd = rs1 | imm`.
198 -
pub fn ori(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
198 +
export fn ori(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
199 199
    return encodeI(OP_IMM, rd, rs1, F3_OR, imm);
200 200
}
201 201
202 202
/// AND immediate: `rd = rs1 & imm`.
203 -
pub fn andi(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
203 +
export fn andi(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
204 204
    return encodeI(OP_IMM, rd, rs1, F3_AND, imm);
205 205
}
206 206
207 207
/// Shift left logical immediate: `rd = rs1 << shamt`.
208 -
pub fn slli(rd: gen::Reg, rs1: gen::Reg, shamt: i32) -> u32 {
208 +
export fn slli(rd: gen::Reg, rs1: gen::Reg, shamt: i32) -> u32 {
209 209
    assert shamt >= 0 and shamt < 64;
210 210
    return encodeI(OP_IMM, rd, rs1, F3_SLL, shamt & 0x3F);
211 211
}
212 212
213 213
/// Shift right logical immediate: `rd = rs1 >> shamt` (zero-extend).
214 -
pub fn srli(rd: gen::Reg, rs1: gen::Reg, shamt: i32) -> u32 {
214 +
export fn srli(rd: gen::Reg, rs1: gen::Reg, shamt: i32) -> u32 {
215 215
    assert shamt >= 0 and shamt < 64;
216 216
    return encodeI(OP_IMM, rd, rs1, F3_SRL, shamt & 0x3F);
217 217
}
218 218
219 219
/// Shift right arithmetic immediate: `rd = rs1 >> shamt` (sign-extend).
220 -
pub fn srai(rd: gen::Reg, rs1: gen::Reg, shamt: i32) -> u32 {
220 +
export fn srai(rd: gen::Reg, rs1: gen::Reg, shamt: i32) -> u32 {
221 221
    assert shamt >= 0 and shamt < 64;
222 222
    // SRAI has bit 10 set in immediate field (becomes bit 30 in instruction)
223 223
    return encodeI(OP_IMM, rd, rs1, F3_SRL, (shamt & 0x3F) | 0b10000000000);
224 224
}
225 225
226 226
///////////////////////////
227 227
// ALU Register (R-type) //
228 228
///////////////////////////
229 229
230 230
/// Add: `rd = rs1 + rs2`.
231 -
pub fn add(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
231 +
export fn add(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
232 232
    return encodeR(OP_OP, rd, rs1, rs2, F3_ADD, F7_NORMAL);
233 233
}
234 234
235 235
/// Subtract: `rd = rs1 - rs2`.
236 -
pub fn sub(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
236 +
export fn sub(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
237 237
    return encodeR(OP_OP, rd, rs1, rs2, F3_ADD, F7_SUB);
238 238
}
239 239
240 240
/// Shift left logical: `rd = rs1 << rs2[5:0]`.
241 -
pub fn sll(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
241 +
export fn sll(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
242 242
    return encodeR(OP_OP, rd, rs1, rs2, F3_SLL, F7_NORMAL);
243 243
}
244 244
245 245
/// Set less than (signed): `rd = (rs1 < rs2) ? 1 : 0`.
246 -
pub fn slt(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
246 +
export fn slt(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
247 247
    return encodeR(OP_OP, rd, rs1, rs2, F3_SLT, F7_NORMAL);
248 248
}
249 249
250 250
/// Set less than unsigned: `rd = (rs1 < rs2) ? 1 : 0`.
251 -
pub fn sltu(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
251 +
export fn sltu(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
252 252
    return encodeR(OP_OP, rd, rs1, rs2, F3_SLTU, F7_NORMAL);
253 253
}
254 254
255 255
/// XOR: `rd = rs1 ^ rs2`.
256 -
pub fn xor(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
256 +
export fn xor(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
257 257
    return encodeR(OP_OP, rd, rs1, rs2, F3_XOR, F7_NORMAL);
258 258
}
259 259
260 260
/// Shift right logical: `rd = rs1 >> rs2[5:0]` (zero-extend).
261 -
pub fn srl(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
261 +
export fn srl(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
262 262
    return encodeR(OP_OP, rd, rs1, rs2, F3_SRL, F7_NORMAL);
263 263
}
264 264
265 265
/// Shift right arithmetic: `rd = rs1 >> rs2[5:0]` (sign-extend).
266 -
pub fn sra(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
266 +
export fn sra(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
267 267
    return encodeR(OP_OP, rd, rs1, rs2, F3_SRL, F7_SRA);
268 268
}
269 269
270 270
/// OR: `rd = rs1 | rs2`.
271 -
pub fn or_(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
271 +
export fn or_(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
272 272
    return encodeR(OP_OP, rd, rs1, rs2, F3_OR, F7_NORMAL);
273 273
}
274 274
275 275
/// AND: `rd = rs1 & rs2`.
276 -
pub fn and_(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
276 +
export fn and_(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
277 277
    return encodeR(OP_OP, rd, rs1, rs2, F3_AND, F7_NORMAL);
278 278
}
279 279
280 280
/////////////////
281 281
// M Extension //
282 282
/////////////////
283 283
284 284
/// Multiply: `rd = (rs1 * rs2)[63:0]`.
285 -
pub fn mul(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
285 +
export fn mul(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
286 286
    return encodeR(OP_OP, rd, rs1, rs2, F3_ADD, F7_MUL);
287 287
}
288 288
289 289
/// Multiply high (signed x signed): `rd = (rs1 * rs2)[127:64]`.
290 -
pub fn mulh(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
290 +
export fn mulh(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
291 291
    return encodeR(OP_OP, rd, rs1, rs2, F3_SLL, F7_MUL);
292 292
}
293 293
294 294
/// Multiply high (signed x unsigned): `rd = (rs1 * rs2)[127:64]`.
295 -
pub fn mulhsu(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
295 +
export fn mulhsu(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
296 296
    return encodeR(OP_OP, rd, rs1, rs2, F3_SLT, F7_MUL);
297 297
}
298 298
299 299
/// Multiply high (unsigned x unsigned): `rd = (rs1 * rs2)[127:64]`.
300 -
pub fn mulhu(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
300 +
export fn mulhu(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
301 301
    return encodeR(OP_OP, rd, rs1, rs2, F3_SLTU, F7_MUL);
302 302
}
303 303
304 304
/// Divide (signed): `rd = rs1 / rs2`.
305 -
pub fn div(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
305 +
export fn div(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
306 306
    return encodeR(OP_OP, rd, rs1, rs2, F3_XOR, F7_MUL);
307 307
}
308 308
309 309
/// Divide (unsigned): `rd = rs1 / rs2`.
310 -
pub fn divu(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
310 +
export fn divu(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
311 311
    return encodeR(OP_OP, rd, rs1, rs2, F3_SRL, F7_MUL);
312 312
}
313 313
314 314
/// Remainder (signed): `rd = rs1 % rs2`.
315 -
pub fn rem(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
315 +
export fn rem(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
316 316
    return encodeR(OP_OP, rd, rs1, rs2, F3_OR, F7_MUL);
317 317
}
318 318
319 319
/// Remainder (unsigned): `rd = rs1 % rs2`.
320 -
pub fn remu(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
320 +
export fn remu(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
321 321
    return encodeR(OP_OP, rd, rs1, rs2, F3_AND, F7_MUL);
322 322
}
323 323
324 324
//////////////////////////
325 325
// RV64 Word Operations //
326 326
//////////////////////////
327 327
328 328
/// Add immediate word (32-bit, sign-extended): `rd = sign_ext((rs1 + imm)[31:0])`.
329 -
pub fn addiw(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
329 +
export fn addiw(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
330 330
    return encodeI(OP_IMM32, rd, rs1, F3_ADD, imm);
331 331
}
332 332
333 333
/// Shift left logical immediate word: `rd = sign_ext((rs1 << shamt)[31:0])`.
334 -
pub fn slliw(rd: gen::Reg, rs1: gen::Reg, shamt: i32) -> u32 {
334 +
export fn slliw(rd: gen::Reg, rs1: gen::Reg, shamt: i32) -> u32 {
335 335
    assert shamt >= 0 and shamt < 32;
336 336
    return encodeI(OP_IMM32, rd, rs1, F3_SLL, shamt & 0x1F);
337 337
}
338 338
339 339
/// Shift right logical immediate word: `rd = sign_ext((rs1[31:0] >> shamt))`.
340 -
pub fn srliw(rd: gen::Reg, rs1: gen::Reg, shamt: i32) -> u32 {
340 +
export fn srliw(rd: gen::Reg, rs1: gen::Reg, shamt: i32) -> u32 {
341 341
    assert shamt >= 0 and shamt < 32;
342 342
    return encodeI(OP_IMM32, rd, rs1, F3_SRL, shamt & 0x1F);
343 343
}
344 344
345 345
/// Shift right arithmetic immediate word: `rd = sign_ext((rs1[31:0] >> shamt))` (sign-extended).
346 -
pub fn sraiw(rd: gen::Reg, rs1: gen::Reg, shamt: i32) -> u32 {
346 +
export fn sraiw(rd: gen::Reg, rs1: gen::Reg, shamt: i32) -> u32 {
347 347
    assert shamt >= 0 and shamt < 32;
348 348
    return encodeI(OP_IMM32, rd, rs1, F3_SRL, (shamt & 0x1F) | 0b10000000000);
349 349
}
350 350
351 351
/// Add word: `rd = sign_ext((rs1 + rs2)[31:0])`.
352 -
pub fn addw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
352 +
export fn addw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
353 353
    return encodeR(OP_OP32, rd, rs1, rs2, F3_ADD, F7_NORMAL);
354 354
}
355 355
356 356
/// Subtract word: `rd = sign_ext((rs1 - rs2)[31:0])`.
357 -
pub fn subw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
357 +
export fn subw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
358 358
    return encodeR(OP_OP32, rd, rs1, rs2, F3_ADD, F7_SUB);
359 359
}
360 360
361 361
/// Shift left logical word: `rd = sign_ext((rs1 << rs2[4:0])[31:0])`.
362 -
pub fn sllw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
362 +
export fn sllw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
363 363
    return encodeR(OP_OP32, rd, rs1, rs2, F3_SLL, F7_NORMAL);
364 364
}
365 365
366 366
/// Shift right logical word: `rd = sign_ext((rs1[31:0] >> rs2[4:0]))`.
367 -
pub fn srlw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
367 +
export fn srlw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
368 368
    return encodeR(OP_OP32, rd, rs1, rs2, F3_SRL, F7_NORMAL);
369 369
}
370 370
371 371
/// Shift right arithmetic word: `rd = sign_ext((rs1[31:0] >> rs2[4:0]))` (sign-extended).
372 -
pub fn sraw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
372 +
export fn sraw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
373 373
    return encodeR(OP_OP32, rd, rs1, rs2, F3_SRL, F7_SRA);
374 374
}
375 375
376 376
/// Multiply word: `rd = sign_ext((rs1 * rs2)[31:0])`.
377 -
pub fn mulw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
377 +
export fn mulw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
378 378
    return encodeR(OP_OP32, rd, rs1, rs2, F3_ADD, F7_MUL);
379 379
}
380 380
381 381
/// Divide word (signed): `rd = sign_ext(rs1[31:0] / rs2[31:0])`.
382 -
pub fn divw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
382 +
export fn divw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
383 383
    return encodeR(OP_OP32, rd, rs1, rs2, F3_XOR, F7_MUL);
384 384
}
385 385
386 386
/// Divide word (unsigned): `rd = sign_ext(rs1[31:0] / rs2[31:0])`.
387 -
pub fn divuw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
387 +
export fn divuw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
388 388
    return encodeR(OP_OP32, rd, rs1, rs2, F3_SRL, F7_MUL);
389 389
}
390 390
391 391
/// Remainder word (signed): `rd = sign_ext(rs1[31:0] % rs2[31:0])`.
392 -
pub fn remw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
392 +
export fn remw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
393 393
    return encodeR(OP_OP32, rd, rs1, rs2, F3_OR, F7_MUL);
394 394
}
395 395
396 396
/// Remainder word (unsigned): `rd = sign_ext(rs1[31:0] % rs2[31:0])`.
397 -
pub fn remuw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
397 +
export fn remuw(rd: gen::Reg, rs1: gen::Reg, rs2: gen::Reg) -> u32 {
398 398
    return encodeR(OP_OP32, rd, rs1, rs2, F3_AND, F7_MUL);
399 399
}
400 400
401 401
///////////////////
402 402
// Load (I-type) //
403 403
///////////////////
404 404
405 405
/// Load byte (sign-extend): `rd = mem[rs1 + imm][7:0]`.
406 -
pub fn lb(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
406 +
export fn lb(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
407 407
    return encodeI(OP_LOAD, rd, rs1, F3_BYTE, imm);
408 408
}
409 409
410 410
/// Load halfword (sign-extend): `rd = mem[rs1 + imm][15:0]`.
411 -
pub fn lh(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
411 +
export fn lh(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
412 412
    return encodeI(OP_LOAD, rd, rs1, F3_HALF, imm);
413 413
}
414 414
415 415
/// Load word (sign-extend to 64-bit): `rd = sign_ext(mem[rs1 + imm][31:0])`.
416 -
pub fn lw(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
416 +
export fn lw(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
417 417
    return encodeI(OP_LOAD, rd, rs1, F3_WORD, imm);
418 418
}
419 419
420 420
/// Load byte unsigned (zero-extend): `rd = mem[rs1 + imm][7:0]`.
421 -
pub fn lbu(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
421 +
export fn lbu(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
422 422
    return encodeI(OP_LOAD, rd, rs1, F3_BYTE_U, imm);
423 423
}
424 424
425 425
/// Load halfword unsigned (zero-extend): `rd = mem[rs1 + imm][15:0]`.
426 -
pub fn lhu(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
426 +
export fn lhu(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
427 427
    return encodeI(OP_LOAD, rd, rs1, F3_HALF_U, imm);
428 428
}
429 429
430 430
/// Load word unsigned (zero-extend to 64-bit): `rd = mem[rs1 + imm][31:0]`.
431 -
pub fn lwu(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
431 +
export fn lwu(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
432 432
    return encodeI(OP_LOAD, rd, rs1, F3_WORD_U, imm);
433 433
}
434 434
435 435
/// Load doubleword: `rd = mem[rs1 + imm][63:0]`.
436 -
pub fn ld(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
436 +
export fn ld(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
437 437
    return encodeI(OP_LOAD, rd, rs1, F3_DWORD, imm);
438 438
}
439 439
440 440
////////////////////
441 441
// Store (S-type) //
442 442
////////////////////
443 443
444 444
/// Store byte: `mem[rs1 + imm] = rs2[7:0]`.
445 -
pub fn sb(rs2: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
445 +
export fn sb(rs2: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
446 446
    return encodeS(OP_STORE, rs1, rs2, F3_BYTE, imm);
447 447
}
448 448
449 449
/// Store halfword: `mem[rs1 + imm] = rs2[15:0]`.
450 -
pub fn sh(rs2: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
450 +
export fn sh(rs2: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
451 451
    return encodeS(OP_STORE, rs1, rs2, F3_HALF, imm);
452 452
}
453 453
454 454
/// Store word: `mem[rs1 + imm] = rs2[31:0]`.
455 -
pub fn sw(rs2: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
455 +
export fn sw(rs2: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
456 456
    return encodeS(OP_STORE, rs1, rs2, F3_WORD, imm);
457 457
}
458 458
459 459
/// Store doubleword: `mem[rs1 + imm] = rs2[63:0]`.
460 -
pub fn sd(rs2: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
460 +
export fn sd(rs2: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
461 461
    return encodeS(OP_STORE, rs1, rs2, F3_DWORD, imm);
462 462
}
463 463
464 464
/////////////////////
465 465
// Branch (B-type) //
466 466
/////////////////////
467 467
468 468
/// Branch if equal: `if (rs1 == rs2) pc += imm`.
469 -
pub fn beq(rs1: gen::Reg, rs2: gen::Reg, imm: i32) -> u32 {
469 +
export fn beq(rs1: gen::Reg, rs2: gen::Reg, imm: i32) -> u32 {
470 470
    return encodeB(OP_BRANCH, rs1, rs2, F3_BEQ, imm);
471 471
}
472 472
473 473
/// Branch if not equal: `if (rs1 != rs2) pc += imm`.
474 -
pub fn bne(rs1: gen::Reg, rs2: gen::Reg, imm: i32) -> u32 {
474 +
export fn bne(rs1: gen::Reg, rs2: gen::Reg, imm: i32) -> u32 {
475 475
    return encodeB(OP_BRANCH, rs1, rs2, F3_BNE, imm);
476 476
}
477 477
478 478
/// Branch if less than (signed): `if (rs1 < rs2) pc += imm`.
479 -
pub fn blt(rs1: gen::Reg, rs2: gen::Reg, imm: i32) -> u32 {
479 +
export fn blt(rs1: gen::Reg, rs2: gen::Reg, imm: i32) -> u32 {
480 480
    return encodeB(OP_BRANCH, rs1, rs2, F3_BLT, imm);
481 481
}
482 482
483 483
/// Branch if greater or equal (signed): `if (rs1 >= rs2) pc += imm`.
484 -
pub fn bge(rs1: gen::Reg, rs2: gen::Reg, imm: i32) -> u32 {
484 +
export fn bge(rs1: gen::Reg, rs2: gen::Reg, imm: i32) -> u32 {
485 485
    return encodeB(OP_BRANCH, rs1, rs2, F3_BGE, imm);
486 486
}
487 487
488 488
/// Branch if less than unsigned: `if (rs1 < rs2) pc += imm`.
489 -
pub fn bltu(rs1: gen::Reg, rs2: gen::Reg, imm: i32) -> u32 {
489 +
export fn bltu(rs1: gen::Reg, rs2: gen::Reg, imm: i32) -> u32 {
490 490
    return encodeB(OP_BRANCH, rs1, rs2, F3_BLTU, imm);
491 491
}
492 492
493 493
/// Branch if greater or equal unsigned: `if (rs1 >= rs2) pc += imm`.
494 -
pub fn bgeu(rs1: gen::Reg, rs2: gen::Reg, imm: i32) -> u32 {
494 +
export fn bgeu(rs1: gen::Reg, rs2: gen::Reg, imm: i32) -> u32 {
495 495
    return encodeB(OP_BRANCH, rs1, rs2, F3_BGEU, imm);
496 496
}
497 497
498 498
//////////
499 499
// Jump //
500 500
//////////
501 501
502 502
/// Jump and link: `rd = pc + 4; pc += imm`.
503 -
pub fn jal(rd: gen::Reg, imm: i32) -> u32 {
503 +
export fn jal(rd: gen::Reg, imm: i32) -> u32 {
504 504
    return encodeJ(OP_JAL, rd, imm);
505 505
}
506 506
507 507
/// Jump and link register: `rd = pc + 4; pc = rs1 + imm`.
508 -
pub fn jalr(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
508 +
export fn jalr(rd: gen::Reg, rs1: gen::Reg, imm: i32) -> u32 {
509 509
    return encodeI(OP_JALR, rd, rs1, 0, imm);
510 510
}
511 511
512 512
/////////////////////
513 513
// Upper Immediate //
514 514
/////////////////////
515 515
516 516
/// Load upper immediate: `rd = imm << 12`.
517 -
pub fn lui(rd: gen::Reg, imm: i32) -> u32 {
517 +
export fn lui(rd: gen::Reg, imm: i32) -> u32 {
518 518
    return encodeU(OP_LUI, rd, imm);
519 519
}
520 520
521 521
/// Add upper immediate to PC: `rd = pc + (imm << 12)`.
522 -
pub fn auipc(rd: gen::Reg, imm: i32) -> u32 {
522 +
export fn auipc(rd: gen::Reg, imm: i32) -> u32 {
523 523
    return encodeU(OP_AUIPC, rd, imm);
524 524
}
525 525
526 526
////////////
527 527
// System //
528 528
////////////
529 529
530 530
/// Environment call (system call).
531 -
pub fn ecall() -> u32 {
531 +
export fn ecall() -> u32 {
532 532
    return encodeI(OP_SYSTEM, super::ZERO, super::ZERO, 0, 0);
533 533
}
534 534
535 535
/// Environment break (debugger breakpoint).
536 -
pub fn ebreak() -> u32 {
536 +
export fn ebreak() -> u32 {
537 537
    return encodeI(OP_SYSTEM, super::ZERO, super::ZERO, 0, 1);
538 538
}
539 539
540 540
/////////////////////////
541 541
// Pseudo-instructions //
542 542
/////////////////////////
543 543
544 544
/// No operation: `addi zero, zero, 0`.
545 -
pub fn nop() -> u32 {
545 +
export fn nop() -> u32 {
546 546
    return addi(super::ZERO, super::ZERO, 0);
547 547
}
548 548
549 549
/// Move: `rd = rs` (`addi rd, rs, 0`).
550 -
pub fn mv(rd: gen::Reg, rs: gen::Reg) -> u32 {
550 +
export fn mv(rd: gen::Reg, rs: gen::Reg) -> u32 {
551 551
    return addi(rd, rs, 0);
552 552
}
553 553
554 554
/// Bitwise NOT: `rd = ~rs` (`xori rd, rs, -1`).
555 -
pub fn not_(rd: gen::Reg, rs: gen::Reg) -> u32 {
555 +
export fn not_(rd: gen::Reg, rs: gen::Reg) -> u32 {
556 556
    return xori(rd, rs, -1);
557 557
}
558 558
559 559
/// Negate: `rd = -rs` (`sub rd, zero, rs`).
560 -
pub fn neg(rd: gen::Reg, rs: gen::Reg) -> u32 {
560 +
export fn neg(rd: gen::Reg, rs: gen::Reg) -> u32 {
561 561
    return sub(rd, super::ZERO, rs);
562 562
}
563 563
564 564
/// Return: `jalr zero, ra, 0`.
565 -
pub fn ret() -> u32 {
565 +
export fn ret() -> u32 {
566 566
    return jalr(super::ZERO, super::RA, 0);
567 567
}
568 568
569 569
/// Jump (unconditional): `jal zero, imm`.
570 -
pub fn j(imm: i32) -> u32 {
570 +
export fn j(imm: i32) -> u32 {
571 571
    return jal(super::ZERO, imm);
572 572
}
573 573
574 574
/// Branch if less than or equal (signed): `if (rs1 <= rs2) pc += imm`.
575 575
/// Implemented as `bge rs2, rs1, imm` (swap operands).
576 -
pub fn ble(rs1: gen::Reg, rs2: gen::Reg, imm: i32) -> u32 {
576 +
export fn ble(rs1: gen::Reg, rs2: gen::Reg, imm: i32) -> u32 {
577 577
    return bge(rs2, rs1, imm);
578 578
}
579 579
580 580
/// Branch if greater than (signed): `if (rs1 > rs2) pc += imm`.
581 581
/// Implemented as `blt rs2, rs1, imm` (swap operands).
582 -
pub fn bgt(rs1: gen::Reg, rs2: gen::Reg, imm: i32) -> u32 {
582 +
export fn bgt(rs1: gen::Reg, rs2: gen::Reg, imm: i32) -> u32 {
583 583
    return blt(rs2, rs1, imm);
584 584
}
585 585
586 586
/// Set if equal to zero: `rd = (rs == 0) ? 1 : 0`.
587 587
/// Implemented as `sltiu rd, rs, 1`.
588 -
pub fn seqz(rd: gen::Reg, rs: gen::Reg) -> u32 {
588 +
export fn seqz(rd: gen::Reg, rs: gen::Reg) -> u32 {
589 589
    return sltiu(rd, rs, 1);
590 590
}
591 591
592 592
/// Set if not equal to zero: `rd = (rs != 0) ? 1 : 0`.
593 593
/// Implemented as `sltu rd, zero, rs`.
594 -
pub fn snez(rd: gen::Reg, rs: gen::Reg) -> u32 {
594 +
export fn snez(rd: gen::Reg, rs: gen::Reg) -> u32 {
595 595
    return sltu(rd, super::ZERO, rs);
596 596
}
597 597
598 598
/// Branch if equal to zero: `if (rs == 0) pc += imm`.
599 -
pub fn beqz(rs: gen::Reg, imm: i32) -> u32 {
599 +
export fn beqz(rs: gen::Reg, imm: i32) -> u32 {
600 600
    return beq(rs, super::ZERO, imm);
601 601
}
602 602
603 603
/// Branch if not equal to zero: `if (rs != 0) pc += imm`.
604 -
pub fn bnez(rs: gen::Reg, imm: i32) -> u32 {
604 +
export fn bnez(rs: gen::Reg, imm: i32) -> u32 {
605 605
    return bne(rs, super::ZERO, imm);
606 606
}
607 607
608 608
/// Call: `jal ra, imm`.
609 -
pub fn call(imm: i32) -> u32 {
609 +
export fn call(imm: i32) -> u32 {
610 610
    return jal(super::RA, imm);
611 611
}
lib/std/arch/rv64/isel.rad +3 -3
71 71
union ShiftOp { Sll, Srl, Sra }
72 72
/// Compare operation.
73 73
union CmpOp { Slt, Ult }
74 74
75 75
/// Selector errors.
76 -
pub union SelectorError {
76 +
export union SelectorError {
77 77
    Internal,
78 78
}
79 79
80 80
/// A pending spill store to be flushed after instruction selection.
81 81
record PendingSpill {
88 88
////////////////////
89 89
// Selector State //
90 90
////////////////////
91 91
92 92
/// Instruction selector state.
93 -
pub record Selector {
93 +
export record Selector {
94 94
    /// Emitter for outputting instructions.
95 95
    e: *mut emit::Emitter,
96 96
    /// Register allocation result.
97 97
    ralloc: *regalloc::AllocResult,
98 98
    /// Hash-indexed data symbol map.
302 302
    }
303 303
    return ReserveInfo { size: offset, isDynamic };
304 304
}
305 305
306 306
/// Select instructions for a function.
307 -
pub fn selectFn(
307 +
export fn selectFn(
308 308
    e: *mut emit::Emitter,
309 309
    dataSymMap: *data::DataSymMap,
310 310
    ralloc: *regalloc::AllocResult,
311 311
    func: *il::Fn
312 312
) {
lib/std/arch/rv64/printer.rad +2 -2
162 162
    writeMnem(out, m);
163 163
    writeDelim(out, &[regNameR(rs1), formatI32(a, imm)]);
164 164
}
165 165
166 166
/// Print a single instruction to output buffer.
167 -
pub fn printInstr(out: *mut sexpr::Output, a: *mut alloc::Arena, instr: u32) {
167 +
export fn printInstr(out: *mut sexpr::Output, a: *mut alloc::Arena, instr: u32) {
168 168
    let decoded = decode::decode(instr);
169 169
170 170
    match decoded {
171 171
        case decode::Instr::Lui { rd, imm } => fmtRI(out, a, "lui", rd, imm),
172 172
        case decode::Instr::Auipc { rd, imm } => fmtRI(out, a, "auipc", rd, imm),
304 304
        },
305 305
    }
306 306
}
307 307
308 308
/// Print code with labels to the given output.
309 -
pub fn printCodeTo(out: *mut sexpr::Output, pkgName: *[u8], code: *[u32], funcs: *[types::FuncAddr], arena: *mut alloc::Arena) {
309 +
export fn printCodeTo(out: *mut sexpr::Output, pkgName: *[u8], code: *[u32], funcs: *[types::FuncAddr], arena: *mut alloc::Arena) {
310 310
    // Package header.
311 311
    write(out, "# package `");
312 312
    write(out, pkgName);
313 313
    write(out, "`\n\n");
314 314
lib/std/collections.rad +1 -1
1 1
//! Collection types.
2 -
pub mod dict;
2 +
export mod dict;
lib/std/collections/dict.rad +6 -6
4 4
//! of two and is provided by the caller via arena-allocated storage.
5 5
6 6
use std::mem;
7 7
8 8
/// Hash map entry.
9 -
pub record Entry {
9 +
export record Entry {
10 10
    /// Key.
11 11
    key: *[u8],
12 12
    /// Associated value.
13 13
    value: i32,
14 14
}
15 15
16 16
/// Open-addressed hash map with caller-provided storage.
17 -
pub record Dict {
17 +
export record Dict {
18 18
    /// Hash table entries.
19 19
    entries: *mut [Entry],
20 20
    /// Number of occupied entries.
21 21
    count: u32,
22 22
}
23 23
24 24
/// Create a dict backed by the given entry storage.
25 25
/// The storage length must be a power of two.
26 -
pub fn init(entries: *mut [Entry]) -> Dict {
26 +
export fn init(entries: *mut [Entry]) -> Dict {
27 27
    for i in 0..entries.len {
28 28
        entries[i] = Entry { key: &[], value: 0 };
29 29
    }
30 30
    return Dict { entries, count: 0 };
31 31
}
32 32
33 33
/// Insert or update a key-value pair. Panics if the table exceeds 50% load.
34 -
pub fn insert(m: *mut Dict, key: *[u8], value: i32) {
34 +
export fn insert(m: *mut Dict, key: *[u8], value: i32) {
35 35
    let mask = m.entries.len - 1;
36 36
    let mut idx = hash(key) & mask;
37 37
38 38
    loop {
39 39
        let entry = m.entries[idx];
50 50
        idx = (idx + 1) & mask;
51 51
    }
52 52
}
53 53
54 54
/// Look up a value by key. Returns `nil` if not found.
55 -
pub fn get(m: *Dict, key: *[u8]) -> ?i32 {
55 +
export fn get(m: *Dict, key: *[u8]) -> ?i32 {
56 56
    let mask = m.entries.len - 1;
57 57
    let mut idx = hash(key) & mask;
58 58
59 59
    loop {
60 60
        let entry = m.entries[idx];
67 67
        idx = (idx + 1) & mask;
68 68
    }
69 69
}
70 70
71 71
/// DJB2 hash function.
72 -
pub fn hash(str: *[u8]) -> u32 {
72 +
export fn hash(str: *[u8]) -> u32 {
73 73
    let mut h: u32 = 5381;
74 74
    for b in str {
75 75
        h = ((h << 5) + h) + b as u32;
76 76
    }
77 77
    return h;
lib/std/fmt.rad +14 -14
1 1
//! Formatting utilities for converting values to strings.
2 2
use super::mem;
3 3
4 4
/// Maximum string length for a formatted u32 (eg. "4294967295").
5 -
pub const U32_STR_LEN: u32 = 10;
5 +
export const U32_STR_LEN: u32 = 10;
6 6
/// Maximum string length for a formatted i32 (eg. "-2147483648").
7 -
pub const I32_STR_LEN: u32 = U32_STR_LEN + 1;
7 +
export const I32_STR_LEN: u32 = U32_STR_LEN + 1;
8 8
/// Maximum string length for a formatted u64 (eg. "18446744073709551615").
9 -
pub const U64_STR_LEN: u32 = 20;
9 +
export const U64_STR_LEN: u32 = 20;
10 10
/// Maximum string length for a formatted i64 (eg. "-9223372036854775808").
11 -
pub const I64_STR_LEN: u32 = 20;
11 +
export const I64_STR_LEN: u32 = 20;
12 12
/// Maximum string length for a formatted bool (eg. "false").
13 -
pub const BOOL_STR_LEN: u32 = 5;
13 +
export const BOOL_STR_LEN: u32 = 5;
14 14
15 15
/// Format a u32 by writing it to the provided buffer.
16 -
pub fn formatU32(val: u32, buffer: *mut [u8]) -> *[u8] {
16 +
export fn formatU32(val: u32, buffer: *mut [u8]) -> *[u8] {
17 17
    assert buffer.len >= U32_STR_LEN;
18 18
19 19
    let mut x: u32 = val;
20 20
    let mut i: u32 = buffer.len;
21 21
35 35
    // the end of the buffer.
36 36
    return &buffer[i..];
37 37
}
38 38
39 39
/// Format a i32 by writing it to the provided buffer.
40 -
pub fn formatI32(val: i32, buffer: *mut [u8]) -> *[u8] {
40 +
export fn formatI32(val: i32, buffer: *mut [u8]) -> *[u8] {
41 41
    assert buffer.len >= I32_STR_LEN;
42 42
43 43
    let neg: bool = val < 0;
44 44
    let mut x: u32 = -val as u32 if neg else val as u32;
45 45
    let mut i: u32 = buffer.len;
62 62
    }
63 63
    return &buffer[i..];
64 64
}
65 65
66 66
/// Format a u64 by writing it to the provided buffer.
67 -
pub fn formatU64(val: u64, buffer: *mut [u8]) -> *[u8] {
67 +
export fn formatU64(val: u64, buffer: *mut [u8]) -> *[u8] {
68 68
    assert buffer.len >= U64_STR_LEN;
69 69
70 70
    let mut x: u64 = val;
71 71
    let mut i: u32 = buffer.len;
72 72
82 82
    }
83 83
    return &buffer[i..];
84 84
}
85 85
86 86
/// Format a i64 by writing it to the provided buffer.
87 -
pub fn formatI64(val: i64, buffer: *mut [u8]) -> *[u8] {
87 +
export fn formatI64(val: i64, buffer: *mut [u8]) -> *[u8] {
88 88
    assert buffer.len >= I64_STR_LEN;
89 89
90 90
    let neg: bool = val < 0;
91 91
    let mut x: u64 = -val as u64 if neg else val as u64;
92 92
    let mut i: u32 = buffer.len;
106 106
    }
107 107
    return &buffer[i..];
108 108
}
109 109
110 110
/// Format a i8 by writing it to the provided buffer.
111 -
pub fn formatI8(val: i8, buffer: *mut [u8]) -> *[u8] {
111 +
export fn formatI8(val: i8, buffer: *mut [u8]) -> *[u8] {
112 112
    return formatI32(val as i32, buffer);
113 113
}
114 114
115 115
/// Format a i16 by writing it to the provided buffer.
116 -
pub fn formatI16(val: i16, buffer: *mut [u8]) -> *[u8] {
116 +
export fn formatI16(val: i16, buffer: *mut [u8]) -> *[u8] {
117 117
    return formatI32(val as i32, buffer);
118 118
}
119 119
120 120
/// Format a u8 by writing it to the provided buffer.
121 -
pub fn formatU8(val: u8, buffer: *mut [u8]) -> *[u8] {
121 +
export fn formatU8(val: u8, buffer: *mut [u8]) -> *[u8] {
122 122
    return formatU32(val as u32, buffer);
123 123
}
124 124
125 125
/// Format a u16 by writing it to the provided buffer.
126 -
pub fn formatU16(val: u16, buffer: *mut [u8]) -> *[u8] {
126 +
export fn formatU16(val: u16, buffer: *mut [u8]) -> *[u8] {
127 127
    return formatU32(val as u32, buffer);
128 128
}
129 129
130 130
/// Format a bool by writing it to the provided buffer.
131 -
pub fn formatBool(val: bool, buffer: *mut [u8]) -> *[u8] {
131 +
export fn formatBool(val: bool, buffer: *mut [u8]) -> *[u8] {
132 132
    if val {
133 133
        try! mem::copy(buffer, "true");
134 134
        return &buffer[..4];
135 135
    } else {
136 136
        try! mem::copy(buffer, "false");
lib/std/intrinsics.rad +2 -2
3 3
/// Environment call.
4 4
///
5 5
/// Issues a system call with the given number and arguments.
6 6
/// Arguments and the return value are `i64` to support both 32-bit
7 7
/// emulator addresses and 64-bit native (AMD64) pointers.
8 -
@intrinsic pub fn ecall(number: u32, arg1: i64, arg2: i64, arg3: i64, arg4: i64) -> i64;
8 +
@intrinsic export fn ecall(number: u32, arg1: i64, arg2: i64, arg3: i64, arg4: i64) -> i64;
9 9
10 10
/// Break out of program.
11 -
@intrinsic pub fn ebreak();
11 +
@intrinsic export fn ebreak();
lib/std/io.rad +8 -8
1 1
//! Input/output utilities.
2 2
use std::fmt;
3 3
use std::intrinsics;
4 4
5 -
pub fn print(str: *[u8]) {
5 +
export fn print(str: *[u8]) {
6 6
    intrinsics::ecall(64, 1, str.ptr as i64, str.len as i64, 0);
7 7
}
8 8
9 -
pub fn printError(str: *[u8]) {
9 +
export fn printError(str: *[u8]) {
10 10
    intrinsics::ecall(64, 2, str.ptr as i64, str.len as i64, 0);
11 11
}
12 12
13 -
pub fn printLn(str: *[u8]) {
13 +
export fn printLn(str: *[u8]) {
14 14
    print(str);
15 15
    print("\n");
16 16
}
17 17
18 -
pub fn printI32(val: i32) {
18 +
export fn printI32(val: i32) {
19 19
    let mut buffer: [u8; 11] = [0; 11];
20 20
    let result: *[u8] = fmt::formatI32(val, &mut buffer[..]);
21 21
    print(result);
22 22
}
23 23
24 -
pub fn printU32(val: u32) {
24 +
export fn printU32(val: u32) {
25 25
    let mut buffer: [u8; 10] = [0; 10];
26 26
    let result: *[u8] = fmt::formatU32(val, &mut buffer[..]);
27 27
    print(result);
28 28
}
29 29
30 -
pub fn printBool(val: bool) {
30 +
export fn printBool(val: bool) {
31 31
    let mut buffer: [u8; 5] = [0; 5];
32 32
    let result: *[u8] = fmt::formatBool(val, &mut buffer[..]);
33 33
    print(result);
34 34
}
35 35
36 -
pub fn read(buf: *mut [u8]) -> u32 {
36 +
export fn read(buf: *mut [u8]) -> u32 {
37 37
    return intrinsics::ecall(63, 0, buf.ptr as i64, buf.len as i64, 0) as u32;
38 38
}
39 39
40 -
pub fn readToEnd(buf: *mut [u8]) -> *[u8] {
40 +
export fn readToEnd(buf: *mut [u8]) -> *[u8] {
41 41
    let mut total: u32 = 0;
42 42
43 43
    while total < buf.len {
44 44
        let chunk: *mut [u8] = &mut buf[total..];
45 45
        let n: u32 = read(chunk);
lib/std/lang.rad +12 -12
1 1
//! Radiance language implementation.
2 -
pub mod alloc;
3 -
pub mod sexpr;
4 -
pub mod strings;
5 -
pub mod scanner;
6 -
pub mod ast;
7 -
pub mod parser;
8 -
pub mod module;
9 -
pub mod resolver;
10 -
pub mod package;
11 -
pub mod il;
12 -
pub mod lower;
13 -
pub mod gen;
2 +
export mod alloc;
3 +
export mod sexpr;
4 +
export mod strings;
5 +
export mod scanner;
6 +
export mod ast;
7 +
export mod parser;
8 +
export mod module;
9 +
export mod resolver;
10 +
export mod package;
11 +
export mod il;
12 +
export mod lower;
13 +
export mod gen;
lib/std/lang/alloc.rad +14 -14
7 7
@test mod tests;
8 8
9 9
use std::mem;
10 10
11 11
/// Error thrown by allocator.
12 -
pub union AllocError {
12 +
export union AllocError {
13 13
    /// Allocator is out of memory.
14 14
    OutOfMemory,
15 15
}
16 16
17 17
/// Bump allocator backed by a byte slice.
18 18
///
19 19
/// Allocations are made by advancing an offset pointer. Individual allocations
20 20
/// cannot be freed; instead, the entire arena is reset at once via [`reset`].
21 -
pub record Arena {
21 +
export record Arena {
22 22
    /// Backing storage.
23 23
    data: *mut [u8],
24 24
    /// Current allocation offset in bytes.
25 25
    offset: u32,
26 26
}
27 27
28 28
/// Create a new arena backed by the given byte slice.
29 -
pub fn new(data: *mut [u8]) -> Arena {
29 +
export fn new(data: *mut [u8]) -> Arena {
30 30
    return Arena { data, offset: 0 };
31 31
}
32 32
33 33
/// Allocate `size` bytes with the given alignment.
34 34
///
35 35
/// Returns an opaque pointer to the allocated memory. Throws `AllocError` if
36 36
/// the arena is exhausted. The caller is responsible for casting to the
37 37
/// appropriate type and initializing the memory.
38 -
pub fn alloc(arena: *mut Arena, size: u32, alignment: u32) -> *mut opaque throws (AllocError) {
38 +
export fn alloc(arena: *mut Arena, size: u32, alignment: u32) -> *mut opaque throws (AllocError) {
39 39
    assert alignment > 0;
40 40
    assert size > 0;
41 41
42 42
    let aligned = mem::alignUp(arena.offset, alignment);
43 43
    let newOffset = aligned + size;
52 52
}
53 53
54 54
/// Reset the arena, allowing all memory to be reused.
55 55
///
56 56
/// Does not zero the memory.
57 -
pub fn reset(arena: *mut Arena) {
57 +
export fn reset(arena: *mut Arena) {
58 58
    arena.offset = 0;
59 59
}
60 60
61 61
/// Save the current arena state for later restoration.
62 -
pub fn save(arena: *Arena) -> u32 {
62 +
export fn save(arena: *Arena) -> u32 {
63 63
    return arena.offset;
64 64
}
65 65
66 66
/// Restore the arena to a previously saved state, reclaiming all
67 67
/// allocations made since that point.
68 -
pub fn restore(arena: *mut Arena, savedOffset: u32) {
68 +
export fn restore(arena: *mut Arena, savedOffset: u32) {
69 69
    arena.offset = savedOffset;
70 70
}
71 71
72 72
/// Returns the number of bytes currently allocated.
73 -
pub fn used(arena: *Arena) -> u32 {
73 +
export fn used(arena: *Arena) -> u32 {
74 74
    return arena.offset;
75 75
}
76 76
77 77
/// Returns the number of bytes remaining in the arena.
78 -
pub fn remaining(arena: *Arena) -> u32 {
78 +
export fn remaining(arena: *Arena) -> u32 {
79 79
    return arena.data.len as u32 - arena.offset;
80 80
}
81 81
82 82
/// Returns the remaining buffer as a mutable slice.
83 -
pub fn remainingBuf(arena: *mut Arena) -> *mut [u8] {
83 +
export fn remainingBuf(arena: *mut Arena) -> *mut [u8] {
84 84
    return &mut arena.data[arena.offset..];
85 85
}
86 86
87 87
/// Commits `size` bytes of allocation, advancing the offset.
88 88
/// Use after writing to the buffer returned by [`remainingBuf`].
89 -
pub fn commit(arena: *mut Arena, size: u32) {
89 +
export fn commit(arena: *mut Arena, size: u32) {
90 90
    arena.offset += size;
91 91
}
92 92
93 93
/// Allocate a slice of `count` elements, each of `size` bytes with given alignment.
94 94
///
95 95
/// Returns a type-erased slice that should be cast to the appropriate `*[T]`.
96 96
/// The slice length is set to `count` (element count, not bytes).
97 97
/// Throws `AllocError` if the arena is exhausted.
98 -
pub fn allocSlice(arena: *mut Arena, size: u32, alignment: u32, count: u32) -> *mut [opaque] throws (AllocError) {
98 +
export fn allocSlice(arena: *mut Arena, size: u32, alignment: u32, count: u32) -> *mut [opaque] throws (AllocError) {
99 99
    if count == 0 {
100 100
        return &mut [];
101 101
    }
102 102
    let ptr = try alloc(arena, size * count, alignment);
103 103
109 109
/// Bundles an allocation function with an opaque context pointer so that
110 110
/// any allocation strategy (arena, free-list, mmap, pool) can be used
111 111
/// through a uniform interface. The `func` field is called with the context
112 112
/// pointer, a byte size and an alignment, and must return a pointer to
113 113
/// the allocated memory or panic on failure.
114 -
pub record Allocator {
114 +
export record Allocator {
115 115
    /// Allocation function. Returns a pointer to `size` bytes
116 116
    /// aligned to `alignment`, or panics on failure.
117 117
    func: fn(*mut opaque, u32, u32) -> *mut opaque,
118 118
    /// Opaque context pointer passed to `func`.
119 119
    ctx: *mut opaque,
120 120
}
121 121
122 122
/// Create an `Allocator` backed by an `Arena`.
123 -
pub fn arenaAllocator(arena: *mut Arena) -> Allocator {
123 +
export fn arenaAllocator(arena: *mut Arena) -> Allocator {
124 124
    return Allocator {
125 125
        func: arenaAllocFn,
126 126
        ctx: arena as *mut opaque,
127 127
    };
128 128
}
lib/std/lang/ast.rad +60 -60
1 1
//! Radiance AST modules.
2 -
pub mod printer;
2 +
export mod printer;
3 3
4 4
use std::io;
5 5
use std::lang::alloc;
6 6
7 7
/// Maximum number of trait methods.
8 -
pub const MAX_TRAIT_METHODS: u32 = 8;
8 +
export const MAX_TRAIT_METHODS: u32 = 8;
9 9
10 10
/// Arena for all parser allocations.
11 11
///
12 12
/// Uses a bump allocator for both AST nodes and node pointer arrays.
13 -
pub record NodeArena {
13 +
export record NodeArena {
14 14
    /// Bump allocator for all allocations.
15 15
    arena: alloc::Arena,
16 16
    /// Next node ID to assign. Incremented on each node allocation.
17 17
    nextId: u32,
18 18
}
19 19
20 20
/// Initialize a node arena backed by the given byte slice.
21 -
pub fn nodeArena(data: *mut [u8]) -> NodeArena {
21 +
export fn nodeArena(data: *mut [u8]) -> NodeArena {
22 22
    return NodeArena {
23 23
        arena: alloc::new(data),
24 24
        nextId: 0,
25 25
    };
26 26
}
27 27
28 28
/// Create an empty `*mut [*Node]` slice with the given capacity.
29 -
pub fn nodeSlice(arena: *mut NodeArena, capacity: u32) -> *mut [*Node] {
29 +
export fn nodeSlice(arena: *mut NodeArena, capacity: u32) -> *mut [*Node] {
30 30
    if capacity == 0 {
31 31
        return &mut [];
32 32
    }
33 33
    let ptr = try! alloc::allocSlice(&mut arena.arena, @sizeOf(*Node), @alignOf(*Node), capacity);
34 34
35 35
    return @sliceOf(ptr.ptr as *mut *Node, 0, capacity);
36 36
}
37 37
38 38
/// Attribute bit set applied to declarations or fields.
39 -
pub union Attribute {
39 +
export union Attribute {
40 40
    /// Public visibility attribute.
41 -
    Pub = 0b1,
41 +
    Export = 0b1,
42 42
    /// Default implementation attribute.
43 43
    Default = 0b10,
44 44
    /// Extern linkage attribute.
45 45
    Extern = 0b100,
46 46
    /// Test-only declaration attribute.
48 48
    /// Compiler intrinsic attribute.
49 49
    Intrinsic = 0b10000,
50 50
}
51 51
52 52
/// Ordered collection of attribute nodes applied to a declaration.
53 -
pub record Attributes {
53 +
export record Attributes {
54 54
    list: *mut [*Node],
55 55
}
56 56
57 57
/// Check if an attributes list contains an attribute.
58 -
pub fn attributesContains(self: *Attributes, attr: Attribute) -> bool {
58 +
export fn attributesContains(self: *Attributes, attr: Attribute) -> bool {
59 59
    for node in self.list {
60 60
        if let case NodeValue::Attribute(a) = node.value; a == attr {
61 61
            return true;
62 62
        }
63 63
    }
64 64
    return false;
65 65
}
66 66
67 67
/// Check if an attribute set includes the given attribute.
68 -
pub fn hasAttribute(attrs: u32, attr: Attribute) -> bool {
68 +
export fn hasAttribute(attrs: u32, attr: Attribute) -> bool {
69 69
    return (attrs & (attr as u32)) != 0;
70 70
}
71 71
72 72
/// Signedness of an integer type.
73 -
pub union Signedness {
73 +
export union Signedness {
74 74
    /// Signed, eg. `i8`.
75 75
    Signed,
76 76
    /// Unsigned, eg. `u32`.
77 77
    Unsigned,
78 78
}
79 79
80 80
/// Radix/base of a number.
81 -
pub union Radix {
81 +
export union Radix {
82 82
    /// Binary literal (0b...).
83 83
    Binary,
84 84
    /// Decimal literal.
85 85
    Decimal,
86 86
    /// Hexadecimal literal (0x...).
87 87
    Hex,
88 88
}
89 89
90 90
/// Parsed integer literal metadata.
91 -
pub record IntLiteral {
91 +
export record IntLiteral {
92 92
    /// Raw characters that comprised the literal.
93 93
    text: *[u8],
94 94
    /// Absolute magnitude parsed from the literal.
95 95
    magnitude: u64,
96 96
    /// Radix used by the literal.
100 100
    /// Whether the literal used a negative sign.
101 101
    negative: bool,
102 102
}
103 103
104 104
/// Binary operator kinds used in numeric expressions.
105 -
pub union BinaryOp {
105 +
export union BinaryOp {
106 106
    /// Addition (`+`).
107 107
    Add,
108 108
    /// Subtraction (`-`).
109 109
    Sub,
110 110
    /// Multiplication (`*`).
144 144
    /// Logical exclusive disjunction (`xor`).
145 145
    Xor,
146 146
}
147 147
148 148
/// Unary operator kinds used in expressions.
149 -
pub union UnaryOp {
149 +
export union UnaryOp {
150 150
    /// Logical negation (`not`).
151 151
    Not,
152 152
    /// Arithmetic negation (`-`).
153 153
    Neg,
154 154
    /// Bitwise NOT (`~`).
155 155
    BitNot,
156 156
}
157 157
158 158
/// Builtin function kind.
159 -
pub union Builtin {
159 +
export union Builtin {
160 160
    /// Size of type in bytes (`@sizeOf`).
161 161
    SizeOf,
162 162
    /// Alignment requirement of type (`@alignOf`).
163 163
    AlignOf,
164 164
    /// Construct a slice from pointer, length, and optional capacity (`@sliceOf`).
165 165
    SliceOf,
166 166
}
167 167
168 168
/// Source extent for a node measured in bytes.
169 -
pub record Span {
169 +
export record Span {
170 170
    /// Byte offset from the start of the source file.
171 171
    offset: u32,
172 172
    /// Length of the node in bytes.
173 173
    length: u32,
174 174
}
175 175
176 176
/// Type signature node.
177 -
pub union TypeSig {
177 +
export union TypeSig {
178 178
    /// Absence of type.
179 179
    Void,
180 180
    /// Opaque type.
181 181
    Opaque,
182 182
    /// Boolean type.
233 233
        mutable: bool,
234 234
    },
235 235
}
236 236
237 237
/// Function signature.
238 -
pub record FnSig {
238 +
export record FnSig {
239 239
    /// Parameter type nodes in declaration order.
240 240
    params: *mut [*Node],
241 241
    /// Optional return type node.
242 242
    returnType: ?*Node,
243 243
    /// Throwable type nodes declared in the signature.
244 244
    throwList: *mut [*Node],
245 245
}
246 246
247 247
/// Address-of expression metadata.
248 -
pub record AddressOf {
248 +
export record AddressOf {
249 249
    /// Target expression being referenced.
250 250
    target: *Node,
251 251
    /// Indicates whether the reference is mutable.
252 252
    mutable: bool,
253 253
}
254 254
255 255
/// Compound statement block with optional dedicated scope.
256 -
pub record Block {
256 +
export record Block {
257 257
    /// Statements that belong to this block.
258 258
    statements: *mut [*Node],
259 259
}
260 260
261 261
/// Function call expression.
262 -
pub record Call {
262 +
export record Call {
263 263
    /// Callee expression.
264 264
    callee: *Node,
265 265
    /// Argument expressions in source order.
266 266
    args: *mut [*Node],
267 267
}
268 268
269 269
/// Single argument to a function or record literal, optionally labeled.
270 -
pub record Arg {
270 +
export record Arg {
271 271
    /// Optional label applied to the argument.
272 272
    label: ?*Node,
273 273
    /// Expression supplying the argument value.
274 274
    value: *Node,
275 275
}
276 276
277 277
/// Assignment expression connecting a target and value.
278 -
pub record Assign {
278 +
export record Assign {
279 279
    /// Expression representing the assignment target.
280 280
    left: *Node,
281 281
    /// Expression providing the value being assigned.
282 282
    right: *Node,
283 283
}
284 284
285 285
/// While loop with an optional alternate branch.
286 -
pub record While {
286 +
export record While {
287 287
    /// Condition evaluated before each iteration.
288 288
    condition: *Node,
289 289
    /// Loop body executed while `condition` is true.
290 290
    body: *Node,
291 291
    /// Optional branch executed when the condition is false at entry.
292 292
    elseBranch: ?*Node,
293 293
}
294 294
295 295
/// `while let` loop binding metadata.
296 -
pub record WhileLet {
296 +
export record WhileLet {
297 297
    /// Pattern matching structure.
298 298
    pattern: PatternMatch,
299 299
    /// Loop body executed when the pattern matches.
300 300
    body: *Node,
301 301
    /// Optional branch executed when the match fails immediately.
302 302
    elseBranch: ?*Node,
303 303
}
304 304
305 305
/// Try expression metadata.
306 -
pub record Try {
306 +
export record Try {
307 307
    /// Expression evaluated with implicit error propagation.
308 308
    expr: *Node,
309 309
    /// Catch clauses. Empty for propagation (`try`), `try!`, or `try?`.
310 310
    catches: *mut [*Node],
311 311
    /// Whether the try should panic instead of returning an error.
313 313
    /// Whether the try should return an optional instead of propagating error.
314 314
    returnsOptional: bool,
315 315
}
316 316
317 317
/// A single catch clause in a `try ... catch` expression.
318 -
pub record CatchClause {
318 +
export record CatchClause {
319 319
    /// Optional identifier binding for the error value (eg. `e`).
320 320
    binding: ?*Node,
321 321
    /// Optional type annotation after `as` (eg. `IoError`).
322 322
    typeNode: ?*Node,
323 323
    /// Block body executed when this clause matches.
324 324
    body: *Node,
325 325
}
326 326
327 327
/// `for` loop metadata.
328 -
pub record For {
328 +
export record For {
329 329
    /// Loop variable binding.
330 330
    binding: *Node,
331 331
    /// Optional index binding for enumeration loops.
332 332
    index: ?*Node,
333 333
    /// Expression producing the iterable value.
337 337
    /// Optional branch executed when the loop body never runs.
338 338
    elseBranch: ?*Node,
339 339
}
340 340
341 341
/// Conditional `if` statement metadata.
342 -
pub record If {
342 +
export record If {
343 343
    /// Condition controlling the branch.
344 344
    condition: *Node,
345 345
    /// Branch executed when `condition` is true.
346 346
    thenBranch: *Node,
347 347
    /// Optional branch executed when `condition` is false.
348 348
    elseBranch: ?*Node,
349 349
}
350 350
351 351
/// Conditional expression (`<true> if <condition> else <false>`).
352 -
pub record CondExpr {
352 +
export record CondExpr {
353 353
    /// Condition controlling which branch is evaluated.
354 354
    condition: *Node,
355 355
    /// Expression evaluated when `condition` is true.
356 356
    thenExpr: *Node,
357 357
    /// Expression evaluated when `condition` is false.
358 358
    elseExpr: *Node,
359 359
}
360 360
361 361
/// Classification of pattern matches (if-let, while-let, let-else).
362 -
pub union PatternKind {
362 +
export union PatternKind {
363 363
    /// Case pattern match.
364 364
    Case,
365 365
    /// Binding pattern match.
366 366
    Binding,
367 367
}
368 368
369 369
/// Prong arm.
370 -
pub union ProngArm {
370 +
export union ProngArm {
371 371
    /// Case arm with pattern list.
372 372
    Case(*mut [*Node]),
373 373
    /// Binding arm with single identifier or placeholder.
374 374
    Binding(*Node),
375 375
    /// Else arm.
376 376
    Else,
377 377
}
378 378
379 379
/// Common pattern matching structure used by `if let`, `while let`, and `let-else`.
380 -
pub record PatternMatch {
380 +
export record PatternMatch {
381 381
    /// Pattern or binding to match against.
382 382
    pattern: *Node,
383 383
    /// Scrutinee expression to match against.
384 384
    scrutinee: *Node,
385 385
    /// Optional guard that must evaluate to `true`.
389 389
    /// Whether the binding is mutable.
390 390
    mutable: bool,
391 391
}
392 392
393 393
/// `if let` conditional binding metadata.
394 -
pub record IfLet {
394 +
export record IfLet {
395 395
    /// Pattern matching structure.
396 396
    pattern: PatternMatch,
397 397
    /// Branch executed when the pattern matches.
398 398
    thenBranch: *Node,
399 399
    /// Optional branch executed when the match fails.
400 400
    elseBranch: ?*Node,
401 401
}
402 402
403 403
/// `let-else` statement metadata.
404 -
pub record LetElse {
404 +
export record LetElse {
405 405
    /// Pattern matching structure.
406 406
    pattern: PatternMatch,
407 407
    /// Else branch executed if match fails (must diverge).
408 408
    elseBranch: *Node,
409 409
}
410 410
411 411
/// `match` statement metadata.
412 -
pub record Match {
412 +
export record Match {
413 413
    /// Expression whose value controls the match.
414 414
    subject: *Node,
415 415
    /// Prong nodes evaluated in order.
416 416
    prongs: *mut [*Node],
417 417
}
418 418
419 419
/// `match` prong metadata.
420 -
pub record MatchProng {
420 +
export record MatchProng {
421 421
    /// Prong arm.
422 422
    arm: ProngArm,
423 423
    /// Optional guard that must evaluate to `true`.
424 424
    guard: ?*Node,
425 425
    /// Body executed when patterns match and guard passes.
426 426
    body: *Node,
427 427
}
428 428
429 429
/// `let` binding.
430 -
pub record Let {
430 +
export record Let {
431 431
    /// Identifier bound by the declaration.
432 432
    ident: *Node,
433 433
    /// Declared type annotation.
434 434
    type: ?*Node,
435 435
    /// Initializer expression.
439 439
    /// Whether the variable is mutable.
440 440
    mutable: bool,
441 441
}
442 442
443 443
/// Constant declaration.
444 -
pub record ConstDecl {
444 +
export record ConstDecl {
445 445
    /// Identifier bound by the declaration.
446 446
    ident: *Node,
447 447
    /// Declared type annotation.
448 448
    type: *Node,
449 449
    /// Constant initializer expression.
451 451
    /// Optional attribute list applied to the constant.
452 452
    attrs: ?Attributes,
453 453
}
454 454
455 455
/// Static storage declaration.
456 -
pub record StaticDecl {
456 +
export record StaticDecl {
457 457
    /// Identifier bound by the declaration.
458 458
    ident: *Node,
459 459
    /// Declared storage type.
460 460
    type: *Node,
461 461
    /// Initialization expression.
463 463
    /// Optional attribute list applied to the static.
464 464
    attrs: ?Attributes,
465 465
}
466 466
467 467
/// Function parameter declaration.
468 -
pub record FnParam {
468 +
export record FnParam {
469 469
    /// Parameter identifier.
470 470
    name: *Node,
471 471
    /// Parameter type annotation.
472 472
    type: *Node,
473 473
}
474 474
475 475
/// Record literal expression metadata.
476 -
pub record RecordLit {
476 +
export record RecordLit {
477 477
    /// Type name associated with the literal.
478 478
    /// If `nil`, it's an anonymous record literal.
479 479
    typeName: ?*Node,
480 480
    /// Field initializer nodes.
481 481
    fields: *mut [*Node],
482 482
    /// When true, remaining fields are discarded (`{ x, .. }`).
483 483
    ignoreRest: bool,
484 484
}
485 485
486 486
/// Record declaration.
487 -
pub record RecordDecl {
487 +
export record RecordDecl {
488 488
    /// Identifier naming the record.
489 489
    name: *Node,
490 490
    /// Field declaration nodes.
491 491
    fields: *mut [*Node],
492 492
    /// Optional attribute list applied to the record.
496 496
    /// Whether this record has labeled fields.
497 497
    labeled: bool,
498 498
}
499 499
500 500
/// Union declarations.
501 -
pub record UnionDecl {
501 +
export record UnionDecl {
502 502
    /// Identifier naming the union.
503 503
    name: *Node,
504 504
    /// Variant nodes making up the union.
505 505
    variants: *mut [*Node],
506 506
    /// Optional attribute list applied to the union.
508 508
    /// Trait derivations attached to the union.
509 509
    derives: *mut [*Node],
510 510
}
511 511
512 512
/// Union variant declaration.
513 -
pub record UnionDeclVariant {
513 +
export record UnionDeclVariant {
514 514
    /// Identifier naming the variant.
515 515
    name: *Node,
516 516
    /// Variant index.
517 517
    index: u32,
518 518
    /// Explicit discriminant value, if provided.
520 520
    /// Optional payload type.
521 521
    type: ?*Node,
522 522
}
523 523
524 524
/// Function declaration.
525 -
pub record FnDecl {
525 +
export record FnDecl {
526 526
    /// Identifier naming the function.
527 527
    name: *Node,
528 528
    /// Function type signature.
529 529
    sig: FnSig,
530 530
    /// Optional function body (`nil` for extern functions).
532 532
    /// Optional attribute list applied to the function.
533 533
    attrs: ?Attributes,
534 534
}
535 535
536 536
/// Array repeat literal metadata.
537 -
pub record ArrayRepeatLit {
537 +
export record ArrayRepeatLit {
538 538
    /// Expression providing the repeated value.
539 539
    item: *Node,
540 540
    /// Expression providing the repetition count.
541 541
    count: *Node,
542 542
}
543 543
544 544
/// Module declaration.
545 -
pub record Mod {
545 +
export record Mod {
546 546
    /// Identifier naming the module.
547 547
    name: *Node,
548 548
    /// Optional attribute list applied to the module.
549 549
    attrs: ?Attributes,
550 550
}
551 551
552 552
/// Use declaration for importing modules.
553 -
pub record Use {
553 +
export record Use {
554 554
    /// Access node identifying the imported module.
555 555
    path: *Node,
556 556
    /// Whether this is a wildcard import (e.g. `use ast::*`).
557 557
    wildcard: bool,
558 558
    /// Optional attribute list applied to the use declaration.
559 559
    attrs: ?Attributes,
560 560
}
561 561
562 562
/// Access expression used for field, scope, and index lookups.
563 -
pub record Access {
563 +
export record Access {
564 564
    /// Expression providing the container or namespace.
565 565
    parent: *Node,
566 566
    /// Expression identifying the member, scope element, or index.
567 567
    child: *Node,
568 568
}
569 569
570 570
/// `as` cast expression metadata.
571 -
pub record As {
571 +
export record As {
572 572
    /// Expression being coerced.
573 573
    value: *Node,
574 574
    /// Target type annotation.
575 575
    type: *Node,
576 576
}
577 577
578 578
/// Range expression metadata.
579 -
pub record Range {
579 +
export record Range {
580 580
    /// Optional inclusive start expression.
581 581
    start: ?*Node,
582 582
    /// Optional exclusive end expression.
583 583
    end: ?*Node,
584 584
}
585 585
586 586
/// Binary operation expression, eg. `x * y`.
587 -
pub record BinOp {
587 +
export record BinOp {
588 588
    /// Operator applied to the operands.
589 589
    op: BinaryOp,
590 590
    /// Left-hand operand.
591 591
    left: *Node,
592 592
    /// Right-hand operand.
593 593
    right: *Node,
594 594
}
595 595
596 596
/// Unary operation expression, eg. `-x`.
597 -
pub record UnOp {
597 +
export record UnOp {
598 598
    /// Operator applied to the operand.
599 599
    op: UnaryOp,
600 600
    /// Operand expression.
601 601
    value: *Node,
602 602
}
603 603
604 604
/// Tagged union describing every possible AST node payload.
605 -
pub union NodeValue {
605 +
export union NodeValue {
606 606
    /// Placeholder `_` expression.
607 607
    Placeholder,
608 608
    /// Nil literal (`nil`).
609 609
    Nil,
610 610
    /// Undefined literal (`undefined`).
804 804
        attrs: ?Attributes,
805 805
    },
806 806
}
807 807
808 808
/// Full AST node with shared metadata and variant-specific payload.
809 -
pub record Node {
809 +
export record Node {
810 810
    /// Unique identifier for this node.
811 811
    id: u32,
812 812
    /// Source span describing where the node originated.
813 813
    span: Span,
814 814
    /// Variant-specific payload for the node.
815 815
    value: NodeValue,
816 816
}
817 817
818 818
/// Allocate a new AST node from the arena with the given span and value.
819 -
pub fn allocNode(arena: *mut NodeArena, span: Span, value: NodeValue) -> *mut Node {
819 +
export fn allocNode(arena: *mut NodeArena, span: Span, value: NodeValue) -> *mut Node {
820 820
    let p = try! alloc::alloc(&mut arena.arena, @sizeOf(Node), @alignOf(Node));
821 821
    let node = p as *mut Node;
822 822
    let nodeId = arena.nextId;
823 823
    arena.nextId = nodeId + 1;
824 824
826 826
827 827
    return node;
828 828
}
829 829
830 830
/// Allocate a synthetic AST node with a zero-length span.
831 -
pub fn synthNode(arena: *mut NodeArena, value: NodeValue) -> *mut Node {
831 +
export fn synthNode(arena: *mut NodeArena, value: NodeValue) -> *mut Node {
832 832
    return allocNode(arena, Span { offset: 0, length: 0 }, value);
833 833
}
834 834
835 835
/// Synthetic module with a single function in it.
836 836
record SynthFnMod {
839 839
    /// The function block.
840 840
    fnBody: *Node
841 841
}
842 842
843 843
/// Synthesize a module with a function in it with the given name and statements.
844 -
pub fn synthFnModule(
844 +
export fn synthFnModule(
845 845
    arena: *mut NodeArena, name: *[u8], bodyStmts: *mut [*Node]
846 846
) -> SynthFnMod {
847 847
    let a = alloc::arenaAllocator(&mut arena.arena);
848 848
    let fnName = synthNode(arena, NodeValue::Ident(name));
849 849
    let params: *mut [*Node] = &mut [];
lib/std/lang/ast/printer.rad +3 -3
229 229
    }
230 230
    return buf;
231 231
}
232 232
233 233
/// Convert an AST node to an S-expression.
234 -
pub fn toExpr(a: *mut alloc::Arena, node: *super::Node) -> sexpr::Expr {
234 +
export fn toExpr(a: *mut alloc::Arena, node: *super::Node) -> sexpr::Expr {
235 235
    match node.value {
236 236
        case super::NodeValue::Placeholder => return sexpr::sym("_"),
237 237
        case super::NodeValue::Nil => return sexpr::sym("nil"),
238 238
        case super::NodeValue::Undef => return sexpr::sym("undefined"),
239 239
        case super::NodeValue::Bool(v) => {
300 300
        case super::NodeValue::TypeSig(sig) => return typeSigToExpr(a, sig),
301 301
        case super::NodeValue::FnParam(p) =>
302 302
            return sexpr::list(a, "param", &[toExpr(a, p.name), toExpr(a, p.type)]),
303 303
        case super::NodeValue::Attribute(attr) => {
304 304
            match attr {
305 -
                case super::Attribute::Pub => return sexpr::sym("@pub"),
305 +
                case super::Attribute::Export => return sexpr::sym("@export"),
306 306
                case super::Attribute::Default => return sexpr::sym("@default"),
307 307
                case super::Attribute::Extern => return sexpr::sym("@extern"),
308 308
                case super::Attribute::Test => return sexpr::sym("@test"),
309 309
                case super::Attribute::Intrinsic => return sexpr::sym("@intrinsic"),
310 310
            }
463 463
        else => return sexpr::sym("?"),
464 464
    }
465 465
}
466 466
467 467
/// Dump the tree rooted at `root`, using the provided arena for allocation.
468 -
pub fn printTree(root: *super::Node, arena: *mut alloc::Arena) {
468 +
export fn printTree(root: *super::Node, arena: *mut alloc::Arena) {
469 469
    match root.value {
470 470
        case super::NodeValue::Block(blk) => {
471 471
            for stmt, i in blk.statements {
472 472
                sexpr::print(toExpr(arena, stmt), 0);
473 473
                if i < blk.statements.len - 1 { io::print("\n\n"); }
lib/std/lang/gen.rad +6 -6
2 2
//!
3 3
//! Target-independent infrastructure for lowering IL to machine code.
4 4
//! Target-specific backends (e.g., `std::arch::rv64`) provide
5 5
//! instruction selection and emission.
6 6
7 -
pub mod labels;
8 -
pub mod bitset;
9 -
pub mod regalloc;
10 -
pub mod data;
11 -
pub mod types;
7 +
export mod labels;
8 +
export mod bitset;
9 +
export mod regalloc;
10 +
export mod data;
11 +
export mod types;
12 12
13 13
/// Physical register.
14 14
///
15 15
/// Lightweight wrapper around a register number. Used both in the
16 16
/// target-independent register allocator and in architecture-specific
17 17
/// backends.
18 -
pub record Reg(u8);
18 +
export record Reg(u8);
lib/std/lang/gen/bitset.rad +17 -17
21 21
    }
22 22
    return b;
23 23
}
24 24
25 25
/// Calculate the number of 32-bit words needed to store `n` bits.
26 -
pub fn wordsFor(n: u32) -> u32 {
26 +
export fn wordsFor(n: u32) -> u32 {
27 27
    return (n + 31) / 32;
28 28
}
29 29
30 30
/// A fixed-size bitset backed by an array of 32-bit words.
31 -
pub record Bitset {
31 +
export record Bitset {
32 32
    /// Backing storage for bits, organized as 32-bit words.
33 33
    bits: *mut [u32],
34 34
    /// Number of bits this bitset can hold.
35 35
    len: u32,
36 36
}
37 37
38 38
/// Create a new bitset backed by the given zero-initialized storage.
39 -
pub fn new(bits: *mut [u32]) -> Bitset {
39 +
export fn new(bits: *mut [u32]) -> Bitset {
40 40
    return Bitset { bits, len: bits.len * 32 };
41 41
}
42 42
43 43
/// Create a new bitset backed by the given storage, zeroing it first.
44 -
pub fn init(bits: *mut [u32]) -> Bitset {
44 +
export fn init(bits: *mut [u32]) -> Bitset {
45 45
    for i in 0..bits.len {
46 46
        bits[i] = 0;
47 47
    }
48 48
    return new(bits);
49 49
}
50 50
51 51
/// Create a bitset from arena allocation.
52 -
pub fn allocate(arena: *mut alloc::Arena, len: u32) -> Bitset throws (alloc::AllocError) {
52 +
export fn allocate(arena: *mut alloc::Arena, len: u32) -> Bitset throws (alloc::AllocError) {
53 53
    let numWords = wordsFor(len);
54 54
    let bits = try alloc::allocSlice(arena, @sizeOf(u32), @alignOf(u32), numWords) as *mut [u32];
55 55
56 56
    return init(bits);
57 57
}
58 58
59 59
/// Set bit `n` in the bitset.
60 -
pub fn set(bs: *mut Bitset, n: u32) {
60 +
export fn set(bs: *mut Bitset, n: u32) {
61 61
    if n >= bs.len {
62 62
        return;
63 63
    }
64 64
    let word = n / 32;
65 65
    let b = n % 32;
66 66
67 67
    bs.bits[word] |= (1 << b);
68 68
}
69 69
70 70
/// Clear bit `n` in the bitset.
71 -
pub fn clear(bs: *mut Bitset, n: u32) {
71 +
export fn clear(bs: *mut Bitset, n: u32) {
72 72
    if n >= bs.len {
73 73
        return;
74 74
    }
75 75
    let word = n / 32;
76 76
    let b = n % 32;
77 77
78 78
    bs.bits[word] &= ~(1 << b);
79 79
}
80 80
81 81
/// Check if bit `n` is set.
82 -
pub fn contains(bs: *Bitset, n: u32) -> bool {
82 +
export fn contains(bs: *Bitset, n: u32) -> bool {
83 83
    if n >= bs.len {
84 84
        return false;
85 85
    }
86 86
    let word = n / 32;
87 87
    let b = n % 32;
88 88
89 89
    return (bs.bits[word] & (1 << b)) != 0;
90 90
}
91 91
92 92
/// Count the number of set bits.
93 -
pub fn count(bs: *Bitset) -> u32 {
93 +
export fn count(bs: *Bitset) -> u32 {
94 94
    let mut total: u32 = 0;
95 95
    let numWords = bs.bits.len;
96 96
    for i in 0..numWords {
97 97
        let word = bs.bits[i];
98 98
        if word != 0 {
113 113
114 114
    return n & 0x3F;
115 115
}
116 116
117 117
/// Union: `dst = dst | src`.
118 -
pub fn union_(dst: *mut Bitset, src: *Bitset) {
118 +
export fn union_(dst: *mut Bitset, src: *Bitset) {
119 119
    let numWords = dst.bits.len;
120 120
    let srcWords = src.bits.len;
121 121
    let minWords = min(numWords, srcWords);
122 122
    for i in 0..minWords {
123 123
        dst.bits[i] |= src.bits[i];
124 124
    }
125 125
}
126 126
127 127
/// Subtract: `dst = dst - src`.
128 -
pub fn subtract(dst: *mut Bitset, src: *Bitset) {
128 +
export fn subtract(dst: *mut Bitset, src: *Bitset) {
129 129
    let numWords = dst.bits.len;
130 130
    let srcWords = src.bits.len;
131 131
    let minWords = min(numWords, srcWords);
132 132
    for i in 0..minWords {
133 133
        dst.bits[i] &= ~src.bits[i];
134 134
    }
135 135
}
136 136
137 137
/// Check if two bitsets are equal.
138 -
pub fn eq(a: *Bitset, b: *Bitset) -> bool {
138 +
export fn eq(a: *Bitset, b: *Bitset) -> bool {
139 139
    let numWordsA = a.bits.len;
140 140
    let numWordsB = b.bits.len;
141 141
    let maxWords = max(numWordsA, numWordsB);
142 142
143 143
    for i in 0..maxWords {
149 149
    }
150 150
    return true;
151 151
}
152 152
153 153
/// Copy bits from source to destination.
154 -
pub fn copy(dst: *mut Bitset, src: *Bitset) {
154 +
export fn copy(dst: *mut Bitset, src: *Bitset) {
155 155
    let numWords = dst.bits.len;
156 156
    let srcWords = src.bits.len;
157 157
    let minWords = min(numWords, srcWords);
158 158
159 159
    for i in 0..minWords {
164 164
        dst.bits[i] = 0;
165 165
    }
166 166
}
167 167
168 168
/// Clear all bits.
169 -
pub fn clearAll(bs: *mut Bitset) {
169 +
export fn clearAll(bs: *mut Bitset) {
170 170
    let numWords = bs.bits.len;
171 171
    for i in 0..numWords {
172 172
        bs.bits[i] = 0;
173 173
    }
174 174
}
175 175
176 176
/// Iterator state for iterating set bits.
177 -
pub record BitIter {
177 +
export record BitIter {
178 178
    /// Bitset being iterated.
179 179
    bs: *Bitset,
180 180
    /// Current word index.
181 181
    wordIdx: u32,
182 182
    /// Remaining bits in the current word (visited bits cleared).
183 183
    remaining: u32,
184 184
}
185 185
186 186
/// Create an iterator over set bits.
187 -
pub fn iter(bs: *Bitset) -> BitIter {
187 +
export fn iter(bs: *Bitset) -> BitIter {
188 188
    let remaining = bs.bits[0] if bs.len > 0 else 0;
189 189
    return BitIter { bs, wordIdx: 0, remaining };
190 190
}
191 191
192 192
/// Get the next set bit, or nil if none remain.
193 -
pub fn iterNext(it: *mut BitIter) -> ?u32 {
193 +
export fn iterNext(it: *mut BitIter) -> ?u32 {
194 194
    let numWords = it.bs.bits.len;
195 195
    // Skip to next non-zero word.
196 196
    while it.remaining == 0 {
197 197
        it.wordIdx += 1;
198 198
        if it.wordIdx >= numWords {
lib/std/lang/gen/data.rad +8 -8
7 7
use std::collections::dict;
8 8
use std::lang::il;
9 9
use std::lang::gen::labels;
10 10
11 11
/// Maximum number of data symbols.
12 -
pub const MAX_DATA_SYMS: u32 = 8192;
12 +
export const MAX_DATA_SYMS: u32 = 8192;
13 13
14 14
/// Size of the data symbol hash table. Must be a power of two
15 15
/// and at least twice the size of [`MAX_DATA_SYMS`].
16 -
pub const DATA_SYM_TABLE_SIZE: u32 = MAX_DATA_SYMS * 2;
16 +
export const DATA_SYM_TABLE_SIZE: u32 = MAX_DATA_SYMS * 2;
17 17
18 18
/// Data symbol entry mapping name to address.
19 -
pub record DataSym {
19 +
export record DataSym {
20 20
    /// Symbol name.
21 21
    name: *[u8],
22 22
    /// Absolute address, including data base address.
23 23
    addr: u32,
24 24
}
25 25
26 26
/// Hash-indexed data symbol map.
27 -
pub record DataSymMap {
27 +
export record DataSymMap {
28 28
    /// Underlying hash table.
29 29
    dict: dict::Dict,
30 30
    /// Fallback linear array for edge cases.
31 31
    syms: *[DataSym],
32 32
}
33 33
34 34
/// Lay out data symbols for a single section.
35 35
/// Initialized data is placed first, then uninitialized, so that only
36 36
/// initialized data needs to be written to the output file.
37 37
/// Returns the updated offset past all placed symbols.
38 -
pub fn layoutSection(
38 +
export fn layoutSection(
39 39
    program: *il::Program,
40 40
    syms: *mut [DataSym],
41 41
    count: *mut u32,
42 42
    base: u32,
43 43
    readOnly: bool
68 68
}
69 69
70 70
/// Emit data bytes for a single section (read-only or read-write) into `buf`.
71 71
/// Iterates initialized data in the IL program, serializing each data item.
72 72
/// Returns the total number of bytes written.
73 -
pub fn emitSection(
73 +
export fn emitSection(
74 74
    program: *il::Program,
75 75
    dataSymMap: *DataSymMap,
76 76
    fnLabels: *labels::Labels,
77 77
    codeBase: u32,
78 78
    buf: *mut [u8],
131 131
    return offset;
132 132
}
133 133
134 134
/// Build a hash-indexed data symbol map from the laid-out symbols.
135 135
/// The `entries` slice must have length `DATA_SYM_TABLE_SIZE`.
136 -
pub fn buildMap(syms: *[DataSym], entries: *mut [dict::Entry]) -> DataSymMap {
136 +
export fn buildMap(syms: *[DataSym], entries: *mut [dict::Entry]) -> DataSymMap {
137 137
    let mut d = dict::init(entries);
138 138
    for i in 0..syms.len {
139 139
        dict::insert(&mut d, syms[i].name, syms[i].addr as i32);
140 140
    }
141 141
    return DataSymMap { dict: d, syms };
142 142
}
143 143
144 144
/// Resolve a data symbol to its final absolute address using the hash map.
145 -
pub fn lookupAddr(m: *DataSymMap, name: *[u8]) -> ?u32 {
145 +
export fn lookupAddr(m: *DataSymMap, name: *[u8]) -> ?u32 {
146 146
    if let v = dict::get(&m.dict, name) {
147 147
        return v as u32;
148 148
    }
149 149
    return nil;
150 150
}
lib/std/lang/gen/labels.rad +11 -11
3 3
//! Provides target-independent block/label tracking for branch resolution.
4 4
5 5
use std::collections::dict;
6 6
7 7
/// Maximum number of blocks per function.
8 -
pub const MAX_BLOCKS_PER_FN: u32 = 4096;
8 +
export const MAX_BLOCKS_PER_FN: u32 = 4096;
9 9
/// Maximum number of functions.
10 -
pub const MAX_FUNCS: u32 = 8192;
10 +
export const MAX_FUNCS: u32 = 8192;
11 11
/// Size of the function hash table. Must be a power of two.
12 -
pub const FUNC_TABLE_SIZE: u32 = MAX_FUNCS * 2;
12 +
export const FUNC_TABLE_SIZE: u32 = MAX_FUNCS * 2;
13 13
14 14
/// Label tracking for code emission.
15 -
pub record Labels {
15 +
export record Labels {
16 16
    /// Block offsets indexed by block index.
17 17
    /// Per-function, reset each function.
18 18
    blockOffsets: *mut [i32],
19 19
    /// Number of blocks recorded in current function.
20 20
    blockCount: u32,
21 21
    /// Function name to byte offset mapping.
22 22
    funcs: dict::Dict,
23 23
}
24 24
25 25
/// Create a new labels tracker with caller-provided storage.
26 -
pub fn init(blockOffsets: *mut [i32], funcEntries: *mut [dict::Entry]) -> Labels {
26 +
export fn init(blockOffsets: *mut [i32], funcEntries: *mut [dict::Entry]) -> Labels {
27 27
    return Labels {
28 28
        blockOffsets,
29 29
        blockCount: 0,
30 30
        funcs: dict::init(funcEntries),
31 31
    };
32 32
}
33 33
34 34
/// Reset block count for a new function.
35 -
pub fn resetBlocks(l: *mut Labels) {
35 +
export fn resetBlocks(l: *mut Labels) {
36 36
    l.blockCount = 0;
37 37
}
38 38
39 39
/// Record a block's code offset by its index. O(1).
40 -
pub fn recordBlock(l: *mut Labels, blockIdx: u32, offset: i32) {
40 +
export fn recordBlock(l: *mut Labels, blockIdx: u32, offset: i32) {
41 41
    assert blockIdx < l.blockOffsets.len, "recordBlock: block index out of range";
42 42
    l.blockOffsets[blockIdx] = offset;
43 43
    l.blockCount += 1;
44 44
}
45 45
46 46
/// Look up a block's byte offset by index. O(1).
47 -
pub fn blockOffset(l: *Labels, blockIdx: u32) -> i32 {
47 +
export fn blockOffset(l: *Labels, blockIdx: u32) -> i32 {
48 48
    assert blockIdx < l.blockCount, "blockOffset: block not recorded";
49 49
    return l.blockOffsets[blockIdx];
50 50
}
51 51
52 52
/// Look up a function's byte offset by name.
53 -
pub fn funcOffset(l: *Labels, name: *[u8]) -> i32 {
53 +
export fn funcOffset(l: *Labels, name: *[u8]) -> i32 {
54 54
    if let offset = dict::get(&l.funcs, name) {
55 55
        return offset;
56 56
    }
57 57
    panic "funcOffset: unknown function";
58 58
}
59 59
60 60
/// Compute branch offset to a block given source instruction index.
61 -
pub fn branchToBlock(l: *Labels, srcIndex: u32, blockIdx: u32, instrSize: i32) -> i32 {
61 +
export fn branchToBlock(l: *Labels, srcIndex: u32, blockIdx: u32, instrSize: i32) -> i32 {
62 62
    let targetOffset = blockOffset(l, blockIdx);
63 63
    let srcOffset = srcIndex as i32 * instrSize;
64 64
65 65
    return targetOffset - srcOffset;
66 66
}
67 67
68 68
/// Compute branch offset to a function given source instruction index.
69 -
pub fn branchToFunc(l: *Labels, srcIndex: u32, name: *[u8], instrSize: i32) -> i32 {
69 +
export fn branchToFunc(l: *Labels, srcIndex: u32, name: *[u8], instrSize: i32) -> i32 {
70 70
    let targetOffset = funcOffset(l, name);
71 71
    let srcOffset = srcIndex as i32 * instrSize;
72 72
73 73
    return targetOffset - srcOffset;
74 74
}
lib/std/lang/gen/regalloc.rad +6 -6
12 12
//!
13 13
//! Note that the IL is not modified. The allocator produces a mapping that
14 14
//! instruction selection uses to emit physical registers. Spilled values are
15 15
//! handled by [`isel`] inserting loads/stores.
16 16
17 -
pub mod liveness;
18 -
pub mod spill;
19 -
pub mod assign;
17 +
export mod liveness;
18 +
export mod spill;
19 +
export mod assign;
20 20
21 21
use std::lang::il;
22 22
use std::lang::alloc;
23 23
24 24
/// Target configuration for register allocation.
25 -
pub record TargetConfig {
25 +
export record TargetConfig {
26 26
    /// List of allocatable physical registers.
27 27
    /// Order determines allocation preference.
28 28
    allocatable: *[super::Reg],
29 29
    /// Function argument registers.
30 30
    argRegs: *[super::Reg],
33 33
    /// Size of a spill slot in bytes.
34 34
    slotSize: u32,
35 35
}
36 36
37 37
/// Complete register allocation result.
38 -
pub record AllocResult {
38 +
export record AllocResult {
39 39
    /// SSA register to physical register mapping.
40 40
    assignments: *[?super::Reg],
41 41
    /// Spill slot information.
42 42
    spill: spill::SpillInfo,
43 43
    /// Bitmask of used callee-saved registers.
46 46
47 47
/// Run register allocation on a function.
48 48
///
49 49
/// Returns a mapping from SSA registers to physical registers, plus
50 50
/// spill information.
51 -
pub fn allocate(
51 +
export fn allocate(
52 52
    func: *il::Fn,
53 53
    config: *TargetConfig,
54 54
    arena: *mut alloc::Arena
55 55
) -> AllocResult throws (alloc::AllocError) {
56 56
    // Phase 1: Liveness analysis.
lib/std/lang/gen/regalloc/assign.rad +3 -3
16 16
/// Maximum number of active register mappings.
17 17
const MAX_ACTIVE: u32 = 64;
18 18
19 19
/// Register mapping at a program point.
20 20
/// Maps SSA registers to physical registers.
21 -
pub record RegMap {
21 +
export record RegMap {
22 22
    /// SSA (virtual) registers that have mappings.
23 23
    virtRegs: *mut [u32],
24 24
    /// Physical register for each virtual register.
25 25
    physRegs: *mut [gen::Reg],
26 26
    /// Number of active mappings.
27 27
    n: u32,
28 28
}
29 29
30 30
/// Register assignment result, per function.
31 -
pub record AssignInfo {
31 +
export record AssignInfo {
32 32
    /// SSA register -> physical register mapping.
33 33
    assignments: *mut [?gen::Reg],
34 34
    /// Bitmask of used callee-saved registers.
35 35
    usedCalleeSaved: u32,
36 36
}
48 48
    assignments: *mut [?gen::Reg],
49 49
    spillInfo: *spill::SpillInfo,
50 50
}
51 51
52 52
/// Compute register assignment.
53 -
pub fn assign(
53 +
export fn assign(
54 54
    func: *il::Fn,
55 55
    live: *liveness::LiveInfo,
56 56
    spillInfo: *spill::SpillInfo,
57 57
    config: *super::TargetConfig,
58 58
    arena: *mut alloc::Arena
lib/std/lang/gen/regalloc/liveness.rad +4 -4
27 27
use std::lang::il;
28 28
use std::lang::alloc;
29 29
use std::lang::gen::bitset;
30 30
31 31
/// Maximum number of SSA registers supported.
32 -
pub const MAX_SSA_REGS: u32 = 8192;
32 +
export const MAX_SSA_REGS: u32 = 8192;
33 33
34 34
/// Liveness information for a function.
35 -
pub record LiveInfo {
35 +
export record LiveInfo {
36 36
    /// Per-block live-in sets (indexed by block index).
37 37
    liveIn: *mut [bitset::Bitset],
38 38
    /// Per-block live-out sets (indexed by block index).
39 39
    liveOut: *mut [bitset::Bitset],
40 40
    /// Per-block defs sets (registers defined in block).
58 58
    target: u32,
59 59
    found: bool,
60 60
}
61 61
62 62
/// Compute liveness information for a function.
63 -
pub fn analyze(func: *il::Fn, arena: *mut alloc::Arena) -> LiveInfo throws (alloc::AllocError) {
63 +
export fn analyze(func: *il::Fn, arena: *mut alloc::Arena) -> LiveInfo throws (alloc::AllocError) {
64 64
    let blockCount = func.blocks.len;
65 65
    if blockCount == 0 {
66 66
        return LiveInfo {
67 67
            liveIn: &mut [],
68 68
            liveOut: &mut [],
223 223
fn unionBlockLiveIn(target: u32, liveIn: *[bitset::Bitset], scratch: *mut bitset::Bitset) {
224 224
    bitset::union_(scratch, &liveIn[target]);
225 225
}
226 226
227 227
/// Check if this is the last use of a register at this instruction.
228 -
pub fn isLastUse(info: *LiveInfo, func: *il::Fn, blockIdx: u32, instrIdx: u32, reg: il::Reg) -> bool {
228 +
export fn isLastUse(info: *LiveInfo, func: *il::Fn, blockIdx: u32, instrIdx: u32, reg: il::Reg) -> bool {
229 229
    let block = &func.blocks[blockIdx];
230 230
    let instr = block.instrs[instrIdx];
231 231
232 232
    if not instrUsesReg(instr, reg) {
233 233
        return false;
lib/std/lang/gen/regalloc/spill.rad +4 -4
47 47
    /// Number of uses (weighted by loop depth).
48 48
    uses: u32,
49 49
}
50 50
51 51
/// Spill decision for a function.
52 -
pub record SpillInfo {
52 +
export record SpillInfo {
53 53
    /// SSA register mapped to stack slot offset. `-1` means not spilled.
54 54
    slots: *mut [i32],
55 55
    /// Total spill frame size needed in bytes.
56 56
    frameSize: i32,
57 57
    /// Values that must be allocated in callee-saved registers.
77 77
    costs: *mut [SpillCost],
78 78
    weight: u32,
79 79
}
80 80
81 81
/// Analyze a function and determine which values need spill slots.
82 -
pub fn analyze(
82 +
export fn analyze(
83 83
    func: *il::Fn,
84 84
    live: *liveness::LiveInfo,
85 85
    numRegs: u32,
86 86
    numCalleeSaved: u32,
87 87
    slotSize: u32,
319 319
fn addRegToSetCallback(reg: il::Reg, ctx: *mut opaque) {
320 320
    bitset::set(ctx as *mut bitset::Bitset, reg.n);
321 321
}
322 322
323 323
/// Check if a register is spilled.
324 -
pub fn isSpilled(info: *SpillInfo, reg: il::Reg) -> bool {
324 +
export fn isSpilled(info: *SpillInfo, reg: il::Reg) -> bool {
325 325
    if reg.n >= info.maxReg {
326 326
        return false;
327 327
    }
328 328
    return info.slots[reg.n] >= 0;
329 329
}
330 330
331 331
/// Get spill slot offset for a register, or `nil` if not spilled.
332 -
pub fn spillSlot(info: *SpillInfo, reg: il::Reg) -> ?i32 {
332 +
export fn spillSlot(info: *SpillInfo, reg: il::Reg) -> ?i32 {
333 333
    if isSpilled(info, reg) {
334 334
        return info.slots[reg.n];
335 335
    }
336 336
    return nil;
337 337
}
lib/std/lang/gen/types.rad +2 -2
1 1
//! Target-independent code generation types.
2 2
//!
3 3
//! Defines common records used by all code generation backends.
4 4
5 5
/// Function address entry for printing.
6 -
pub record FuncAddr {
6 +
export record FuncAddr {
7 7
    /// Function name.
8 8
    name: *[u8],
9 9
    /// Instruction index where this function starts.
10 10
    index: u32,
11 11
}
12 12
13 13
/// Debug entry mapping an instruction to a source location.
14 -
pub record DebugEntry {
14 +
export record DebugEntry {
15 15
    /// Byte offset of the instruction from the start of the program.
16 16
    pc: u32,
17 17
    /// Module identifier.
18 18
    moduleId: u16,
19 19
    /// Byte offset into the module's source file.
lib/std/lang/il.rad +23 -23
56 56
//! can declare parameters, and jumps/branches pass arguments to their targets.
57 57
58 58
// TODO: Labels should have their own type.
59 59
// TODO: Blocks should have an instruction in `Instr`.
60 60
61 -
pub mod printer;
61 +
export mod printer;
62 62
63 63
use std::mem;
64 64
use std::lang::alloc;
65 65
66 66
/// Source location for debug info.
67 67
///
68 68
/// Associates an IL instruction with its originating source module and
69 69
/// byte offset.
70 -
pub record SrcLoc {
70 +
export record SrcLoc {
71 71
    /// Module identifier.
72 72
    moduleId: u16,
73 73
    /// Byte offset into the module's source file.
74 74
    offset: u32,
75 75
}
77 77
///////////////////////
78 78
// Name Formatting   //
79 79
///////////////////////
80 80
81 81
/// Separator for qualified symbol names.
82 -
pub const PATH_SEPARATOR: *[u8] = "::";
82 +
export const PATH_SEPARATOR: *[u8] = "::";
83 83
84 84
/// Format a qualified symbol name: `pkg::mod::path::name`.
85 -
pub fn formatQualifiedName(arena: *mut alloc::Arena, path: *[*[u8]], name: *[u8]) -> *[u8] {
85 +
export fn formatQualifiedName(arena: *mut alloc::Arena, path: *[*[u8]], name: *[u8]) -> *[u8] {
86 86
    let mut totalLen: u32 = name.len;
87 87
    for segment in path {
88 88
        totalLen += segment.len + PATH_SEPARATOR.len;
89 89
    }
90 90
    let buf = try! alloc::allocSlice(arena, 1, 1, totalLen) as *mut [u8];
102 102
///////////
103 103
// Types //
104 104
///////////
105 105
106 106
/// IL type representation. Integer signedness is encoded in operations, not types.
107 -
pub union Type {
107 +
export union Type {
108 108
    /// 8-bit value.
109 109
    W8,
110 110
    /// 16-bit value.
111 111
    W16,
112 112
    /// 32-bit value.
114 114
    /// 64-bit value (used for pointers on RV64).
115 115
    W64,
116 116
}
117 117
118 118
/// Get the size of a type in bytes.
119 -
pub fn typeSize(t: Type) -> u32 {
119 +
export fn typeSize(t: Type) -> u32 {
120 120
    match t {
121 121
        case Type::W8 => return 1,
122 122
        case Type::W16 => return 2,
123 123
        case Type::W32 => return 4,
124 124
        case Type::W64 => return 8,
125 125
    }
126 126
}
127 127
128 128
/// SSA register reference.
129 -
pub record Reg { n: u32 }
129 +
export record Reg { n: u32 }
130 130
131 131
/// Instruction value.
132 -
pub union Val {
132 +
export union Val {
133 133
    /// Register reference.
134 134
    Reg(Reg),
135 135
    /// Immediate integer value.
136 136
    Imm(i64),
137 137
    /// Data symbol address (globals, constants, string literals).
143 143
    /// eg. in void returns.
144 144
    Undef,
145 145
}
146 146
147 147
/// Comparison operation for compare-and-branch.
148 -
pub union CmpOp { Eq, Ne, Slt, Ult }
148 +
export union CmpOp { Eq, Ne, Slt, Ult }
149 149
150 150
/// Binary ALU operation kind.
151 -
pub union BinOp {
151 +
export union BinOp {
152 152
    // Arithmetic.
153 153
    Add, Sub, Mul, Sdiv, Udiv, Srem, Urem,
154 154
    // Comparison.
155 155
    Eq, Ne, Slt, Sge, Ult, Uge,
156 156
    // Bitwise.
157 157
    And, Or, Xor, Shl, Sshr, Ushr,
158 158
}
159 159
160 160
/// Unary ALU operation kind.
161 -
pub union UnOp {
161 +
export union UnOp {
162 162
    Neg, Not,
163 163
}
164 164
165 165
//////////////////
166 166
// Instructions //
167 167
//////////////////
168 168
169 169
/// Block parameter.
170 -
pub record Param {
170 +
export record Param {
171 171
    /// SSA register.
172 172
    value: Reg,
173 173
    /// Parameter type.
174 174
    type: Type,
175 175
}
176 176
177 177
/// Switch case mapping a constant value to a branch target.
178 -
pub record SwitchCase {
178 +
export record SwitchCase {
179 179
    /// The constant value to match against.
180 180
    value: i64,
181 181
    /// The target block index.
182 182
    target: u32,
183 183
    /// Arguments to pass to the target block.
184 184
    args: *mut [Val],
185 185
}
186 186
187 187
/// IL instruction.
188 188
/// SSA registers are represented as `Reg`, values as `Val`.
189 -
pub union Instr {
189 +
export union Instr {
190 190
    ///////////////////////
191 191
    // Memory operations //
192 192
    ///////////////////////
193 193
194 194
    /// Allocate space on the stack: `reserve %dst <size> <alignment>;`
305 305
///
306 306
/// Basic blocks are instruction sequences with a single entry point and no branch
307 307
/// instruction, except possibly at the end of the sequence, where a terminator
308 308
/// may be found, ie. an instruction that terminates the sequence by jumping
309 309
/// to another sequence.
310 -
pub record Block {
310 +
export record Block {
311 311
    /// Block label.
312 312
    label: *[u8],
313 313
    /// Block parameters.
314 314
    params: *[Param],
315 315
    /// Instructions in the block. The last instruction must be a terminator.
323 323
    /// Used for spill cost weighting in register allocation.
324 324
    loopDepth: u32,
325 325
}
326 326
327 327
/// An IL function.
328 -
pub record Fn {
328 +
export record Fn {
329 329
    /// Qualified function name (e.g. `$mod$path$func`).
330 330
    name: *[u8],
331 331
    /// Function parameters.
332 332
    params: *[Param],
333 333
    /// Return type.
343 343
/////////////
344 344
// Program //
345 345
/////////////
346 346
347 347
/// Data initializer item.
348 -
pub union DataItem {
348 +
export union DataItem {
349 349
    /// Typed value: `w32 42;` or `w8 255;`
350 350
    Val { typ: Type, val: i64 },
351 351
    /// Symbol reference: `$symbol`
352 352
    Sym(*[u8]),
353 353
    /// Function reference: `$fnName`
357 357
    /// Undefined value. Used for padding and `void` returns.
358 358
    Undef,
359 359
}
360 360
361 361
/// Data initializer value with optional repeat count.
362 -
pub record DataValue {
362 +
export record DataValue {
363 363
    /// The item contained in the value.
364 364
    item: DataItem,
365 365
    /// The number of times the item should be repeated.
366 366
    count: u32,
367 367
}
368 368
369 369
/// Global data definition.
370 -
pub record Data {
370 +
export record Data {
371 371
    /// Data name.
372 372
    name: *[u8],
373 373
    /// Size in bytes.
374 374
    size: u32,
375 375
    /// Alignment requirement.
384 384
    /// Initializer values.
385 385
    values: *[DataValue],
386 386
}
387 387
388 388
/// An IL program (compilation unit).
389 -
pub record Program {
389 +
export record Program {
390 390
    /// Global data.
391 391
    data: *[Data],
392 392
    /// Functions.
393 393
    fns: *[*Fn],
394 394
    /// Index of entry point function, if any.
398 398
///////////////////////
399 399
// Utility Functions //
400 400
///////////////////////
401 401
402 402
/// Get the destination register of an instruction, if any.
403 -
pub fn instrDst(instr: Instr) -> ?Reg {
403 +
export fn instrDst(instr: Instr) -> ?Reg {
404 404
    match instr {
405 405
        case Instr::Reserve { dst, .. } => return dst,
406 406
        case Instr::Load { dst, .. } => return dst,
407 407
        case Instr::Sload { dst, .. } => return dst,
408 408
        case Instr::Copy { dst, .. } => return dst,
415 415
        else => return nil,
416 416
    }
417 417
}
418 418
419 419
/// Check if an instruction is a function call.
420 -
pub fn isCall(instr: Instr) -> bool {
420 +
export fn isCall(instr: Instr) -> bool {
421 421
    match instr {
422 422
        case Instr::Call { .. },
423 423
             Instr::Ecall { .. } => return true,
424 424
        else => return false,
425 425
    }
426 426
}
427 427
428 428
/// Call a function for each register used by an instruction.
429 429
/// This is called by the register allocator to analyze register usage.
430 -
pub fn forEachReg(instr: Instr, f: fn(Reg, *mut opaque), ctx: *mut opaque) {
430 +
export fn forEachReg(instr: Instr, f: fn(Reg, *mut opaque), ctx: *mut opaque) {
431 431
    match instr {
432 432
        case Instr::Reserve { size, .. } =>
433 433
            withReg(size, f, ctx),
434 434
        case Instr::Load { src, .. } => f(src, ctx),
435 435
        case Instr::Sload { src, .. } => f(src, ctx),
lib/std/lang/il/printer.rad +2 -2
523 523
//////////////////////
524 524
// Program printing //
525 525
//////////////////////
526 526
527 527
/// Print a program.
528 -
pub fn printProgram(out: *mut sexpr::Output, a: *mut alloc::Arena, program: *super::Program) {
528 +
export fn printProgram(out: *mut sexpr::Output, a: *mut alloc::Arena, program: *super::Program) {
529 529
    // Data declarations.
530 530
    for data, i in program.data {
531 531
        writeData(out, a, data);
532 532
        if i < program.data.len - 1 or program.fns.len > 0 {
533 533
            write(out, "\n");
541 541
        }
542 542
    }
543 543
}
544 544
545 545
/// Print a program to a buffer, returning the written slice.
546 -
pub fn printProgramToBuffer(
546 +
export fn printProgramToBuffer(
547 547
    program: *super::Program,
548 548
    arena: *mut alloc::Arena,
549 549
    buf: *mut [u8]
550 550
) -> *[u8] {
551 551
    let mut pos: u32 = 0;
lib/std/lang/lower.rad +12 -12
106 106
// Error Handling //
107 107
////////////////////
108 108
109 109
/// Lowering errors are typically unrecoverable since they indicate bugs in
110 110
/// the resolver or malformed AST that should have been caught earlier.
111 -
pub union LowerError {
111 +
export union LowerError {
112 112
    /// A node's symbol was not set before lowering.
113 113
    MissingSymbol(*ast::Node),
114 114
    /// A node's type was not set before lowering.
115 115
    MissingType(*ast::Node),
116 116
    /// A node's constant value was not set before lowering.
163 163
    /// Allocation failure.
164 164
    AllocationFailed,
165 165
}
166 166
167 167
/// Print a LowerError for debugging.
168 -
pub fn printError(err: LowerError) {
168 +
export fn printError(err: LowerError) {
169 169
    match err {
170 170
        case LowerError::MissingSymbol(_) => io::print("MissingSymbol"),
171 171
        case LowerError::MissingType(_) => io::print("MissingType"),
172 172
        case LowerError::MissingConst(_) => io::print("MissingConst"),
173 173
        case LowerError::ExpectedOptional => io::print("ExpectedOptional"),
246 246
//////////////////////////
247 247
// Core Data Structures //
248 248
//////////////////////////
249 249
250 250
/// Options controlling the lowering pass.
251 -
pub record LowerOptions {
251 +
export record LowerOptions {
252 252
    /// Whether to emit source location info.
253 253
    debug: bool,
254 254
    /// Whether to lower `@test` functions.
255 255
    buildTest: bool,
256 256
}
257 257
258 258
/// Module-level lowering context. Shared across all function lowerings.
259 259
/// Holds global state like the data section (strings, constants) and provides
260 260
/// access to the resolver for type queries.
261 -
pub record Lowerer {
261 +
export record Lowerer {
262 262
    /// Arena for IL allocations. All IL nodes are allocated here.
263 263
    arena: *mut alloc::Arena,
264 264
    /// Allocator backed by the arena.
265 265
    allocator: alloc::Allocator,
266 266
    /// Resolver for type information. Used to query types, symbols, and
374 374
// a mapping from [`Var`] to current SSA value. When control flow merges,
375 375
// block parameters are inserted to merge values from different control flow paths.
376 376
377 377
/// A variable handle. Represents a source-level variable during lowering.
378 378
/// The same [`Var`] can have different SSA values in different blocks.
379 -
pub record Var(u32);
379 +
export record Var(u32);
380 380
381 381
/// Metadata for a source-level variable, stored once per function.
382 382
///
383 383
/// Each variable declaration in the source creates one [`VarData`] entry in the
384 384
/// function's `variables` array, indexed by `id`. This contains static
421 421
// instructions until terminated by a jump, branch, return, or unreachable.
422 422
// Blocks can be created before they're filled (forward references for jumps).
423 423
424 424
/// A handle to a basic block within the current function.
425 425
/// Block handles are stable, they don't change as more blocks are added.
426 -
pub record BlockId(u32);
426 +
export record BlockId(u32);
427 427
428 428
/// Internal block state during construction.
429 429
///
430 430
/// The key invariants:
431 431
///
480 480
// Loop and Control Flow Context //
481 481
///////////////////////////////////
482 482
483 483
/// Context for break/continue statements within a loop.
484 484
/// Each nested loop pushes a new context onto the loop stack.
485 -
pub record LoopCtx {
485 +
export record LoopCtx {
486 486
    /// Where `break` should transfer control (the loop's exit block).
487 487
    breakTarget: BlockId,
488 488
    /// Where `continue` should transfer control.
489 489
    continueTarget: ?BlockId,
490 490
}
672 672
/// 2. Iterates over top-level declarations, lowering each.
673 673
/// 3. Returns the complete IL program with functions and data section.
674 674
///
675 675
/// The resolver must have already processed the AST -- we rely on its type
676 676
/// annotations, symbol table, and constant evaluations.
677 -
pub fn lower(
677 +
export fn lower(
678 678
    res: *resolver::Resolver,
679 679
    root: *ast::Node,
680 680
    pkgName: *[u8],
681 681
    arena: *mut alloc::Arena
682 682
) -> il::Program throws (LowerError) {
706 706
/////////////////////////////////
707 707
// Multi-Module Lowering API   //
708 708
/////////////////////////////////
709 709
710 710
/// Create a lowerer for multi-module compilation.
711 -
pub fn lowerer(
711 +
export fn lowerer(
712 712
    res: *resolver::Resolver,
713 713
    graph: *module::ModuleGraph,
714 714
    pkgName: *[u8],
715 715
    arena: *mut alloc::Arena,
716 716
    options: LowerOptions
731 731
    };
732 732
}
733 733
734 734
/// Lower a module's AST into the lowerer accumulator.
735 735
/// Call this for each module in the package, then use `finalize` to get the program.
736 -
pub fn lowerModule(
736 +
export fn lowerModule(
737 737
    low: *mut Lowerer,
738 738
    moduleId: u16,
739 739
    root: *ast::Node,
740 740
    isRoot: bool
741 741
) -> ?u32 throws (LowerError) {
780 780
    }
781 781
    return defaultFnIdx;
782 782
}
783 783
784 784
/// Finalize lowering and return the unified IL program.
785 -
pub fn finalize(low: *Lowerer, defaultFnIdx: ?u32) -> il::Program {
785 +
export fn finalize(low: *Lowerer, defaultFnIdx: ?u32) -> il::Program {
786 786
    return il::Program {
787 787
        data: &low.data[..],
788 788
        fns: &low.fns[..],
789 789
        defaultFnIdx,
790 790
    };
844 844
    return nil;
845 845
}
846 846
847 847
/// Set the package context for lowering.
848 848
/// Called before lowering each package.
849 -
pub fn setPackage(self: *mut Lowerer, graph: *module::ModuleGraph, pkgName: *[u8]) {
849 +
export fn setPackage(self: *mut Lowerer, graph: *module::ModuleGraph, pkgName: *[u8]) {
850 850
    self.moduleGraph = graph;
851 851
    self.pkgName = pkgName;
852 852
    self.currentMod = nil;
853 853
}
854 854
lib/std/lang/module.rad +21 -21
2 2
//!
3 3
//! This module tracks source files, parent/child relationships, and basic state for each
4 4
//! module so that later phases (parser, semantic analyzer) can resolve imports (`use`)
5 5
//! and avoid reloading the same file twice.
6 6
7 -
pub mod printer;
7 +
export mod printer;
8 8
9 9
@test mod tests;
10 10
11 11
use std::mem;
12 12
use std::lang::alloc;
13 13
use std::lang::ast;
14 14
use std::lang::strings;
15 15
16 16
/// Maximum number of modules tracked in a single compilation graph.
17 -
pub const MAX_MODULES: u32 = 128;
17 +
export const MAX_MODULES: u32 = 128;
18 18
/// Maximum number of characters for a module path.
19 19
const MAX_PATH_LEN: u32 = 256;
20 20
/// Maximum number of components that make up a logical module path.
21 21
const MAX_MODULE_PATH_DEPTH: u32 = 16;
22 22
/// Filesystem separator used when constructing child paths.
23 23
const PATH_SEP: u8 = '/';
24 24
/// Source file extension handled by the loader.
25 25
const SOURCE_EXT: *[u8] = ".rad";
26 26
27 27
/// Lifecycle state for modules in the dependency graph.
28 -
pub union ModuleState {
28 +
export union ModuleState {
29 29
    /// Slot unused or yet to be initialized.
30 30
    Vacant,
31 31
    /// Module registered with a known path but not parsed yet.
32 32
    Registered,
33 33
    /// Module is currently being parsed (used for cycle detection).
39 39
    /// Module finished semantic analysis and is ready for codegen.
40 40
    Ready,
41 41
}
42 42
43 43
/// Error categories that can be produced by the module graph.
44 -
pub union ModuleError {
44 +
export union ModuleError {
45 45
    /// Module not found.
46 46
    NotFound(u16),
47 47
    /// Adding the module would exceed the fixed storage.
48 48
    CapacityExceeded,
49 49
    /// Provided path is missing the required `.rad` extension or otherwise invalid.
53 53
    /// Attempt to register a module before its parent.
54 54
    MissingParent,
55 55
}
56 56
57 57
/// Module metadata recorded inside the dependency graph.
58 -
pub record ModuleEntry {
58 +
export record ModuleEntry {
59 59
    /// Numeric identifier for the slot (index in the graph array).
60 60
    id: u16,
61 61
    /// Package identifier this module belongs to.
62 62
    packageId: u16,
63 63
    /// Parent module identifier, or `nil` for root modules.
83 83
    /// Source text for this module (for error reporting).
84 84
    source: ?*[u8],
85 85
}
86 86
87 87
/// Dense storage for all modules referenced by the compilation unit.
88 -
pub record ModuleGraph {
88 +
export record ModuleGraph {
89 89
    entries: *mut [ModuleEntry],
90 90
    entriesLen: u32,
91 91
    pool: *mut strings::Pool,
92 92
    /// Arena used for all AST node allocations.
93 93
    arena: ?*ast::NodeArena,
94 94
}
95 95
96 96
/// Initialize an empty module graph backed by the provided storage.
97 -
pub fn moduleGraph(
97 +
export fn moduleGraph(
98 98
    storage: *mut [ModuleEntry],
99 99
    pool: *mut strings::Pool,
100 100
    arena: *mut ast::NodeArena
101 101
) -> ModuleGraph {
102 102
    return ModuleGraph {
106 106
        arena,
107 107
    };
108 108
}
109 109
110 110
/// Register a root module residing at `path` for a package.
111 -
pub fn registerRoot(graph: *mut ModuleGraph, packageId: u16, filePath: *[u8]) -> u16 throws (ModuleError) {
111 +
export fn registerRoot(graph: *mut ModuleGraph, packageId: u16, filePath: *[u8]) -> u16 throws (ModuleError) {
112 112
    let name = try basenameSlice(filePath);
113 113
    return try registerRootWithName(graph, packageId, name, filePath);
114 114
}
115 115
116 116
/// Register a root module with an explicit name and file path for a package.
117 -
pub fn registerRootWithName(
117 +
export fn registerRootWithName(
118 118
    graph: *mut ModuleGraph,
119 119
    packageId: u16,
120 120
    name: *[u8],
121 121
    filePath: *[u8]
122 122
) -> u16 throws (ModuleError) {
128 128
    return m.id;
129 129
}
130 130
131 131
/// Register a child module.
132 132
/// Returns the module identifier.
133 -
pub fn registerChild(
133 +
export fn registerChild(
134 134
    graph: *mut ModuleGraph,
135 135
    parentId: u16,
136 136
    name: *[u8],
137 137
    filePath: *[u8]
138 138
) -> u16 throws (ModuleError) {
161 161
162 162
    return try addChild(parent, m.id);
163 163
}
164 164
165 165
/// Fetch a read-only view of the module identified by `id`.
166 -
pub fn get(graph: *ModuleGraph, id: u16) -> ?*ModuleEntry {
166 +
export fn get(graph: *ModuleGraph, id: u16) -> ?*ModuleEntry {
167 167
    if not isValidId(graph, id) {
168 168
        return nil;
169 169
    }
170 170
    return &graph.entries[id as u32];
171 171
}
177 177
    }
178 178
    return &mut graph.entries[id as u32];
179 179
}
180 180
181 181
/// Return the identifier of the child stored at `index`.
182 -
pub fn childAt(m: *ModuleEntry, index: u32) -> u16 {
182 +
export fn childAt(m: *ModuleEntry, index: u32) -> u16 {
183 183
    assert index < m.childrenLen, "childAt: index must be valid";
184 184
    return m.children[index];
185 185
}
186 186
187 187
/// Public accessor for a module's directory prefix.
188 -
pub fn moduleDir(m: *ModuleEntry) -> *[u8] {
188 +
export fn moduleDir(m: *ModuleEntry) -> *[u8] {
189 189
    return &m.filePath[..m.dirLen];
190 190
}
191 191
192 192
/// Public accessor to the logical module path segments.
193 -
pub fn moduleQualifiedPath(m: *ModuleEntry) -> *[*[u8]] {
193 +
export fn moduleQualifiedPath(m: *ModuleEntry) -> *[*[u8]] {
194 194
    assert m.pathDepth > 0, "moduleQualifiedPath: path must not be empty";
195 195
    return &m.path[..m.pathDepth];
196 196
}
197 197
198 198
/// Retrieve the lifecycle state for `id`.
199 -
pub fn state(graph: *ModuleGraph, id: u16) -> ModuleState throws (ModuleError) {
199 +
export fn state(graph: *ModuleGraph, id: u16) -> ModuleState throws (ModuleError) {
200 200
    let m = get(graph, id) else {
201 201
        throw ModuleError::NotFound(id);
202 202
    };
203 203
    return m.state;
204 204
}
205 205
206 206
/// Record the parsed AST root for `id`.
207 -
pub fn setAst(graph: *mut ModuleGraph, id: u16, root: *mut ast::Node) throws (ModuleError) {
207 +
export fn setAst(graph: *mut ModuleGraph, id: u16, root: *mut ast::Node) throws (ModuleError) {
208 208
    let m = getMut(graph, id) else throw ModuleError::NotFound(id);
209 209
    m.ast = root;
210 210
    m.state = ModuleState::Parsed;
211 211
}
212 212
213 213
/// Set the source text for a module.
214 -
pub fn setSource(graph: *mut ModuleGraph, id: u16, source: *[u8]) throws (ModuleError) {
214 +
export fn setSource(graph: *mut ModuleGraph, id: u16, source: *[u8]) throws (ModuleError) {
215 215
    let m = getMut(graph, id) else throw ModuleError::NotFound(id);
216 216
    m.source = source;
217 217
}
218 218
219 219
/// Look up a child module by name under the given parent.
220 -
pub fn findChild(graph: *ModuleGraph, name: *[u8], parentId: u16) -> ?*ModuleEntry {
220 +
export fn findChild(graph: *ModuleGraph, name: *[u8], parentId: u16) -> ?*ModuleEntry {
221 221
    assert isValidId(graph, parentId), "findChild: parent identifier is valid";
222 222
223 223
    let parent = &graph.entries[parentId as u32];
224 224
    for i in 0..parent.childrenLen {
225 225
        let childId = parent.children[i];
232 232
}
233 233
234 234
/// Parse a file path into components by splitting on '/'.
235 235
/// Expects and removes the '.rad' extension from the last component.
236 236
/// Returns the number of components extracted, or `nil` if the path is invalid.
237 -
pub fn parsePath(filePath: *[u8], components: *mut [*[u8]]) -> ?u32 {
237 +
export fn parsePath(filePath: *[u8], components: *mut [*[u8]]) -> ?u32 {
238 238
    let mut count: u32 = 0;
239 239
    let mut last: u32 = 0;
240 240
241 241
    // Split on '/' to extract all but the last component.
242 242
    for i in 0..filePath.len {
267 267
268 268
/// Register a module from a file path, creating the full hierarchy as needed.
269 269
/// The path is split into components and the module hierarchy is built accordingly.
270 270
/// If `rootId` is `nil`, registers a new root for the given package.
271 271
/// Returns the module ID of the last component.
272 -
pub fn registerFromPath(graph: *mut ModuleGraph, packageId: u16, rootId: ?u16, filePath: *[u8]) -> u16 throws (ModuleError) {
272 +
export fn registerFromPath(graph: *mut ModuleGraph, packageId: u16, rootId: ?u16, filePath: *[u8]) -> u16 throws (ModuleError) {
273 273
    let root = rootId else {
274 274
        return try registerRoot(graph, packageId, filePath);
275 275
    };
276 276
    let rootEntry = get(graph, root) else {
277 277
        panic "registerFromPath: root is missing from storage";
405 405
    return start;
406 406
}
407 407
408 408
/// Trim the `.rad` extension from `path` if present.
409 409
/// Returns the path slice without the extension.
410 -
pub fn trimExtension(path: *[u8]) -> ?*[u8] {
410 +
export fn trimExtension(path: *[u8]) -> ?*[u8] {
411 411
    if path.len < SOURCE_EXT.len {
412 412
        return nil;
413 413
    }
414 414
    let extStart = path.len - SOURCE_EXT.len;
415 415
    for ext, i in SOURCE_EXT {
lib/std/lang/module/printer.rad +1 -1
62 62
        sexpr::Expr::List { head: "::", tail: pathBuf, multiline: false }
63 63
    ], childBuf);
64 64
}
65 65
66 66
/// Print the entire module graph in S-expression format.
67 -
pub fn printGraph(graph: *super::ModuleGraph, arena: *mut alloc::Arena) {
67 +
export fn printGraph(graph: *super::ModuleGraph, arena: *mut alloc::Arena) {
68 68
    // Print all root modules.
69 69
    for i in 0..graph.entriesLen {
70 70
        let entry = &graph.entries[i];
71 71
        if entry.parent == nil {
72 72
            sexpr::print(subtreeToExpr(arena, graph, entry), 0);
lib/std/lang/package.rad +4 -4
4 4
use std::lang::ast;
5 5
use std::lang::module;
6 6
use std::lang::strings;
7 7
8 8
/// Maximum number of packages processed in a single invocation.
9 -
pub const MAX_PACKAGES: u32 = 4;
9 +
export const MAX_PACKAGES: u32 = 4;
10 10
11 11
/// A compilation unit.
12 -
pub record Package {
12 +
export record Package {
13 13
    /// Package identifier (index in the package array).
14 14
    id: u16,
15 15
    /// Package name.
16 16
    name: *[u8],
17 17
    /// Root module identifier for this package, or `nil` if not yet registered.
18 18
    // TODO: This shouldn't be optional.
19 19
    rootModuleId: ?u16,
20 20
}
21 21
22 22
/// Initialize `pkg` with the provided name and ID.
23 -
pub fn init(
23 +
export fn init(
24 24
    pkg: *mut Package,
25 25
    id: u16,
26 26
    name: *[u8],
27 27
    pool: *mut strings::Pool
28 28
) -> *mut Package {
32 32
33 33
    return pkg;
34 34
}
35 35
36 36
/// Register a module described by the file path.
37 -
pub fn registerModule(pkg: *mut Package, graph: *mut module::ModuleGraph, filePath: *[u8]) -> u16
37 +
export fn registerModule(pkg: *mut Package, graph: *mut module::ModuleGraph, filePath: *[u8]) -> u16
38 38
    throws (module::ModuleError)
39 39
{
40 40
    let modId = try module::registerFromPath(graph, pkg.id, pkg.rootModuleId, filePath);
41 41
    // First registered module becomes the root.
42 42
    if pkg.rootModuleId == nil {
lib/std/lang/parser.rad +26 -26
1 1
//! Recursive descent parser for the Radiance programming language.
2 -
@test pub mod tests;
2 +
@test export mod tests;
3 3
4 4
use std::mem;
5 5
use std::io;
6 6
use std::lang::alloc;
7 7
use std::lang::ast;
8 8
use std::lang::strings;
9 9
use std::lang::scanner;
10 10
11 11
/// Maximum `u32` value.
12 -
pub const U32_MAX: u32 = 0xFFFFFFFF;
12 +
export const U32_MAX: u32 = 0xFFFFFFFF;
13 13
/// Maximum representable `u64` value.
14 -
pub const U64_MAX: u64 = 0xFFFFFFFFFFFFFFFF;
14 +
export const U64_MAX: u64 = 0xFFFFFFFFFFFFFFFF;
15 15
/// Maximum number of fields in a record.
16 -
pub const MAX_RECORD_FIELDS: u32 = 32;
16 +
export const MAX_RECORD_FIELDS: u32 = 32;
17 17
18 18
/// Maximum number of parser errors before aborting.
19 19
const MAX_ERRORS: u32 = 8;
20 20
21 21
/// Parser error type.
22 -
pub union ParseError {
22 +
export union ParseError {
23 23
    /// Encountered a token that was not expected in the current context.
24 24
    UnexpectedToken,
25 25
}
26 26
27 27
/// Represents a parsed name-type-value triple.
60 60
    /// and `if` is reserved for guards.
61 61
    Condition,
62 62
}
63 63
64 64
/// A single parser error with location information.
65 -
pub record Error {
65 +
export record Error {
66 66
    /// Human-readable error message.
67 67
    message: *[u8],
68 68
    /// The token where the error occurred.
69 69
    token: scanner::Token,
70 70
}
134 134
            return nil,
135 135
    }
136 136
}
137 137
138 138
/// Parser state.
139 -
pub record Parser {
139 +
export record Parser {
140 140
    /// The scanner that provides tokens.
141 141
    scanner: scanner::Scanner,
142 142
    /// The current token being examined.
143 143
    current: scanner::Token,
144 144
    /// The most recently consumed token.
152 152
    /// Current parsing context (normal or conditional).
153 153
    context: Context,
154 154
}
155 155
156 156
/// Create a new parser initialized with the given source kind, source and node arena.
157 -
pub fn mkParser(sourceLoc: scanner::SourceLoc, source: *[u8], arena: *mut ast::NodeArena, pool: *mut strings::Pool) -> Parser {
157 +
export fn mkParser(sourceLoc: scanner::SourceLoc, source: *[u8], arena: *mut ast::NodeArena, pool: *mut strings::Pool) -> Parser {
158 158
    return Parser {
159 159
        scanner: scanner::scanner(sourceLoc, source, pool),
160 160
        current: scanner::invalid(0, ""),
161 161
        previous: scanner::invalid(0, ""),
162 162
        errors: ErrorList { list: undefined, count: 0 },
170 170
fn nodeBool(p: *mut Parser, value: bool) -> *ast::Node {
171 171
    return node(p, ast::NodeValue::Bool(value));
172 172
}
173 173
174 174
/// Convert a single ASCII digit into its numeric value for the given radix.
175 -
pub fn digitFromAscii(ch: u8, radix: u32) -> ?u32 {
175 +
export fn digitFromAscii(ch: u8, radix: u32) -> ?u32 {
176 176
    assert radix >= 2 and radix <= 36;
177 177
178 178
    // Default to an out-of-range value so non-digits fall through to `nil`.
179 179
    let mut value: u32 = 36;
180 180
489 489
    }
490 490
    return result;
491 491
}
492 492
493 493
/// Parse a conditional expression.
494 -
pub fn parseCond(p: *mut Parser) -> *ast::Node throws (ParseError) {
494 +
export fn parseCond(p: *mut Parser) -> *ast::Node throws (ParseError) {
495 495
    let saved = p.context;
496 496
    p.context = Context::Condition;
497 497
    let expr = try parseExpr(p);
498 498
    p.context = saved;
499 499
806 806
807 807
/// Parse a single expression.
808 808
///
809 809
/// Parses unary and binary operators using precedence climbing.
810 810
/// Conditional expressions (`x if cond else y`) have lowest precedence.
811 -
pub fn parseExpr(p: *mut Parser) -> *ast::Node
811 +
export fn parseExpr(p: *mut Parser) -> *ast::Node
812 812
    throws (ParseError)
813 813
{
814 814
    let left = try parseUnary(p);
815 815
    let expr = try parseBinary(p, left, -1);
816 816
    return try parseCondExpr(p, expr);
832 832
        else => return nil,
833 833
    }
834 834
}
835 835
836 836
/// Parse an expression statement.
837 -
pub fn parseExprStmt(p: *mut Parser) -> *ast::Node
837 +
export fn parseExprStmt(p: *mut Parser) -> *ast::Node
838 838
    throws (ParseError)
839 839
{
840 840
    let expr = try parseExpr(p);
841 841
842 842
    if consume(p, scanner::TokenKind::Equal) {
874 874
    let mut attrs = ast::nodeSlice(p.arena, 4);
875 875
876 876
    if let attr = tryParseAnnotation(p) {
877 877
        attrs.append(attr, p.allocator);
878 878
    }
879 -
    if consume(p, scanner::TokenKind::Pub) or consume(p, scanner::TokenKind::Export) {
880 -
        let attrNode = nodeAttribute(p, ast::Attribute::Pub);
879 +
    if consume(p, scanner::TokenKind::Export) {
880 +
        let attrNode = nodeAttribute(p, ast::Attribute::Export);
881 881
        attrs.append(attrNode, p.allocator);
882 882
    }
883 883
    if attrs.len > 0 {
884 884
        return ast::Attributes { list: attrs };
885 885
    }
912 912
}
913 913
914 914
/// Parse a single statement.
915 915
///
916 916
/// Dispatches to the appropriate statement parser based on the current token.
917 -
pub fn parseStmt(p: *mut Parser) -> *ast::Node
917 +
export fn parseStmt(p: *mut Parser) -> *ast::Node
918 918
    throws (ParseError)
919 919
{
920 920
    // TODO: Why is `parseStmt` checking for attributes?
921 921
    // We should have a `parseDecl` which is top-level, and `parseStmt` which
922 922
    // is inside functions.
1020 1020
}
1021 1021
1022 1022
/// Parse statements until the specified ending token is encountered.
1023 1023
///
1024 1024
/// Adds each parsed statement to the given block's statement list.
1025 -
pub fn parseStmtsUntil(p: *mut Parser, end: scanner::TokenKind, blk: *mut ast::Block)
1025 +
export fn parseStmtsUntil(p: *mut Parser, end: scanner::TokenKind, blk: *mut ast::Block)
1026 1026
    throws (ParseError)
1027 1027
{
1028 1028
    while not check(p, end) {
1029 1029
        let stmt = try parseStmt(p);
1030 1030
        blk.statements.append(stmt, p.allocator);
1040 1040
        }
1041 1041
    }
1042 1042
}
1043 1043
1044 1044
/// Parse a block of statements enclosed in curly braces.
1045 -
pub fn parseBlock(p: *mut Parser) -> *ast::Node
1045 +
export fn parseBlock(p: *mut Parser) -> *ast::Node
1046 1046
    throws (ParseError)
1047 1047
{
1048 1048
    let start = p.current;
1049 1049
    let mut blk = mkBlock(p, 64);
1050 1050
1143 1143
    reportError(p, p.current, err);
1144 1144
    return ParseError::UnexpectedToken;
1145 1145
}
1146 1146
1147 1147
/// Print all errors that have been collected during parsing.
1148 -
pub fn printErrors(p: *Parser) {
1148 +
export fn printErrors(p: *Parser) {
1149 1149
    for i in 0..p.errors.count {
1150 1150
        let e = p.errors.list[i];
1151 1151
        if let loc = scanner::getLocation(
1152 1152
            p.scanner.sourceLoc, p.scanner.source, e.token.offset
1153 1153
        ) {
1174 1174
        io::print("\n");
1175 1175
    }
1176 1176
}
1177 1177
1178 1178
/// Check whether the current token matches the expected kind.
1179 -
pub fn check(p: *Parser, kind: scanner::TokenKind) -> bool {
1179 +
export fn check(p: *Parser, kind: scanner::TokenKind) -> bool {
1180 1180
    return p.current.kind == kind;
1181 1181
}
1182 1182
1183 1183
/// Advance the parser by one token.
1184 -
pub fn advance(p: *mut Parser) {
1184 +
export fn advance(p: *mut Parser) {
1185 1185
    p.previous = p.current;
1186 1186
    p.current = scanner::next(&mut p.scanner);
1187 1187
}
1188 1188
1189 1189
/// Parse an `if let` pattern matching statement.
2016 2016
    }
2017 2017
    return path;
2018 2018
}
2019 2019
2020 2020
/// Parse a type annotation.
2021 -
pub fn parseType(p: *mut Parser) -> *ast::Node
2021 +
export fn parseType(p: *mut Parser) -> *ast::Node
2022 2022
    throws (ParseError)
2023 2023
{
2024 2024
    match p.current.kind {
2025 2025
        case scanner::TokenKind::Question => {
2026 2026
            advance(p);
2250 2250
        ident: binding.name, type: binding.type, value, alignment: binding.alignment, mutable,
2251 2251
    }));
2252 2252
}
2253 2253
2254 2254
/// Parse a module from source text using the provided arena for node storage.
2255 -
pub fn parse(sourceLoc: scanner::SourceLoc, input: *[u8], arena: *mut ast::NodeArena, pool: *mut strings::Pool) -> *mut ast::Node
2255 +
export fn parse(sourceLoc: scanner::SourceLoc, input: *[u8], arena: *mut ast::NodeArena, pool: *mut strings::Pool) -> *mut ast::Node
2256 2256
    throws (ParseError)
2257 2257
{
2258 2258
    let mut p = mkParser(sourceLoc, input, arena, pool);
2259 2259
    return try parseModule(&mut p) catch {
2260 2260
        printErrors(&p);
2264 2264
2265 2265
/// Parse a complete module into a block of top-level statements.
2266 2266
///
2267 2267
/// This is the main entry point for parsing an entire Radiance source file.
2268 2268
/// The parser must already be initialized with source code.
2269 -
pub fn parseModule(p: *mut Parser) -> *mut ast::Node
2269 +
export fn parseModule(p: *mut Parser) -> *mut ast::Node
2270 2270
    throws (ParseError)
2271 2271
{
2272 2272
    advance(p); // Set the parser up with a first token.
2273 2273
2274 2274
    let mut blk = mkBlock(p, 512);
2277 2277
2278 2278
    return node(p, ast::NodeValue::Block(blk));
2279 2279
}
2280 2280
2281 2281
/// Consume a token of the given kind if present.
2282 -
pub fn consume(p: *mut Parser, kind: scanner::TokenKind) -> bool {
2282 +
export fn consume(p: *mut Parser, kind: scanner::TokenKind) -> bool {
2283 2283
    if check(p, kind) {
2284 2284
        advance(p);
2285 2285
        return true;
2286 2286
    }
2287 2287
    return false;
2288 2288
}
2289 2289
2290 2290
/// Expect a token of the given kind or report an error.
2291 -
pub fn expect(p: *mut Parser, kind: scanner::TokenKind, message: *[u8]) -> *[u8]
2291 +
export fn expect(p: *mut Parser, kind: scanner::TokenKind, message: *[u8]) -> *[u8]
2292 2292
    throws (ParseError)
2293 2293
{
2294 2294
    if not consume(p, kind) {
2295 2295
        reportError(p, p.current, message);
2296 2296
        throw ParseError::UnexpectedToken;
2353 2353
}
2354 2354
2355 2355
/// Parse an instance block.
2356 2356
/// Syntax: `instance Trait for Type { fn (t: *mut Type) fnord(..) {..} }`
2357 2357
///
2358 -
/// Instance declarations do not accept attributes (e.g. `pub`).
2358 +
/// Instance declarations do not accept attributes (e.g. `export`).
2359 2359
/// Visibility is determined by the trait declaration itself.
2360 2360
fn parseInstanceDecl(p: *mut Parser) -> *ast::Node
2361 2361
    throws (ParseError)
2362 2362
{
2363 2363
    try expect(p, scanner::TokenKind::Instance, "expected `instance`");
lib/std/lang/parser/tests.rad +11 -11
89 89
90 90
    return root;
91 91
}
92 92
93 93
/// Parse a single expression from a string.
94 -
pub fn parseExprStr(input: *[u8]) -> *ast::Node
94 +
export fn parseExprStr(input: *[u8]) -> *ast::Node
95 95
    throws (super::ParseError)
96 96
{
97 97
    let mut arena = ast::nodeArena(&mut ARENA_STORAGE[..]);
98 98
    let mut parser = super::mkParser(scanner::SourceLoc::String, input, &mut arena, &mut STRING_POOL);
99 99
    super::advance(&mut parser);
229 229
    }
230 230
    panic "expectBlockExprStmt: comparing values we don't know how to compare";
231 231
}
232 232
233 233
/// Get the first statement from a block node.
234 -
pub fn getBlockFirstStmt(blk: *ast::Node) -> *ast::Node
234 +
export fn getBlockFirstStmt(blk: *ast::Node) -> *ast::Node
235 235
    throws (testing::TestError)
236 236
{
237 237
    let case ast::NodeValue::Block(block) = blk.value
238 238
        else throw testing::TestError::Failed;
239 239
242 242
    }
243 243
    return block.statements[0];
244 244
}
245 245
246 246
/// Get the last statement from a block node.
247 -
pub fn getBlockLastStmt(blk: *ast::Node) -> *ast::Node
247 +
export fn getBlockLastStmt(blk: *ast::Node) -> *ast::Node
248 248
    throws (testing::TestError)
249 249
{
250 250
    let case ast::NodeValue::Block(block) = blk.value
251 251
        else throw testing::TestError::Failed;
252 252
797 797
    try testing::expect(decl.attrs == nil);
798 798
}
799 799
800 800
/// Test parsing a module declaration with attributes.
801 801
@test fn testParseModAttributes() throws (testing::TestError) {
802 -
    let node = try! parseStmtStr("pub mod io;");
802 +
    let node = try! parseStmtStr("export mod io;");
803 803
    let case ast::NodeValue::Mod(decl) = node.value
804 804
        else throw testing::TestError::Failed;
805 805
806 806
    try expectIdent(decl.name, "io");
807 807
    let attrs = decl.attrs
810 810
    try testing::expect(attrs.list.len == 1);
811 811
812 812
    let case ast::NodeValue::Attribute(attr) = attrs.list[0].value
813 813
        else throw testing::TestError::Failed;
814 814
815 -
    try testing::expect(attr == ast::Attribute::Pub);
816 -
    try testing::expect(ast::attributesContains(&attrs, ast::Attribute::Pub));
815 +
    try testing::expect(attr == ast::Attribute::Export);
816 +
    try testing::expect(ast::attributesContains(&attrs, ast::Attribute::Export));
817 817
}
818 818
819 819
/// Test parsing an optional type.
820 820
@test fn testParseTypeOptional() throws (testing::TestError) {
821 821
    let node = try! parseTypeStr("?i32");
1115 1115
    try testing::expect(super::check(&parser, scanner::TokenKind::Ident));
1116 1116
}
1117 1117
1118 1118
/// Test parsing a function declaration with attributes.
1119 1119
@test fn testParseFnDeclAttributes() throws (testing::TestError) {
1120 -
    let node = try! parseStmtStr("pub fn run();");
1120 +
    let node = try! parseStmtStr("export fn run();");
1121 1121
    let case ast::NodeValue::FnDecl(decl) = node.value
1122 1122
        else throw testing::TestError::Failed;
1123 1123
1124 1124
    try expectIdent(decl.name, "run");
1125 1125
1128 1128
1129 1129
    try testing::expect(attrs.list.len == 2);
1130 1130
1131 1131
    let case ast::NodeValue::Attribute(attr0) = attrs.list[0].value
1132 1132
        else throw testing::TestError::Failed;
1133 -
    try testing::expect(attr0 == ast::Attribute::Pub);
1133 +
    try testing::expect(attr0 == ast::Attribute::Export);
1134 1134
1135 1135
    let case ast::NodeValue::Attribute(attr1) = attrs.list[1].value
1136 1136
        else throw testing::TestError::Failed;
1137 1137
    try testing::expect(attr1 == ast::Attribute::Extern);
1138 1138
1139 -
    try testing::expect(ast::attributesContains(&attrs, ast::Attribute::Pub));
1139 +
    try testing::expect(ast::attributesContains(&attrs, ast::Attribute::Export));
1140 1140
    try testing::expect(ast::attributesContains(&attrs, ast::Attribute::Extern));
1141 1141
    try testing::expect(decl.body == nil);
1142 1142
}
1143 1143
1144 1144
/// Test parsing a top-level function declaration with inferred extern from `;`.
1175 1175
1176 1176
    try testing::expect(attrs.list.len == 2);
1177 1177
1178 1178
    let case ast::NodeValue::Attribute(attr0) = attrs.list[0].value
1179 1179
        else throw testing::TestError::Failed;
1180 -
    try testing::expect(attr0 == ast::Attribute::Pub);
1180 +
    try testing::expect(attr0 == ast::Attribute::Export);
1181 1181
1182 1182
    let case ast::NodeValue::Attribute(attr1) = attrs.list[1].value
1183 1183
        else throw testing::TestError::Failed;
1184 1184
    try testing::expect(attr1 == ast::Attribute::Extern);
1185 1185
1186 -
    try testing::expect(ast::attributesContains(&attrs, ast::Attribute::Pub));
1186 +
    try testing::expect(ast::attributesContains(&attrs, ast::Attribute::Export));
1187 1187
    try testing::expect(ast::attributesContains(&attrs, ast::Attribute::Extern));
1188 1188
    try testing::expect(decl.body == nil);
1189 1189
}
1190 1190
1191 1191
/// Test parsing a scoped identifier type.
lib/std/lang/resolver.rad +104 -104
1 1
//! Radiance semantic analyzer and type resolver.
2 2
//!
3 3
//! This module performs scope construction, symbol binding, and identifier
4 4
//! resolution on top of the AST produced by the parser.
5 5
6 -
pub mod printer;
6 +
export mod printer;
7 7
8 8
/// Unit tests for the resolver.
9 9
@test mod tests;
10 10
11 11
// TODO: Move to raw vectors to reduce list duplication?
19 19
use std::lang::ast;
20 20
use std::lang::parser;
21 21
use std::lang::module;
22 22
23 23
/// Maximum number of diagnostics recorded.
24 -
pub const MAX_ERRORS: u32 = 64;
24 +
export const MAX_ERRORS: u32 = 64;
25 25
26 26
/// Synthetic function name used when wrapping a bare expression for analysis.
27 -
pub const ANALYZE_EXPR_FN_NAME: *[u8] = "__expr__";
27 +
export const ANALYZE_EXPR_FN_NAME: *[u8] = "__expr__";
28 28
/// Synthetic function name used when wrapping a block for analysis.
29 -
pub const ANALYZE_BLOCK_FN_NAME: *[u8] = "__block__";
29 +
export const ANALYZE_BLOCK_FN_NAME: *[u8] = "__block__";
30 30
31 31
/// Maximum number of symbols stored within a module scope.
32 -
pub const MAX_MODULE_SYMBOLS: u32 = 512;
32 +
export const MAX_MODULE_SYMBOLS: u32 = 512;
33 33
/// Maximum number of symbols stored within a local scope.
34 -
pub const MAX_LOCAL_SYMBOLS: u32 = 32;
34 +
export const MAX_LOCAL_SYMBOLS: u32 = 32;
35 35
/// Maximum function parameters.
36 -
pub const MAX_FN_PARAMS: u32 = 8;
36 +
export const MAX_FN_PARAMS: u32 = 8;
37 37
/// Maximum function thrown types.
38 -
pub const MAX_FN_THROWS: u32 = 8;
38 +
export const MAX_FN_THROWS: u32 = 8;
39 39
/// Maximum number of variants in a union.
40 40
/// Nb. This should not be raised above `255`,
41 41
/// as tags are stored using 8-bits only.
42 -
pub const MAX_UNION_VARIANTS: u32 = 128;
42 +
export const MAX_UNION_VARIANTS: u32 = 128;
43 43
/// Maximum nesting of loops.
44 -
pub const MAX_LOOP_DEPTH: u32 = 16;
44 +
export const MAX_LOOP_DEPTH: u32 = 16;
45 45
/// Maximum trait instances.
46 -
pub const MAX_INSTANCES: u32 = 128;
46 +
export const MAX_INSTANCES: u32 = 128;
47 47
/// Maximum standalone methods (across all types).
48 -
pub const MAX_METHODS: u32 = 256;
48 +
export const MAX_METHODS: u32 = 256;
49 49
50 50
/// Trait definition stored in the resolver.
51 -
pub record TraitType {
51 +
export record TraitType {
52 52
    /// Trait name.
53 53
    name: *[u8],
54 54
    /// Method signatures, including from supertraits.
55 55
    methods: *mut [TraitMethod],
56 56
    /// Supertraits that must also be implemented.
57 57
    supertraits: *mut [*TraitType],
58 58
}
59 59
60 60
/// A single method signature within a trait.
61 -
pub record TraitMethod {
61 +
export record TraitMethod {
62 62
    /// Method name.
63 63
    name: *[u8],
64 64
    /// Function type for the method, excluding the receiver.
65 65
    fnType: *FnType,
66 66
    /// Whether the receiver is mutable.
68 68
    /// V-table slot index.
69 69
    index: u32,
70 70
}
71 71
72 72
/// An entry in the trait instance registry.
73 -
pub record InstanceEntry {
73 +
export record InstanceEntry {
74 74
    /// Trait type descriptor.
75 75
    traitType: *TraitType,
76 76
    /// Concrete type that implements the trait.
77 77
    concreteType: Type,
78 78
    /// Name of the concrete type.
82 82
    /// Method symbols for each trait method, in declaration order.
83 83
    methods: *mut [*mut Symbol],
84 84
}
85 85
86 86
/// An entry in the method registry.
87 -
pub record MethodEntry {
87 +
export record MethodEntry {
88 88
    /// Concrete type that owns the method.
89 89
    concreteType: Type,
90 90
    /// Name of the concrete type.
91 91
    concreteTypeName: *[u8],
92 92
    /// Method name.
98 98
    /// Symbol for the method.
99 99
    symbol: *mut Symbol,
100 100
}
101 101
102 102
/// Identifier for the synthetic `len` field.
103 -
pub const LEN_FIELD: *[u8] = "len";
103 +
export const LEN_FIELD: *[u8] = "len";
104 104
/// Identifier for the synthetic `ptr` field.
105 -
pub const PTR_FIELD: *[u8] = "ptr";
105 +
export const PTR_FIELD: *[u8] = "ptr";
106 106
/// Identifier for the synthetic `cap` field.
107 -
pub const CAP_FIELD: *[u8] = "cap";
107 +
export const CAP_FIELD: *[u8] = "cap";
108 108
109 109
/// Maximum `u16` value.
110 110
const U16_MAX: u16 = 0xFFFF;
111 111
/// Maximum `u8` value.
112 112
const U8_MAX: u16 = 0xFF;
128 128
const I64_MIN: i64 = -9223372036854775808;
129 129
/// Maximum `i64` value: 2^63 - 1.
130 130
const I64_MAX: i64 = 9223372036854775807;
131 131
132 132
/// Size of a pointer in bytes.
133 -
pub const PTR_SIZE: u32 = 8;
133 +
export const PTR_SIZE: u32 = 8;
134 134
135 135
/// Information about a record or tuple field.
136 -
pub record RecordField {
136 +
export record RecordField {
137 137
    /// Field name, `nil` for positional fields.
138 138
    name: ?*[u8],
139 139
    /// Field type.
140 140
    fieldType: Type,
141 141
    /// Byte offset from the start of the record.
148 148
    valueType: Type,
149 149
    symbol: *mut Symbol,
150 150
}
151 151
152 152
/// Array type payload.
153 -
pub record ArrayType {
153 +
export record ArrayType {
154 154
    item: *Type,
155 155
    length: u32,
156 156
}
157 157
158 158
/// Record nominal type.
159 -
pub record RecordType {
159 +
export record RecordType {
160 160
    fields: *[RecordField],
161 161
    labeled: bool,
162 162
    /// Cached layout.
163 163
    layout: Layout,
164 164
}
165 165
166 166
/// Union nominal type.
167 -
pub record UnionType {
167 +
export record UnionType {
168 168
    variants: *[UnionVariant],
169 169
    /// Cached layout.
170 170
    layout: Layout,
171 171
    /// Cached payload offset within the union aggregate.
172 172
    valOffset: u32,
173 173
    /// If all variants have void payloads.
174 174
    isAllVoid: bool,
175 175
}
176 176
177 177
/// Metadata for user-defined types.
178 -
pub union NominalType {
178 +
export union NominalType {
179 179
    /// Placeholder for a type that hasn't been fully resolved yet.
180 180
    /// Stores the declaration node for lazy resolution.
181 181
    Placeholder(*ast::Node),
182 182
    Record(RecordType),
183 183
    Union(UnionType),
184 184
}
185 185
186 186
/// Coercion plan, when coercion from one type to another.
187 -
pub union Coercion {
187 +
export union Coercion {
188 188
    /// No coercion, eg. `T -> T`.
189 189
    Identity,
190 190
    /// Eg. `u8 -> i32`. Stores both source and target types for lowering.
191 191
    NumericCast { from: Type, to: Type },
192 192
    /// Eg. `T -> ?T`. Stores the inner value type.
209 209
    /// Scope containing the module's declarations.
210 210
    scope: *mut Scope,
211 211
}
212 212
213 213
/// Type layout.
214 -
pub record Layout {
214 +
export record Layout {
215 215
    /// Size in bytes.
216 216
    size: u32,
217 217
    /// Alignment in bytes.
218 218
    alignment: u32,
219 219
}
225 225
    isAllVoid: bool,
226 226
}
227 227
228 228
/// Pre-computed metadata for slice range expressions.
229 229
/// Used by the lowerer.
230 -
pub record SliceRangeInfo {
230 +
export record SliceRangeInfo {
231 231
    /// Element type of the resulting slice.
232 232
    itemType: *Type,
233 233
    /// Whether the resulting slice is mutable.
234 234
    mutable: bool,
235 235
    /// Static capacity if container is an array.
236 236
    capacity: ?u32,
237 237
}
238 238
239 239
/// Pre-computed metadata for `for` loop iteration.
240 240
/// Used by the lowerer to avoid re-analyzing the iterable type.
241 -
pub union ForLoopInfo {
241 +
export union ForLoopInfo {
242 242
    /// Iterating over a range expression (e.g., `for i in 0..n`).
243 243
    Range {
244 244
        valType: *Type,
245 245
        range: ast::Range,
246 246
        bindingName: ?*[u8],
254 254
        indexName: ?*[u8]
255 255
    },
256 256
}
257 257
258 258
/// Resolved function signature details.
259 -
pub record FnType {
259 +
export record FnType {
260 260
    paramTypes: *[*Type],
261 261
    returnType: *Type,
262 262
    throwList: *[*Type],
263 263
    localCount: u32,
264 264
}
265 265
266 266
/// Describes a type computed during semantic analysis.
267 -
pub union Type {
267 +
export union Type {
268 268
    /// A type that couldn't be decided.
269 269
    Unknown,
270 270
    /// Types only used during inference.
271 271
    Nil, Undefined, Int,
272 272
    /// Primitive types.
304 304
        mutable: bool,
305 305
    },
306 306
}
307 307
308 308
/// Structured diagnostic payload for type mismatches.
309 -
pub record TypeMismatch {
309 +
export record TypeMismatch {
310 310
    expected: Type,
311 311
    actual: Type,
312 312
}
313 313
314 314
/// Structured diagnostic payload for invalid `as` casts.
315 -
pub record InvalidAsCast {
315 +
export record InvalidAsCast {
316 316
    from: Type,
317 317
    to: Type,
318 318
}
319 319
320 320
/// Diagnostic payload for argument count mismatches.
321 -
pub record CountMismatch {
321 +
export record CountMismatch {
322 322
    expected: u32,
323 323
    actual: u32,
324 324
}
325 325
326 326
/// Detailed payload attached to a symbol, specialized per symbol kind.
327 -
pub union SymbolData {
327 +
export union SymbolData {
328 328
    /// Payload describing mutable bindings like variables or functions.
329 329
    Value {
330 330
        /// Whether the binding permits mutation.
331 331
        mutable: bool,
332 332
        /// Custom alignment requirement, or 0 for default.
367 367
    /// Trait symbol.
368 368
    Trait(*mut TraitType),
369 369
}
370 370
371 371
/// Resolved symbol allocated during semantic analysis.
372 -
pub record Symbol {
372 +
export record Symbol {
373 373
    /// Symbol name in source code.
374 374
    name: *[u8],
375 375
    /// Data associated with the symbol.
376 376
    data: SymbolData,
377 377
    /// Bitset of attributes applied to the declaration.
381 381
    /// Module ID this symbol belongs to. Only for module-level symbols.
382 382
    moduleId: ?u16,
383 383
}
384 384
385 385
/// Integer constant payload.
386 -
pub record ConstInt {
386 +
export record ConstInt {
387 387
    /// Absolute magnitude of the value.
388 388
    magnitude: u64,
389 389
    /// Bit width of the integer.
390 390
    bits: u8,
391 391
    /// Whether the integer is signed.
393 393
    /// Whether the value is negative (only valid when `signed` is true).
394 394
    negative: bool,
395 395
}
396 396
397 397
/// Constant value recorded for literal nodes.
398 -
pub union ConstValue {
398 +
export union ConstValue {
399 399
    Bool(bool),
400 400
    Char(u8),
401 401
    String(*[u8]),
402 402
    Int(ConstInt),
403 403
}
415 415
        max: u64,
416 416
    },
417 417
}
418 418
419 419
/// Diagnostic emitted by the analyzer.
420 -
pub record Error {
420 +
export record Error {
421 421
    /// Error category.
422 422
    kind: ErrorKind,
423 423
    /// Node associated with the error, if known.
424 424
    node: ?*ast::Node,
425 425
    /// Module ID where this error occurred.
426 426
    moduleId: u16,
427 427
}
428 428
429 429
/// High-level classification for semantic diagnostics.
430 -
pub union ErrorKind {
430 +
export union ErrorKind {
431 431
    /// Identifier declared more than once in the same scope.
432 432
    DuplicateBinding(*[u8]),
433 433
    /// Identifier referenced before it was declared.
434 434
    UnresolvedSymbol(*[u8]),
435 435
    /// Attempted to assign to an immutable binding.
577 577
    /// Internal error.
578 578
    Internal,
579 579
}
580 580
581 581
/// Diagnostics returned by the analyzer.
582 -
pub record Diagnostics {
582 +
export record Diagnostics {
583 583
    errors: *mut [Error],
584 584
}
585 585
586 586
/// Call context.
587 587
union CallCtx {
604 604
    scope: *mut Scope,
605 605
    child: *ast::Node,
606 606
}
607 607
608 608
/// Node-specific resolver metadata.
609 -
pub union NodeExtra {
609 +
export union NodeExtra {
610 610
    /// No extra data for this node.
611 611
    None,
612 612
    /// Resolved field index for record literal fields.
613 613
    RecordField { index: u32 },
614 614
    /// Slice range metadata for subscript expressions with ranges.
635 635
    /// Slice `.delete(index)` method call.
636 636
    SliceDelete { elemType: *Type },
637 637
}
638 638
639 639
/// Combined resolver metadata for a single AST node.
640 -
pub record NodeData {
640 +
export record NodeData {
641 641
    /// Resolved type for this node.
642 642
    ty: Type,
643 643
    /// Coercion plan applied to this node.
644 644
    coercion: Coercion,
645 645
    /// Symbol associated with this node.
656 656
record NodeDataTable {
657 657
    entries: *mut [NodeData],
658 658
}
659 659
660 660
/// Lexical scope.
661 -
pub record Scope {
661 +
export record Scope {
662 662
    /// Owning AST node, or `nil` for the root scope.
663 663
    owner: ?*ast::Node,
664 664
    /// Parent/enclosing scope.
665 665
    parent: ?*mut Scope,
666 666
    /// Module ID if this is a module scope.
691 691
    /// This is used to determine whether a loop diverges.
692 692
    hasBreak: bool,
693 693
}
694 694
695 695
/// Configuration for semantic analysis.
696 -
pub record Config {
696 +
export record Config {
697 697
    /// Whether we're building in test mode.
698 698
    buildTest: bool,
699 699
}
700 700
701 701
/// How pattern bindings are created during match.
702 -
pub union MatchBy {
702 +
export union MatchBy {
703 703
    /// Match by value.
704 704
    Value,
705 705
    /// Match by immutable reference.
706 706
    Ref,
707 707
    /// Match by mutable reference.
716 716
    /// Is the match constant?
717 717
    isConst: bool
718 718
}
719 719
720 720
/// Result of unwrapping a type for pattern matching.
721 -
pub record MatchSubject {
721 +
export record MatchSubject {
722 722
    /// The effective type to match against.
723 723
    effectiveTy: Type,
724 724
    /// How bindings should be created.
725 725
    by: MatchBy,
726 726
}
727 727
728 728
/// Unwrap a pointer type for pattern matching.
729 -
pub fn unwrapMatchSubject(ty: Type) -> MatchSubject {
729 +
export fn unwrapMatchSubject(ty: Type) -> MatchSubject {
730 730
    if let case Type::Pointer { target, mutable } = ty {
731 731
        let by = MatchBy::MutRef if mutable else MatchBy::Ref;
732 732
        return MatchSubject { effectiveTy: *target, by };
733 733
    }
734 734
    return MatchSubject { effectiveTy: ty, by: MatchBy::Value };
735 735
}
736 736
737 737
/// Global resolver state.
738 -
pub record Resolver {
738 +
export record Resolver {
739 739
    /// Current scope.
740 740
    scope: *mut Scope,
741 741
    /// Package scope containing package roots and top-level symbols.
742 742
    pkgScope: *mut Scope,
743 743
    /// Stack of loop contexts for nested loops.
771 771
    /// Number of registered standalone methods.
772 772
    methodsLen: u32,
773 773
}
774 774
775 775
/// Internal error sentinel thrown when analysis cannot proceed.
776 -
pub union ResolveError {
776 +
export union ResolveError {
777 777
    Failure,
778 778
}
779 779
780 780
/// Node in the type interning linked list.
781 781
record TypeNode {
782 782
    ty: Type,
783 783
    next: ?*TypeNode,
784 784
}
785 785
786 786
/// Allocate and intern a type in the arena, returning a pointer for deduplication.
787 -
pub fn allocType(self: *mut Resolver, ty: Type) -> *Type {
787 +
export fn allocType(self: *mut Resolver, ty: Type) -> *Type {
788 788
    // Search existing types for a match.
789 789
    let mut cursor = self.types;
790 790
    while let node = cursor {
791 791
        if node.ty == ty {
792 792
            return &node.ty;
839 839
    }
840 840
    return nil;
841 841
}
842 842
843 843
/// Storage buffers used by the analyzer.
844 -
pub record ResolverStorage {
844 +
export record ResolverStorage {
845 845
    /// Unified arena for symbols, scopes, and nominal type.
846 846
    arena: alloc::Arena,
847 847
    /// Node semantic metadata indexed by node ID.
848 848
    nodeData: *mut [NodeData],
849 849
    /// Package scope.
851 851
    /// Error storage.
852 852
    errors: *mut [Error],
853 853
}
854 854
855 855
/// Input for resolving a single package.
856 -
pub record Pkg {
856 +
export record Pkg {
857 857
    /// Root module entry.
858 858
    rootEntry: *module::ModuleEntry,
859 859
    /// Root AST node.
860 860
    rootAst: *ast::Node,
861 861
}
862 862
863 863
/// Construct a resolver with module context and backing storage.
864 -
pub fn resolver(
864 +
export fn resolver(
865 865
    storage: ResolverStorage,
866 866
    config: Config
867 867
) -> Resolver {
868 868
    let mut arena = storage.arena;
869 869
    let symbols = try! alloc::allocSlice(
919 919
        methodsLen: 0,
920 920
    };
921 921
}
922 922
923 923
/// Return `true` if there are no errors in the diagnostics.
924 -
pub fn success(diag: *Diagnostics) -> bool {
924 +
export fn success(diag: *Diagnostics) -> bool {
925 925
    return diag.errors.len == 0;
926 926
}
927 927
928 928
/// Retrieve an error diagnostic by index, if present.
929 -
pub fn errorAt(errs: *[Error], index: u32) -> ?*Error {
929 +
export fn errorAt(errs: *[Error], index: u32) -> ?*Error {
930 930
    if index >= errs.len {
931 931
        return nil;
932 932
    }
933 933
    return &errs[index];
934 934
}
978 978
}
979 979
980 980
/// Enter a new local scope that is the child of the current scope.
981 981
/// This creates a parent/child relationship that means that lookups in the
982 982
/// child scope can recurse upwards.
983 -
pub fn enterScope(self: *mut Resolver, owner: *ast::Node) -> *Scope {
983 +
export fn enterScope(self: *mut Resolver, owner: *ast::Node) -> *Scope {
984 984
    let scope = allocScope(self, owner, MAX_LOCAL_SYMBOLS);
985 985
    scope.parent = self.scope;
986 986
    self.scope = scope;
987 987
    return scope;
988 988
}
989 989
990 990
/// Enter a module scope. Returns an object that can be used to exit the scope.
991 -
pub fn enterModuleScope(self: *mut Resolver, owner: *ast::Node, module: *module::ModuleEntry) -> ModuleScope {
991 +
export fn enterModuleScope(self: *mut Resolver, owner: *ast::Node, module: *module::ModuleEntry) -> ModuleScope {
992 992
    let prevScope = self.scope;
993 993
    let prevMod = self.currentMod;
994 994
    let scope = allocScope(self, owner, MAX_MODULE_SYMBOLS);
995 995
996 996
    self.scope = scope;
1011 1011
1012 1012
    return enterModuleScope(self, modRoot, modEntry);
1013 1013
}
1014 1014
1015 1015
/// Exit a module scope, given the object returned by `enterModuleScope`.
1016 -
pub fn exitModuleScope(self: *mut Resolver, entry: ModuleScope) {
1016 +
export fn exitModuleScope(self: *mut Resolver, entry: ModuleScope) {
1017 1017
    self.scope = entry.prevScope;
1018 1018
    self.currentMod = entry.prevMod;
1019 1019
}
1020 1020
1021 1021
/// Exit the most recent scope.
1022 -
pub fn exitScope(self: *mut Resolver) {
1022 +
export fn exitScope(self: *mut Resolver) {
1023 1023
    let parent = self.scope.parent else {
1024 1024
        // TODO: This should be a panic, but one of the tests hits this
1025 1025
        // clause, which might be a bug in the generator.
1026 1026
        return;
1027 1027
    };
1169 1169
fn setForLoopInfo(self: *mut Resolver, node: *ast::Node, info: ForLoopInfo) {
1170 1170
    self.nodeData.entries[node.id].extra = NodeExtra::ForLoop(info);
1171 1171
}
1172 1172
1173 1173
/// Retrieve the constant value associated with a node, if any.
1174 -
pub fn constValueEntry(self: *Resolver, node: *ast::Node) -> ?ConstValue {
1174 +
export fn constValueEntry(self: *Resolver, node: *ast::Node) -> ?ConstValue {
1175 1175
    return self.nodeData.entries[node.id].constValue;
1176 1176
}
1177 1177
1178 1178
/// Get the resolved record field index for a record literal field node.
1179 -
pub fn recordFieldIndexFor(self: *Resolver, node: *ast::Node) -> ?u32 {
1179 +
export fn recordFieldIndexFor(self: *Resolver, node: *ast::Node) -> ?u32 {
1180 1180
    if let case NodeExtra::RecordField { index } = self.nodeData.entries[node.id].extra {
1181 1181
        return index;
1182 1182
    }
1183 1183
    return nil;
1184 1184
}
1185 1185
1186 1186
/// Get the slice range metadata for a subscript expression with a range index.
1187 -
pub fn sliceRangeInfoFor(self: *Resolver, node: *ast::Node) -> ?SliceRangeInfo {
1187 +
export fn sliceRangeInfoFor(self: *Resolver, node: *ast::Node) -> ?SliceRangeInfo {
1188 1188
    if let case NodeExtra::SliceRange(info) = self.nodeData.entries[node.id].extra {
1189 1189
        return info;
1190 1190
    }
1191 1191
    return nil;
1192 1192
}
1193 1193
1194 1194
/// Get the for-loop metadata for a for-loop node.
1195 -
pub fn forLoopInfoFor(self: *Resolver, node: *ast::Node) -> ?ForLoopInfo {
1195 +
export fn forLoopInfoFor(self: *Resolver, node: *ast::Node) -> ?ForLoopInfo {
1196 1196
    if let case NodeExtra::ForLoop(info) = self.nodeData.entries[node.id].extra {
1197 1197
        return info;
1198 1198
    }
1199 1199
    return nil;
1200 1200
}
1203 1203
fn setProngCatchAll(self: *mut Resolver, node: *ast::Node, catchAll: bool) {
1204 1204
    self.nodeData.entries[node.id].extra = NodeExtra::MatchProng { catchAll };
1205 1205
}
1206 1206
1207 1207
/// Check if a prong is catch-all.
1208 -
pub fn isProngCatchAll(self: *Resolver, node: *ast::Node) -> bool {
1208 +
export fn isProngCatchAll(self: *Resolver, node: *ast::Node) -> bool {
1209 1209
    if let case NodeExtra::MatchProng { catchAll } = self.nodeData.entries[node.id].extra {
1210 1210
        return catchAll;
1211 1211
    }
1212 1212
    return false;
1213 1213
}
1216 1216
fn setMatchConst(self: *mut Resolver, node: *ast::Node, isConst: bool) {
1217 1217
    self.nodeData.entries[node.id].extra = NodeExtra::Match { isConst };
1218 1218
}
1219 1219
1220 1220
/// Check if a match has all constant patterns.
1221 -
pub fn isMatchConst(self: *Resolver, node: *ast::Node) -> bool {
1221 +
export fn isMatchConst(self: *Resolver, node: *ast::Node) -> bool {
1222 1222
    if let case NodeExtra::Match { isConst } = self.nodeData.entries[node.id].extra {
1223 1223
        return isConst;
1224 1224
    }
1225 1225
    return false;
1226 1226
}
1227 1227
1228 1228
/// Get the resolver metadata for a node.
1229 -
pub fn nodeData(self: *Resolver, node: *ast::Node) -> *NodeData {
1229 +
export fn nodeData(self: *Resolver, node: *ast::Node) -> *NodeData {
1230 1230
    return &self.nodeData.entries[node.id];
1231 1231
}
1232 1232
1233 1233
/// Get the type for a node, or `nil` if unknown.
1234 -
pub fn typeFor(self: *Resolver, node: *ast::Node) -> ?Type {
1234 +
export fn typeFor(self: *Resolver, node: *ast::Node) -> ?Type {
1235 1235
    let ty = self.nodeData.entries[node.id].ty;
1236 1236
    if ty == Type::Unknown {
1237 1237
        return nil;
1238 1238
    }
1239 1239
    return ty;
1240 1240
}
1241 1241
1242 1242
/// Get the scope associated with a node.
1243 -
pub fn scopeFor(self: *Resolver, node: *ast::Node) -> ?*mut Scope {
1243 +
export fn scopeFor(self: *Resolver, node: *ast::Node) -> ?*mut Scope {
1244 1244
    return self.nodeData.entries[node.id].scope;
1245 1245
}
1246 1246
1247 1247
/// Get the symbol bound to a node.
1248 -
pub fn symbolFor(self: *Resolver, node: *ast::Node) -> ?*mut Symbol {
1248 +
export fn symbolFor(self: *Resolver, node: *ast::Node) -> ?*mut Symbol {
1249 1249
    return self.nodeData.entries[node.id].sym;
1250 1250
}
1251 1251
1252 1252
/// Get the coercion plan associated with a node, if any.
1253 -
pub fn coercionFor(self: *Resolver, node: *ast::Node) -> ?Coercion {
1253 +
export fn coercionFor(self: *Resolver, node: *ast::Node) -> ?Coercion {
1254 1254
    let c = self.nodeData.entries[node.id].coercion;
1255 1255
    if c == Coercion::Identity {
1256 1256
        return nil;
1257 1257
    }
1258 1258
    return c;
1259 1259
}
1260 1260
1261 1261
/// Get the module ID for a symbol by walking up its scope chain.
1262 -
pub fn moduleIdForSymbol(self: *Resolver, sym: *Symbol) -> ?u16 {
1262 +
export fn moduleIdForSymbol(self: *Resolver, sym: *Symbol) -> ?u16 {
1263 1263
    // For module-level symbols, return the cached module ID.
1264 1264
    if let id = sym.moduleId {
1265 1265
        return id;
1266 1266
    }
1267 1267
    // For module symbols, return the module ID directly.
1275 1275
    return nil;
1276 1276
}
1277 1277
1278 1278
/// Get the binding node for a variant pattern.
1279 1279
/// Returns the argument node if this is a variant constructor with a non-placeholder binding.
1280 -
pub fn variantPatternBinding(self: *Resolver, pattern: *ast::Node) -> ?*ast::Node {
1280 +
export fn variantPatternBinding(self: *Resolver, pattern: *ast::Node) -> ?*ast::Node {
1281 1281
    let case ast::NodeValue::Call(call) = pattern.value
1282 1282
        else return nil;
1283 1283
    let sym = symbolFor(self, call.callee)
1284 1284
        else return nil;
1285 1285
    let case SymbolData::Variant { .. } = sym.data
1327 1327
        else => return false,
1328 1328
    }
1329 1329
}
1330 1330
1331 1331
/// Check if a type is an unsigned integer type.
1332 -
pub fn isUnsignedIntegerType(ty: Type) -> bool {
1332 +
export fn isUnsignedIntegerType(ty: Type) -> bool {
1333 1333
    match ty {
1334 1334
        case Type::U8, Type::U16, Type::U32, Type::U64 => return true,
1335 1335
        else => return false,
1336 1336
    }
1337 1337
}
1343 1343
    }
1344 1344
    return b;
1345 1345
}
1346 1346
1347 1347
/// Get the layout of a type.
1348 -
pub fn getTypeLayout(ty: Type) -> Layout {
1348 +
export fn getTypeLayout(ty: Type) -> Layout {
1349 1349
    match ty {
1350 1350
        case Type::Void, Type::Never => return Layout { size: 0, alignment: 0 },
1351 1351
        case Type::Bool, Type::U8, Type::I8 => return Layout { size: 1, alignment: 1 },
1352 1352
        case Type::U16, Type::I16 => return Layout { size: 2, alignment: 2 },
1353 1353
        case Type::U32, Type::I32 => return Layout { size: 4, alignment: 4 },
1365 1365
        }
1366 1366
    }
1367 1367
}
1368 1368
1369 1369
/// Get the layout of a type or value.
1370 -
pub fn getLayout(self: *Resolver, node: *ast::Node, ty: Type) -> Layout {
1370 +
export fn getLayout(self: *Resolver, node: *ast::Node, ty: Type) -> Layout {
1371 1371
    let mut layout = getTypeLayout(ty);
1372 1372
    // Check for symbol-specific alignment override.
1373 1373
    if let sym = symbolFor(self, node) {
1374 1374
        if let case SymbolData::Value { alignment, .. } = sym.data {
1375 1375
            if alignment > 0 {
1379 1379
    }
1380 1380
    return layout;
1381 1381
}
1382 1382
1383 1383
/// Get the layout of an array type.
1384 -
pub fn getArrayLayout(arr: ArrayType) -> Layout {
1384 +
export fn getArrayLayout(arr: ArrayType) -> Layout {
1385 1385
    let itemLayout = getTypeLayout(*arr.item);
1386 1386
    return Layout {
1387 1387
        size: itemLayout.size * arr.length,
1388 1388
        alignment: itemLayout.alignment,
1389 1389
    };
1390 1390
}
1391 1391
1392 1392
/// Get the layout of an optional type.
1393 -
pub fn getOptionalLayout(inner: Type) -> Layout {
1393 +
export fn getOptionalLayout(inner: Type) -> Layout {
1394 1394
    // Nullable types use null pointer optimization -- no tag byte needed.
1395 1395
    if isNullableType(inner) {
1396 1396
        return getTypeLayout(inner);
1397 1397
    }
1398 1398
    let innerLayout = getTypeLayout(inner);
1405 1405
        alignment,
1406 1406
    };
1407 1407
}
1408 1408
1409 1409
/// Get the payload offset within an optional aggregate.
1410 -
pub fn getOptionalValOffset(inner: Type) -> u32 {
1410 +
export fn getOptionalValOffset(inner: Type) -> u32 {
1411 1411
    let innerLayout = getTypeLayout(inner);
1412 1412
    return mem::alignUp(1, innerLayout.alignment);
1413 1413
}
1414 1414
1415 1415
/// Check if a type is optional.
1416 -
pub fn isOptionalType(ty: Type) -> bool {
1416 +
export fn isOptionalType(ty: Type) -> bool {
1417 1417
    match ty {
1418 1418
        case Type::Optional(_) => return true,
1419 1419
        else => return false,
1420 1420
    }
1421 1421
}
1422 1422
1423 1423
/// Check if a type uses null pointer optimization.
1424 1424
/// This applies to optional pointers `?*T` and optional slices `?*[T]`,
1425 1425
/// where `nil` is represented as a null data pointer with no tag byte.
1426 -
pub fn isOptionalPointer(ty: Type) -> bool {
1426 +
export fn isOptionalPointer(ty: Type) -> bool {
1427 1427
    if let case Type::Optional(inner) = ty {
1428 1428
        return isNullableType(*inner);
1429 1429
    }
1430 1430
    return false;
1431 1431
}
1432 1432
1433 1433
/// Check if a type uses the optional aggregate representation.
1434 -
pub fn isOptionalAggregate(ty: Type) -> bool {
1434 +
export fn isOptionalAggregate(ty: Type) -> bool {
1435 1435
    if let case Type::Optional(inner) = ty {
1436 1436
        return not isNullableType(*inner);
1437 1437
    }
1438 1438
    return false;
1439 1439
}
1440 1440
1441 1441
/// Check if a type can use null to represent `nil`.
1442 1442
/// Pointers and slices have a data pointer that is never null when valid.
1443 -
pub fn isNullableType(ty: Type) -> bool {
1443 +
export fn isNullableType(ty: Type) -> bool {
1444 1444
    match ty {
1445 1445
        case Type::Pointer { .. }, Type::Slice { .. } => return true,
1446 1446
        else => return false,
1447 1447
    }
1448 1448
}
1449 1449
1450 1450
/// Get the layout of a nominal type.
1451 -
pub fn getNominalLayout(info: NominalType) -> Layout {
1451 +
export fn getNominalLayout(info: NominalType) -> Layout {
1452 1452
    match info {
1453 1453
        case NominalType::Placeholder(_) => {
1454 1454
            panic "getNominalLayout: placeholder type";
1455 1455
        }
1456 1456
        case NominalType::Record(recordType) => {
1461 1461
        }
1462 1462
    }
1463 1463
}
1464 1464
1465 1465
/// Get the layout of a result aggregate with a tag and the larger payload.
1466 -
pub fn getResultLayout(payload: Type, throwList: *[*Type]) -> Layout {
1466 +
export fn getResultLayout(payload: Type, throwList: *[*Type]) -> Layout {
1467 1467
    let payloadLayout = getTypeLayout(payload);
1468 1468
    let mut maxSize = payloadLayout.size;
1469 1469
    let mut maxAlign = payloadLayout.alignment;
1470 1470
1471 1471
    for errType in throwList {
1515 1515
    *iota = tag + 1;
1516 1516
    return tag;
1517 1517
}
1518 1518
1519 1519
/// Check if a type is a union without payloads.
1520 -
pub fn isVoidUnion(ty: Type) -> bool {
1520 +
export fn isVoidUnion(ty: Type) -> bool {
1521 1521
    let case Type::Nominal(NominalType::Union(unionType)) = ty
1522 1522
        else return false;
1523 1523
    return unionType.isAllVoid;
1524 1524
}
1525 1525
1841 1841
    }
1842 1842
    return true;
1843 1843
}
1844 1844
1845 1845
/// Check if two types are structurally equal.
1846 -
pub fn typesEqual(a: Type, b: Type) -> bool {
1846 +
export fn typesEqual(a: Type, b: Type) -> bool {
1847 1847
    if a == b {
1848 1848
        return true;
1849 1849
    }
1850 1850
    match a {
1851 1851
        case Type::Pointer { target: aTarget, mutable: aMutable } => {
1871 1871
        else => return false,
1872 1872
    }
1873 1873
}
1874 1874
1875 1875
/// Get the record info from a record type.
1876 -
pub fn getRecord(ty: Type) -> ?RecordType {
1876 +
export fn getRecord(ty: Type) -> ?RecordType {
1877 1877
    let case Type::Nominal(NominalType::Record(recInfo)) = ty else return nil;
1878 1878
    return recInfo;
1879 1879
}
1880 1880
1881 1881
/// Auto-dereference a type: if it's a pointer, return the target type.
1882 -
pub fn autoDeref(ty: Type) -> Type {
1882 +
export fn autoDeref(ty: Type) -> Type {
1883 1883
    if let case Type::Pointer { target, .. } = ty {
1884 1884
        return *target;
1885 1885
    }
1886 1886
    return ty;
1887 1887
}
1888 1888
1889 1889
/// Get field info for a record-like type (records, slices) by field index.
1890 -
pub fn getRecordField(ty: Type, index: u32) -> ?RecordField {
1890 +
export fn getRecordField(ty: Type, index: u32) -> ?RecordField {
1891 1891
    match ty {
1892 1892
        case Type::Nominal(NominalType::Record(recInfo)) => {
1893 1893
            if index >= recInfo.fields.len {
1894 1894
                return nil;
1895 1895
            }
2146 2146
    }
2147 2147
    return nil;
2148 2148
}
2149 2149
2150 2150
/// Find a symbol by name in a specific scope (matches any symbol kind).
2151 -
pub fn findSymbolInScope(scope: *Scope, name: *[u8]) -> ?*mut Symbol {
2151 +
export fn findSymbolInScope(scope: *Scope, name: *[u8]) -> ?*mut Symbol {
2152 2152
    return findInScope(scope, name, isAnySymbol);
2153 2153
}
2154 2154
2155 2155
/// Look up a value symbol by name, searching from the given scope outward.
2156 2156
fn findValueSymbol(scope: *Scope, name: *[u8]) -> ?*mut Symbol {
2258 2258
    return nil;
2259 2259
}
2260 2260
2261 2261
/// Check if a symbol is accessible from the given scope.
2262 2262
/// A symbol is accessible if:
2263 -
/// * It has the `pub` attribute, OR
2263 +
/// * It has the `export` attribute, OR
2264 2264
/// * It's being accessed from within the module where it was defined.
2265 2265
fn isSymbolVisible(sym: *Symbol, symScope: *Scope, fromScope: *Scope) -> bool {
2266 2266
    // Public symbols are visible from anywhere.
2267 -
    if ast::hasAttribute(sym.attrs, ast::Attribute::Pub) {
2267 +
    if ast::hasAttribute(sym.attrs, ast::Attribute::Export) {
2268 2268
        return true;
2269 2269
    }
2270 2270
    // In test mode, @test symbols are visible from anywhere
2271 2271
    // so the test runner can reference them.
2272 2272
    if ast::hasAttribute(sym.attrs, ast::Attribute::Test) {
2805 2805
    }
2806 2806
    return false;
2807 2807
}
2808 2808
2809 2809
/// Determine whether a node represents a compile-time constant expression.
2810 -
pub fn isConstExpr(self: *Resolver, node: *ast::Node) -> bool {
2810 +
export fn isConstExpr(self: *Resolver, node: *ast::Node) -> bool {
2811 2811
    match node.value {
2812 2812
        case ast::NodeValue::Bool(_),
2813 2813
             ast::NodeValue::Char(_),
2814 2814
             ast::NodeValue::Number(_),
2815 2815
             ast::NodeValue::String(_),
3235 3235
3236 3236
    return sym;
3237 3237
}
3238 3238
3239 3239
/// Find a trait method by name.
3240 -
pub fn findTraitMethod(traitType: *TraitType, name: *[u8]) -> ?*TraitMethod {
3240 +
export fn findTraitMethod(traitType: *TraitType, name: *[u8]) -> ?*TraitMethod {
3241 3241
    for i in 0..traitType.methods.len {
3242 3242
        if traitType.methods[i].name == name {
3243 3243
            return &traitType.methods[i];
3244 3244
        }
3245 3245
    }
3773 3773
    }
3774 3774
    return nil;
3775 3775
}
3776 3776
3777 3777
/// Look up a standalone method by concrete type and name.
3778 -
pub fn findMethod(self: *Resolver, concreteType: Type, name: *[u8]) -> ?*MethodEntry {
3778 +
export fn findMethod(self: *Resolver, concreteType: Type, name: *[u8]) -> ?*MethodEntry {
3779 3779
    for i in 0..self.methodsLen {
3780 3780
        let entry = &self.methods[i];
3781 3781
        if typesEqual(entry.concreteType, concreteType) and entry.name == name {
3782 3782
            return entry;
3783 3783
        }
3784 3784
    }
3785 3785
    return nil;
3786 3786
}
3787 3787
3788 3788
/// Look up a standalone method entry by its symbol.
3789 -
pub fn findMethodBySymbol(self: *Resolver, sym: *mut Symbol) -> ?*MethodEntry {
3789 +
export fn findMethodBySymbol(self: *Resolver, sym: *mut Symbol) -> ?*MethodEntry {
3790 3790
    for i in 0..self.methodsLen {
3791 3791
        let entry = &self.methods[i];
3792 3792
        if entry.symbol == sym {
3793 3793
            return entry;
3794 3794
        }
3920 3920
3921 3921
    if decl.wildcard {
3922 3922
        // Import all public symbols from the target module.
3923 3923
        for i in 0..resolved.scope.symbolsLen {
3924 3924
            let sym = resolved.scope.symbols[i];
3925 -
            if ast::hasAttribute(sym.attrs, ast::Attribute::Pub) {
3925 +
            if ast::hasAttribute(sym.attrs, ast::Attribute::Export) {
3926 3926
                try addSymbolToScope(self, sym, self.scope, node);
3927 3927
            }
3928 3928
        }
3929 3929
    } else {
3930 3930
        // Regular module import.
4004 4004
}
4005 4005
4006 4006
/// Check whether a pattern node is a destructuring pattern that looks
4007 4007
/// through structure (union variant, record literal, scope access).
4008 4008
/// Identifiers, placeholders, and plain literals are not destructuring.
4009 -
pub fn isDestructuringPattern(pattern: *ast::Node) -> bool {
4009 +
export fn isDestructuringPattern(pattern: *ast::Node) -> bool {
4010 4010
    match pattern.value {
4011 4011
        case ast::NodeValue::Call(_),
4012 4012
             ast::NodeValue::RecordLit(_),
4013 4013
             ast::NodeValue::ScopeAccess(_) => return true,
4014 4014
        else => return false,
4186 4186
}
4187 4187
4188 4188
/// Get the node within a pattern that carries the `UnionVariant` extra.
4189 4189
/// For `ScopeAccess` it is the pattern itself, for `RecordLit` it is the
4190 4190
/// type name, and for `Call` it is the callee.
4191 -
pub fn patternVariantKeyNode(pattern: *ast::Node) -> ?*ast::Node {
4191 +
export fn patternVariantKeyNode(pattern: *ast::Node) -> ?*ast::Node {
4192 4192
    match pattern.value {
4193 4193
        case ast::NodeValue::ScopeAccess(_) => return pattern,
4194 4194
        case ast::NodeValue::RecordLit(lit) => return lit.typeName,
4195 4195
        case ast::NodeValue::Call(call) => return call.callee,
4196 4196
        else => return nil,
6578 6578
        else => return true,
6579 6579
    }
6580 6580
}
6581 6581
6582 6582
/// Analyze a standalone expression by wrapping it in a synthetic function.
6583 -
pub fn resolveExpr(
6583 +
export fn resolveExpr(
6584 6584
    self: *mut Resolver, expr: *ast::Node, arena: *mut ast::NodeArena
6585 6585
) -> Diagnostics throws (ResolveError) {
6586 6586
    let a = alloc::arenaAllocator(&mut arena.arena);
6587 6587
    let exprStmt = ast::synthNode(arena, ast::NodeValue::ExprStmt(expr));
6588 6588
    let bodyStmts = ast::nodeSlice(arena, 1).append(exprStmt, a);
6601 6601
6602 6602
    return Diagnostics { errors: self.errors };
6603 6603
}
6604 6604
6605 6605
/// Analyze a parsed module root, ie. a block of top-level statements.
6606 -
pub fn resolveModuleRoot(self: *mut Resolver, root: *ast::Node) -> Diagnostics throws (ResolveError) {
6606 +
export fn resolveModuleRoot(self: *mut Resolver, root: *ast::Node) -> Diagnostics throws (ResolveError) {
6607 6607
    let case ast::NodeValue::Block(block) = root.value
6608 6608
        else panic "resolveModuleRoot: expected block for module root";
6609 6609
6610 6610
    enterScope(self, root);
6611 6611
    try resolveModuleDecls(self, &block) catch {
6726 6726
        if let case ast::NodeValue::Mod(decl) = node.value {
6727 6727
            try resolveModDecl(res, node, decl);
6728 6728
        }
6729 6729
    }
6730 6730
    // Phase 5b: Process wildcard imports after submodules are resolved,
6731 -
    // so that transitive re-exports (pub use foo::*) are visible.
6731 +
    // so that transitive re-exports (export use foo::*) are visible.
6732 6732
    for node in block.statements {
6733 6733
        if let case ast::NodeValue::Use(decl) = node.value {
6734 6734
            if decl.wildcard {
6735 6735
                try resolveUse(res, node, decl);
6736 6736
            }
6750 6750
        try visitDef(self, stmt);
6751 6751
    }
6752 6752
}
6753 6753
6754 6754
/// Resolve all packages.
6755 -
pub fn resolve(self: *mut Resolver, graph: *module::ModuleGraph, packages: *[Pkg]) -> Diagnostics throws (ResolveError) {
6755 +
export fn resolve(self: *mut Resolver, graph: *module::ModuleGraph, packages: *[Pkg]) -> Diagnostics throws (ResolveError) {
6756 6756
    self.moduleGraph = graph;
6757 6757
6758 6758
    // 1. Bind all package roots to enable cross-package references.
6759 6759
    for i in 0..packages.len {
6760 6760
        let pkg = &packages[i];
lib/std/lang/resolver/printer.rad +1 -1
506 506
    }
507 507
    io::print("\n");
508 508
}
509 509
510 510
/// Entry point for printing resolver diagnostics in vim quickfix format.
511 -
pub fn printDiagnostics(diag: *super::Diagnostics, res: *super::Resolver) {
511 +
export fn printDiagnostics(diag: *super::Diagnostics, res: *super::Resolver) {
512 512
    for i in 0..diag.errors.len {
513 513
        printError(&diag.errors[i], res);
514 514
    }
515 515
}
lib/std/lang/resolver/tests.rad +55 -55
597 597
        else throw testing::TestError::Failed;
598 598
}
599 599
600 600
@test fn testSymbolStoresFnAttributes() throws (testing::TestError) {
601 601
    let mut a = testResolver();
602 -
    let program = "@default pub fn f() { return; }";
602 +
    let program = "@default export fn f() { return; }";
603 603
    let result = try resolveProgramStr(&mut a, program);
604 604
    try expectNoErrors(&result);
605 605
606 606
    let scope = super::scopeFor(&a, result.root)
607 607
        else throw testing::TestError::Failed;
608 608
    let sym = super::findSymbolInScope(scope, "f")
609 609
        else throw testing::TestError::Failed;
610 610
611 -
    try testing::expect(ast::hasAttribute(sym.attrs, ast::Attribute::Pub));
611 +
    try testing::expect(ast::hasAttribute(sym.attrs, ast::Attribute::Export));
612 612
    try testing::expect(ast::hasAttribute(sym.attrs, ast::Attribute::Default));
613 613
    try testing::expectNot(ast::hasAttribute(sym.attrs, ast::Attribute::Extern));
614 614
}
615 615
616 616
@test fn testSymbolStoresRecordAttributes() throws (testing::TestError) {
617 617
    let mut a = testResolver();
618 -
    let program = "pub record S { value: i32 }";
618 +
    let program = "export record S { value: i32 }";
619 619
    let result = try resolveProgramStr(&mut a, program);
620 620
    try expectNoErrors(&result);
621 621
622 622
    let scope = super::scopeFor(&a, result.root)
623 623
        else throw testing::TestError::Failed;
624 624
    let sym = super::findSymbolInScope(scope, "S")
625 625
        else throw testing::TestError::Failed;
626 626
627 -
    try testing::expect(ast::hasAttribute(sym.attrs, ast::Attribute::Pub));
627 +
    try testing::expect(ast::hasAttribute(sym.attrs, ast::Attribute::Export));
628 628
    try testing::expectNot(ast::hasAttribute(sym.attrs, ast::Attribute::Default));
629 629
}
630 630
631 631
@test fn testDefaultAttributeRejectedOnRecord() throws (testing::TestError) {
632 632
    let mut a = testResolver();
3001 3001
@test fn testResolveModuleCannotAccessParentScope() throws (testing::TestError) {
3002 3002
    let mut a = testResolver();
3003 3003
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3004 3004
3005 3005
    // Register root and util modules.
3006 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod util; pub fn helper() {}", &mut arena);
3006 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod util; export fn helper() {}", &mut arena);
3007 3007
    let utilId = try registerModule(&mut MODULE_GRAPH, rootId, "util", "fn main() { helper(); }", &mut arena);
3008 3008
3009 3009
    // Resolve should fail: the parent module is not in scope.
3010 3010
    let result = try resolveModuleTree(&mut a, rootId);
3011 3011
    let err = try expectError(&result);
3018 3018
    let mut a = testResolver();
3019 3019
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3020 3020
3021 3021
    // Register root and util modules.
3022 3022
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod util; fn main() { util::helper(); }", &mut arena);
3023 -
    let utilId = try registerModule(&mut MODULE_GRAPH, rootId, "util", "pub fn helper() {}", &mut arena);
3023 +
    let utilId = try registerModule(&mut MODULE_GRAPH, rootId, "util", "export fn helper() {}", &mut arena);
3024 3024
3025 3025
    // Resolve should succeed: parent can access child.
3026 3026
    let result = try resolveModuleTree(&mut a, rootId);
3027 3027
    try expectNoErrors(&result);
3028 3028
}
3030 3030
@test fn testResolveSiblingModulesCannotAccessDirectly() throws (testing::TestError) {
3031 3031
    let mut a = testResolver();
3032 3032
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3033 3033
3034 3034
    // Register root with two sibling modules.
3035 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod paul; pub mod patrick;", &mut arena);
3035 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod paul; export mod patrick;", &mut arena);
3036 3036
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "paul", "fn main() { patrick::helper(); }", &mut arena);
3037 -
    let utilId = try registerModule(&mut MODULE_GRAPH, rootId, "patrick", "pub fn helper() -> i32 { return 42; }", &mut arena);
3037 +
    let utilId = try registerModule(&mut MODULE_GRAPH, rootId, "patrick", "export fn helper() -> i32 { return 42; }", &mut arena);
3038 3038
3039 3039
    // Resolve should fail: siblings can't access each other directly.
3040 3040
    let result = try resolveModuleTree(&mut a, rootId);
3041 3041
    let err = try expectError(&result);
3042 3042
    let case super::ErrorKind::UnresolvedSymbol(name) = err.kind
3047 3047
@test fn testResolveSiblingModulesViaRoot() throws (testing::TestError) {
3048 3048
    let mut a = testResolver();
3049 3049
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3050 3050
3051 3051
    // Register root with two sibling modules.
3052 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod paul; pub mod patrick;", &mut arena);
3052 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod paul; export mod patrick;", &mut arena);
3053 3053
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "paul", "use root::patrick; fn main() -> i32 { return patrick::helper(); }", &mut arena);
3054 -
    let utilId = try registerModule(&mut MODULE_GRAPH, rootId, "patrick", "pub fn helper() -> i32 { return 42; }", &mut arena);
3054 +
    let utilId = try registerModule(&mut MODULE_GRAPH, rootId, "patrick", "export fn helper() -> i32 { return 42; }", &mut arena);
3055 3055
3056 3056
    // Resolve should succeed: siblings can access each other via root.
3057 3057
    let result = try resolveModuleTree(&mut a, rootId);
3058 3058
    try expectNoErrors(&result);
3059 3059
}
3061 3061
@test fn testResolveModuleMutualRecursion() throws (testing::TestError) {
3062 3062
    let mut a = testResolver();
3063 3063
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3064 3064
3065 3065
    // Register root with two sibling modules that call each other.
3066 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "pub mod left; pub mod right;", &mut arena);
3067 -
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "left", "use root::right; pub fn leftHelper() -> i32 { return right::rightHelper(); }", &mut arena);
3068 -
    let utilId = try registerModule(&mut MODULE_GRAPH, rootId, "right", "use root::left; pub fn rightHelper() -> i32 { return left::leftHelper(); }", &mut arena);
3066 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod left; export mod right;", &mut arena);
3067 +
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "left", "use root::right; export fn leftHelper() -> i32 { return right::rightHelper(); }", &mut arena);
3068 +
    let utilId = try registerModule(&mut MODULE_GRAPH, rootId, "right", "use root::left; export fn rightHelper() -> i32 { return left::leftHelper(); }", &mut arena);
3069 3069
3070 3070
    // Resolve should succeed: cyclic use is allowed.
3071 3071
    let result = try resolveModuleTree(&mut a, rootId);
3072 3072
    try expectNoErrors(&result);
3073 3073
}
3075 3075
@test fn testResolveAccessModuleType() throws (testing::TestError) {
3076 3076
    let mut a = testResolver();
3077 3077
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3078 3078
3079 3079
    // Register root with types module containing a record.
3080 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "pub mod types; mod app;", &mut arena);
3081 -
    let typesId = try registerModule(&mut MODULE_GRAPH, rootId, "types", "pub record Point { x: i32, y: i32 }", &mut arena);
3080 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod types; mod app;", &mut arena);
3081 +
    let typesId = try registerModule(&mut MODULE_GRAPH, rootId, "types", "export record Point { x: i32, y: i32 }", &mut arena);
3082 3082
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::types; fn main() -> i32 { let p = types::Point { x: 1, y: 2 }; return p.x; }", &mut arena);
3083 3083
3084 3084
    // Resolve should succeed: types can be accessed.
3085 3085
    let result = try resolveModuleTree(&mut a, rootId);
3086 3086
    try expectNoErrors(&result);
3089 3089
@test fn testResolveAccessModuleConstant() throws (testing::TestError) {
3090 3090
    let mut a = testResolver();
3091 3091
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3092 3092
3093 3093
    // Register root with constants module.
3094 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "pub mod consts; mod app;", &mut arena);
3095 -
    let constantsId = try registerModule(&mut MODULE_GRAPH, rootId, "consts", "pub const MAX_SIZE: i32 = 100;", &mut arena);
3094 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod consts; mod app;", &mut arena);
3095 +
    let constantsId = try registerModule(&mut MODULE_GRAPH, rootId, "consts", "export const MAX_SIZE: i32 = 100;", &mut arena);
3096 3096
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::consts; fn main() -> i32 { return consts::MAX_SIZE; }", &mut arena);
3097 3097
3098 3098
    // Resolve should succeed: constants can be accessed.
3099 3099
    let result = try resolveModuleTree(&mut a, rootId);
3100 3100
    try expectNoErrors(&result);
3103 3103
@test fn testResolveRootSymbolMustBeImported() throws (testing::TestError) {
3104 3104
    let mut a = testResolver();
3105 3105
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3106 3106
3107 3107
    // Register deeply nested modules: `root::app::services::auth`.
3108 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod main; pub fn helper() -> i32 { return 42; }", &mut arena);
3108 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod main; export fn helper() -> i32 { return 42; }", &mut arena);
3109 3109
    let mainId = try registerModule(&mut MODULE_GRAPH, rootId, "main", "fn run() -> i32 { return root::helper(); }", &mut arena);
3110 3110
3111 3111
    // Resolve should fail: the `root` module must be imported.
3112 3112
    let result = try resolveModuleTree(&mut a, rootId);
3113 3113
    try expectErrorKind(&result, super::ErrorKind::UnresolvedSymbol("root"));
3116 3116
@test fn testResolveUseImportsNestedSymbol() throws (testing::TestError) {
3117 3117
    let mut a = testResolver();
3118 3118
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3119 3119
3120 3120
    // Register deeply nested modules: `root::app::services::auth`.
3121 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "pub mod app; mod main;", &mut arena);
3122 -
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "pub mod services;", &mut arena);
3123 -
    let servicesId = try registerModule(&mut MODULE_GRAPH, appId, "services", "pub mod auth;", &mut arena);
3124 -
    let authId = try registerModule(&mut MODULE_GRAPH, servicesId, "auth", "pub fn login() -> i32 { return 1; }", &mut arena);
3121 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod app; mod main;", &mut arena);
3122 +
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "export mod services;", &mut arena);
3123 +
    let servicesId = try registerModule(&mut MODULE_GRAPH, appId, "services", "export mod auth;", &mut arena);
3124 +
    let authId = try registerModule(&mut MODULE_GRAPH, servicesId, "auth", "export fn login() -> i32 { return 1; }", &mut arena);
3125 3125
    let mainId = try registerModule(&mut MODULE_GRAPH, rootId, "main", "use root::app::services::auth; fn run() -> i32 { return auth::login(); }", &mut arena);
3126 3126
    let otherId = try registerModule(&mut MODULE_GRAPH, rootId, "other", "use root; fn run() -> i32 { return root::app::services::auth::login(); }", &mut arena);
3127 3127
3128 3128
    // Resolve should succeed: use imports the module symbol.
3129 3129
    let result = try resolveModuleTree(&mut a, rootId);
3146 3146
@test fn testResolveUsePrivateFn() throws (testing::TestError) {
3147 3147
    let mut a = testResolver();
3148 3148
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3149 3149
3150 3150
    // Register root with util module containing a private function.
3151 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "pub mod util; mod app;", &mut arena);
3151 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod util; mod app;", &mut arena);
3152 3152
    let utilId = try registerModule(&mut MODULE_GRAPH, rootId, "util", "fn private() -> i32 { return 42; }", &mut arena);
3153 3153
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::util; fn main() -> i32 { return util::private(); }", &mut arena);
3154 3154
3155 3155
    // Resolve should fail: function is not public.
3156 3156
    let result = try resolveModuleTree(&mut a, rootId);
3174 3174
@test fn testResolveUsePublicMod() throws (testing::TestError) {
3175 3175
    let mut a = testResolver();
3176 3176
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3177 3177
3178 3178
    // Register root with public and private child modules.
3179 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod main; pub mod public;", &mut arena);
3179 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod main; export mod public;", &mut arena);
3180 3180
    let privateId = try registerModule(&mut MODULE_GRAPH, rootId, "public", "{}", &mut arena);
3181 3181
    let publicId = try registerModule(&mut MODULE_GRAPH, rootId, "main", "use root::public;", &mut arena);
3182 3182
3183 3183
    // Resolve should succeed: module is public.
3184 3184
    let result = try resolveModuleTree(&mut a, rootId);
3188 3188
@test fn testResolveUseNonPublicType() throws (testing::TestError) {
3189 3189
    let mut a = testResolver();
3190 3190
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3191 3191
3192 3192
    // Register root with types module containing a private record.
3193 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "pub mod types; mod app;", &mut arena);
3193 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod types; mod app;", &mut arena);
3194 3194
    let typesId = try registerModule(&mut MODULE_GRAPH, rootId, "types", "record Priv { x: i32 }", &mut arena);
3195 3195
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::types; fn main() -> types::Priv { return types::Priv { x: 1 }; }", &mut arena);
3196 3196
3197 3197
    // Resolve should fail: record is not public.
3198 3198
    let result = try resolveModuleTree(&mut a, rootId);
3202 3202
@test fn testResolveImportPublicType() throws (testing::TestError) {
3203 3203
    let mut a = testResolver();
3204 3204
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3205 3205
3206 3206
    // Register root with types module containing a public record.
3207 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "pub mod types; mod app;", &mut arena);
3208 -
    let typesId = try registerModule(&mut MODULE_GRAPH, rootId, "types", "pub record Pub { x: i32 }", &mut arena);
3207 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod types; mod app;", &mut arena);
3208 +
    let typesId = try registerModule(&mut MODULE_GRAPH, rootId, "types", "export record Pub { x: i32 }", &mut arena);
3209 3209
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::types; fn main() -> types::Pub { return types::Pub { x: 1 }; }", &mut arena);
3210 3210
3211 3211
    // Resolve should succeed: record is public.
3212 3212
    let result = try resolveModuleTree(&mut a, rootId);
3213 3213
    try expectNoErrors(&result);
3216 3216
@test fn testResolveUseNonPublicStatic() throws (testing::TestError) {
3217 3217
    let mut a = testResolver();
3218 3218
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3219 3219
3220 3220
    // Register root with statics module containing a private static.
3221 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "pub mod statics; mod app;", &mut arena);
3221 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod statics; mod app;", &mut arena);
3222 3222
    let staticsId = try registerModule(&mut MODULE_GRAPH, rootId, "statics", "static PRIVATE: i32 = 42;", &mut arena);
3223 3223
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::statics; fn main() -> i32 { return statics::PRIVATE; }", &mut arena);
3224 3224
3225 3225
    // Resolve should fail: static is not public.
3226 3226
    let result = try resolveModuleTree(&mut a, rootId);
3230 3230
@test fn testResolveImportPublicStatic() throws (testing::TestError) {
3231 3231
    let mut a = testResolver();
3232 3232
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3233 3233
3234 3234
    // Register root with statics module containing a public static.
3235 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "pub mod statics; mod app;", &mut arena);
3236 -
    let staticsId = try registerModule(&mut MODULE_GRAPH, rootId, "statics", "pub static PUBLIC: i32 = 42;", &mut arena);
3235 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod statics; mod app;", &mut arena);
3236 +
    let staticsId = try registerModule(&mut MODULE_GRAPH, rootId, "statics", "export static PUBLIC: i32 = 42;", &mut arena);
3237 3237
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::statics; fn main() -> i32 { return statics::PUBLIC; }", &mut arena);
3238 3238
3239 3239
    // Resolve should succeed: static is public.
3240 3240
    let result = try resolveModuleTree(&mut a, rootId);
3241 3241
    try expectNoErrors(&result);
3245 3245
    {
3246 3246
        let mut a = testResolver();
3247 3247
        let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3248 3248
3249 3249
        // Test function access.
3250 -
        let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod child; pub fn parentFn() -> i32 { return 42; }", &mut arena);
3250 +
        let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod child; export fn parentFn() -> i32 { return 42; }", &mut arena);
3251 3251
        let childId = try registerModule(&mut MODULE_GRAPH, rootId, "child", "fn main() -> i32 { return super::parentFn(); }", &mut arena);
3252 3252
        let result = try resolveModuleTree(&mut a, rootId);
3253 3253
        try expectNoErrors(&result);
3254 3254
    } {
3255 3255
        let mut a = testResolver();
3256 3256
        let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3257 3257
3258 3258
        // Test type access.
3259 -
        let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod child; pub record Point { x: i32, y: i32 }", &mut arena);
3259 +
        let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod child; export record Point { x: i32, y: i32 }", &mut arena);
3260 3260
        let childId = try registerModule(&mut MODULE_GRAPH, rootId, "child", "fn make() -> super::Point { return super::Point { x: 1, y: 2 }; }", &mut arena);
3261 3261
        let result = try resolveModuleTree(&mut a, rootId);
3262 3262
        try expectNoErrors(&result);
3263 3263
    }
3264 3264
}
3266 3266
/// Test nested super access to union variants (e.g. `super::E::A`).
3267 3267
@test fn testResolveSuperUnionVariant() throws (testing::TestError) {
3268 3268
    let mut a = testResolver();
3269 3269
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3270 3270
3271 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod c; pub union E { A, B }", &mut arena);
3271 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod c; export union E { A, B }", &mut arena);
3272 3272
    let childId = try registerModule(&mut MODULE_GRAPH, rootId, "c",
3273 3273
        "fn f(x: super::E) { match x { case super::E::A => {}, case super::E::B => {} } }",
3274 3274
        &mut arena);
3275 3275
    let result = try resolveModuleTree(&mut a, rootId);
3276 3276
    try expectNoErrors(&result);
3279 3279
@test fn testResolveUseSuper() throws (testing::TestError) {
3280 3280
    let mut a = testResolver();
3281 3281
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3282 3282
3283 3283
    // Register root with a function, and a child module that uses super to access it.
3284 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod joe; pub mod kate;", &mut arena);
3285 -
    let kateId = try registerModule(&mut MODULE_GRAPH, rootId, "kate", "pub fn run() {}", &mut arena);
3284 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod joe; export mod kate;", &mut arena);
3285 +
    let kateId = try registerModule(&mut MODULE_GRAPH, rootId, "kate", "export fn run() {}", &mut arena);
3286 3286
    let joeId = try registerModule(&mut MODULE_GRAPH, rootId, "joe", "use super::kate; fn main() { kate::run(); }", &mut arena);
3287 3287
3288 3288
    // Resolve should succeed - super allows accessing parent module.
3289 3289
    let result = try resolveModuleTree(&mut a, rootId);
3290 3290
    try expectNoErrors(&result);
3626 3626
/// Test transitive re-export.
3627 3627
@test fn testWildcardReexportTransitive() throws (testing::TestError) {
3628 3628
    let mut a = testResolver();
3629 3629
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3630 3630
3631 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod a; pub mod b;", &mut arena);
3631 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "mod a; export mod b;", &mut arena);
3632 3632
    let aId = try registerModule(&mut MODULE_GRAPH, rootId, "a", "use root::b; fn main() -> i32 { return b::helper() + b::MAX; }", &mut arena);
3633 -
    let bId = try registerModule(&mut MODULE_GRAPH, rootId, "b", "mod c; pub use c::*;", &mut arena);
3634 -
    let cId = try registerModule(&mut MODULE_GRAPH, bId, "c", "mod d; pub use d::*; pub fn helper() -> i32 { return 42; }", &mut arena);
3635 -
    let dId = try registerModule(&mut MODULE_GRAPH, cId, "d", "pub const MAX: i32 = 100;", &mut arena);
3633 +
    let bId = try registerModule(&mut MODULE_GRAPH, rootId, "b", "mod c; export use c::*;", &mut arena);
3634 +
    let cId = try registerModule(&mut MODULE_GRAPH, bId, "c", "mod d; export use d::*; export fn helper() -> i32 { return 42; }", &mut arena);
3635 +
    let dId = try registerModule(&mut MODULE_GRAPH, cId, "d", "export const MAX: i32 = 100;", &mut arena);
3636 3636
3637 3637
    let result = try resolveModuleTree(&mut a, rootId);
3638 3638
    try expectNoErrors(&result);
3639 3639
}
3640 3640
3641 3641
/// Test that wildcard import can access public symbols.
3642 3642
@test fn testWildcardImportPublicOnly() throws (testing::TestError) {
3643 3643
    let mut a = testResolver();
3644 3644
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3645 3645
3646 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "pub mod b; mod a;", &mut arena);
3647 -
    let bId = try registerModule(&mut MODULE_GRAPH, rootId, "b", "pub fn public() -> i32 { return 1; } fn private() -> i32 { return 2; }", &mut arena);
3646 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod b; mod a;", &mut arena);
3647 +
    let bId = try registerModule(&mut MODULE_GRAPH, rootId, "b", "export fn public() -> i32 { return 1; } fn private() -> i32 { return 2; }", &mut arena);
3648 3648
    let aId = try registerModule(&mut MODULE_GRAPH, rootId, "a", "use root::b::*; fn main() -> i32 { return public(); }", &mut arena);
3649 3649
3650 3650
    let result = try resolveModuleTree(&mut a, rootId);
3651 3651
    try expectNoErrors(&result);
3652 3652
}
3654 3654
/// Test that wildcard import cannot access private symbols.
3655 3655
@test fn testWildcardImportSkipsPrivate() throws (testing::TestError) {
3656 3656
    let mut a = testResolver();
3657 3657
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3658 3658
3659 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "pub mod b; mod a;", &mut arena);
3660 -
    let bId = try registerModule(&mut MODULE_GRAPH, rootId, "b", "pub fn public() -> i32 { return 1; } fn private() -> i32 { return 2; }", &mut arena);
3659 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod b; mod a;", &mut arena);
3660 +
    let bId = try registerModule(&mut MODULE_GRAPH, rootId, "b", "export fn public() -> i32 { return 1; } fn private() -> i32 { return 2; }", &mut arena);
3661 3661
    let aId = try registerModule(&mut MODULE_GRAPH, rootId, "a", "use root::b::*; fn main() -> i32 { return private(); }", &mut arena);
3662 3662
3663 3663
    let result = try resolveModuleTree(&mut a, rootId);
3664 3664
    try expectErrorKind(&result, super::ErrorKind::UnresolvedSymbol("private"));
3665 3665
}
3710 3710
/// allowing record fields to use types from imported modules.
3711 3711
@test fn testRecordFieldUsesImportedType() throws (testing::TestError) {
3712 3712
    let mut a = testResolver();
3713 3713
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3714 3714
3715 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "pub mod types; mod scanner;", &mut arena);
3716 -
    let typesId = try registerModule(&mut MODULE_GRAPH, rootId, "types", "pub record Pool { count: u32 }", &mut arena);
3715 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod types; mod scanner;", &mut arena);
3716 +
    let typesId = try registerModule(&mut MODULE_GRAPH, rootId, "types", "export record Pool { count: u32 }", &mut arena);
3717 3717
    let scannerId = try registerModule(&mut MODULE_GRAPH, rootId, "scanner", "use root::types; record Scanner { pool: *types::Pool }", &mut arena);
3718 3718
3719 3719
    let result = try resolveModuleTree(&mut a, rootId);
3720 3720
    try expectNoErrors(&result);
3721 3721
}
3726 3726
/// enabling compile-time evaluation of array sizes using imported constants.
3727 3727
@test fn testImportedConstantInArraySize() throws (testing::TestError) {
3728 3728
    let mut a = testResolver();
3729 3729
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
3730 3730
3731 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "pub mod consts; mod app;", &mut arena);
3732 -
    let constsId = try registerModule(&mut MODULE_GRAPH, rootId, "consts", "pub const SIZE: u32 = 8;", &mut arena);
3731 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod consts; mod app;", &mut arena);
3732 +
    let constsId = try registerModule(&mut MODULE_GRAPH, rootId, "consts", "export const SIZE: u32 = 8;", &mut arena);
3733 3733
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::consts; static BUFFER: [u8; consts::SIZE] = undefined;", &mut arena);
3734 3734
3735 3735
    let result = try resolveModuleTree(&mut a, rootId);
3736 3736
    try expectNoErrors(&result);
3737 3737
}
4772 4772
/// Cross-module trait: coerce to trait object and dispatch from a different module.
4773 4773
@test fn testResolveTraitCrossModuleCoercion() throws (testing::TestError) {
4774 4774
    let mut a = testResolver();
4775 4775
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
4776 4776
4777 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "pub mod defs; mod app;", &mut arena);
4778 -
    let defsId = try registerModule(&mut MODULE_GRAPH, rootId, "defs", "pub record Counter { value: i32 } pub trait Adder { fn (*mut Adder) add(n: i32) -> i32; } instance Adder for Counter { fn (c: *mut Counter) add(n: i32) -> i32 { c.value = c.value + n; return c.value; } }", &mut arena);
4777 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod defs; mod app;", &mut arena);
4778 +
    let defsId = try registerModule(&mut MODULE_GRAPH, rootId, "defs", "export record Counter { value: i32 } export trait Adder { fn (*mut Adder) add(n: i32) -> i32; } instance Adder for Counter { fn (c: *mut Counter) add(n: i32) -> i32 { c.value = c.value + n; return c.value; } }", &mut arena);
4779 4779
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::defs; fn test() -> i32 { let mut c = defs::Counter { value: 10 }; let a: *mut opaque defs::Adder = &mut c; return a.add(5); }", &mut arena);
4780 4780
4781 4781
    let result = try resolveModuleTree(&mut a, rootId);
4782 4782
    try expectNoErrors(&result);
4783 4783
}
4785 4785
/// Instance in a different module from trait and type.
4786 4786
@test fn testResolveInstanceCrossModule() throws (testing::TestError) {
4787 4787
    let mut a = testResolver();
4788 4788
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
4789 4789
4790 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "pub mod defs; pub mod impls; mod app;", &mut arena);
4791 -
    let defsId = try registerModule(&mut MODULE_GRAPH, rootId, "defs", "pub record Counter { value: i32 } pub trait Adder { fn (*mut Adder) add(n: i32) -> i32; }", &mut arena);
4790 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod defs; export mod impls; mod app;", &mut arena);
4791 +
    let defsId = try registerModule(&mut MODULE_GRAPH, rootId, "defs", "export record Counter { value: i32 } export trait Adder { fn (*mut Adder) add(n: i32) -> i32; }", &mut arena);
4792 4792
    let implsId = try registerModule(&mut MODULE_GRAPH, rootId, "impls", "use root::defs; instance defs::Adder for defs::Counter { fn (c: *mut defs::Counter) add(n: i32) -> i32 { c.value = c.value + n; return c.value; } }", &mut arena);
4793 4793
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::defs; fn test() -> i32 { let mut c = defs::Counter { value: 10 }; let a: *mut opaque defs::Adder = &mut c; return a.add(5); }", &mut arena);
4794 4794
4795 4795
    let result = try resolveModuleTree(&mut a, rootId);
4796 4796
    try expectNoErrors(&result);
4976 4976
/// a constant from another module via scope access.
4977 4977
@test fn testCrossModuleConstExpr() throws (testing::TestError) {
4978 4978
    let mut a = testResolver();
4979 4979
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
4980 4980
4981 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "pub mod consts; mod app;", &mut arena);
4982 -
    let constsId = try registerModule(&mut MODULE_GRAPH, rootId, "consts", "pub const BASE: i32 = 100;", &mut arena);
4981 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod consts; mod app;", &mut arena);
4982 +
    let constsId = try registerModule(&mut MODULE_GRAPH, rootId, "consts", "export const BASE: i32 = 100;", &mut arena);
4983 4983
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::consts; const DERIVED: i32 = consts::BASE + 50;", &mut arena);
4984 4984
4985 4985
    let result = try resolveModuleTree(&mut a, rootId);
4986 4986
    try expectNoErrors(&result);
4987 4987
}
4989 4989
/// Test cross-module constant expression used as array size.
4990 4990
@test fn testCrossModuleConstExprArraySize() throws (testing::TestError) {
4991 4991
    let mut a = testResolver();
4992 4992
    let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
4993 4993
4994 -
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "pub mod consts; mod app;", &mut arena);
4995 -
    let constsId = try registerModule(&mut MODULE_GRAPH, rootId, "consts", "pub const WIDTH: u32 = 8; pub const HEIGHT: u32 = 4;", &mut arena);
4994 +
    let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod consts; mod app;", &mut arena);
4995 +
    let constsId = try registerModule(&mut MODULE_GRAPH, rootId, "consts", "export const WIDTH: u32 = 8; export const HEIGHT: u32 = 4;", &mut arena);
4996 4996
    let appId = try registerModule(&mut MODULE_GRAPH, rootId, "app", "use root::consts; const TOTAL: u32 = consts::WIDTH * consts::HEIGHT; static BUF: [u8; TOTAL] = undefined;", &mut arena);
4997 4997
4998 4998
    let result = try resolveModuleTree(&mut a, rootId);
4999 4999
    try expectNoErrors(&result);
5000 5000
}
lib/std/lang/scanner.rad +13 -14
9 9
10 10
/// Token kinds representing all lexical elements in Radiance.
11 11
///
12 12
/// This enum covers operators, keywords, literals, and structural
13 13
/// elements used by the parser to build the AST.
14 -
pub union TokenKind {
14 +
export union TokenKind {
15 15
    /// Special end of file token generated when the input is exhausted.
16 16
    Eof,
17 17
    /// Special invalid token.
18 18
    Invalid,
19 19
95 95
96 96
    // Module-related tokens.
97 97
    Mod, Use, Super,
98 98
99 99
    // Type or function attributes.
100 -
    Pub, Export, Static,
100 +
    Export, Static,
101 101
102 102
    // Trait-related tokens.
103 103
    Trait, Instance,
104 104
105 105
    // Type-related tokens.
114 114
    /// Corresponding token.
115 115
    tok: TokenKind,
116 116
}
117 117
118 118
/// Sorted keyword table for binary search.
119 -
const KEYWORDS: [Keyword; 52] = [
119 +
const KEYWORDS: [Keyword; 51] = [
120 120
    { name: "align", tok: TokenKind::Align },
121 121
    { name: "and", tok: TokenKind::And },
122 122
    { name: "as", tok: TokenKind::As },
123 123
    { name: "assert", tok: TokenKind::Assert },
124 124
    { name: "bool", tok: TokenKind::Bool },
149 149
    { name: "nil", tok: TokenKind::Nil },
150 150
    { name: "not", tok: TokenKind::Not },
151 151
    { name: "opaque", tok: TokenKind::Opaque },
152 152
    { name: "or", tok: TokenKind::Or },
153 153
    { name: "panic", tok: TokenKind::Panic },
154 -
    { name: "pub", tok: TokenKind::Pub },
155 154
    { name: "record", tok: TokenKind::Record },
156 155
    { name: "return", tok: TokenKind::Return },
157 156
    { name: "static", tok: TokenKind::Static },
158 157
    { name: "super", tok: TokenKind::Super },
159 158
    { name: "throw", tok: TokenKind::Throw },
170 169
    { name: "use", tok: TokenKind::Use },
171 170
    { name: "while", tok: TokenKind::While },
172 171
];
173 172
174 173
/// Describes where source code originated from.
175 -
pub union SourceLoc {
174 +
export union SourceLoc {
176 175
    /// Source loaded from a file at the given path.
177 176
    File(*[u8]),
178 177
    /// Source provided as an inline string (no file path).
179 178
    String,
180 179
}
181 180
182 181
/// Lexical scanner state for tokenizing Radiance source code.
183 182
///
184 183
/// Maintains position information and source buffer reference.
185 -
pub record Scanner {
184 +
export record Scanner {
186 185
    /// Origin of the source being scanned.
187 186
    sourceLoc: SourceLoc,
188 187
    /// Source buffer.
189 188
    source: *[u8],
190 189
    /// Offset of current token into buffer.
197 196
198 197
/// Individual token with kind, source text, and position.
199 198
///
200 199
/// Represents a single lexical element extracted from source,
201 200
/// including its original text and byte offset for error reporting.
202 -
pub record Token {
201 +
export record Token {
203 202
    /// Token kind.
204 203
    kind: TokenKind,
205 204
    /// Token source string.
206 205
    source: *[u8],
207 206
    /// Byte offset of `source` in input buffer.
209 208
}
210 209
211 210
/// Source code location with line/column information.
212 211
///
213 212
/// Used for error reporting and debugging.
214 -
pub record Location {
213 +
export record Location {
215 214
    /// Origin of the source.
216 215
    source: SourceLoc,
217 216
    /// Line number.
218 217
    line: u16,
219 218
    /// Column number.
220 219
    col: u16,
221 220
}
222 221
223 222
/// Create a new scanner object.
224 -
pub fn scanner(sourceLoc: SourceLoc, source: *[u8], pool: *mut strings::Pool) -> Scanner {
223 +
export fn scanner(sourceLoc: SourceLoc, source: *[u8], pool: *mut strings::Pool) -> Scanner {
225 224
    // Intern built-in functions and attributes.
226 225
    strings::intern(pool, "@sizeOf");
227 226
    strings::intern(pool, "@alignOf");
228 227
    strings::intern(pool, "@sliceOf");
229 228
    strings::intern(pool, "@default");
235 234
236 235
    return Scanner { sourceLoc, source, token: 0, cursor: 0, pool };
237 236
}
238 237
239 238
/// Check if we've reached the end of input.
240 -
pub fn isEof(s: *Scanner) -> bool {
239 +
export fn isEof(s: *Scanner) -> bool {
241 240
    return s.cursor >= s.source.len;
242 241
}
243 242
244 243
/// Get the current character, if any.
245 -
pub fn current(s: *Scanner) -> ?u8 {
244 +
export fn current(s: *Scanner) -> ?u8 {
246 245
    if isEof(s) {
247 246
        return nil;
248 247
    }
249 248
    return s.source[s.cursor];
250 249
}
276 275
fn tok(s: *Scanner, kind: TokenKind) -> Token {
277 276
    return Token { kind, source: &s.source[s.token..s.cursor], offset: s.token };
278 277
}
279 278
280 279
/// Create an invalid token with the given message.
281 -
pub fn invalid(offset: u32, message: *[u8]) -> Token {
280 +
export fn invalid(offset: u32, message: *[u8]) -> Token {
282 281
    return Token { kind: TokenKind::Invalid, source: message, offset };
283 282
}
284 283
285 284
/// Skip whitespace characters and line comments.
286 285
fn skipWhitespace(s: *mut Scanner) {
442 441
    }
443 442
    return tok(s, kind);
444 443
}
445 444
446 445
/// Scan the next token.
447 -
pub fn next(s: *mut Scanner) -> Token {
446 +
export fn next(s: *mut Scanner) -> Token {
448 447
    skipWhitespace(s);  // Skip any whitespace between tokens.
449 448
    s.token = s.cursor; // Token starts at current position.
450 449
451 450
    if isEof(s) {
452 451
        return tok(s, TokenKind::Eof);
610 609
        else => return invalid(s.token, "unexpected character"),
611 610
    }
612 611
}
613 612
614 613
/// Get the source code location from a byte offset.
615 -
pub fn getLocation(sourceLoc: SourceLoc, source: *[u8], offset: u32) -> ?Location {
614 +
export fn getLocation(sourceLoc: SourceLoc, source: *[u8], offset: u32) -> ?Location {
616 615
    let mut l: u16 = 1;
617 616
    let mut c: u16 = 1;
618 617
619 618
    if offset >= source.len {
620 619
        return nil;
lib/std/lang/sexpr.rad +16 -16
5 5
6 6
use std::io;
7 7
use std::lang::alloc;
8 8
9 9
/// Output target for S-expression printing.
10 -
pub union Output {
10 +
export union Output {
11 11
    /// Print to stdout.
12 12
    Stdout,
13 13
    /// Write to a buffer, tracking position.
14 14
    Buffer { buf: *mut [u8], pos: *mut u32 },
15 15
}
16 16
17 17
/// An S-expression element.
18 -
pub union Expr {
18 +
export union Expr {
19 19
    /// An empty expression.
20 20
    Null,
21 21
    /// A symbol/identifier.
22 22
    Sym(*[u8]),
23 23
    /// A quoted string literal.
31 31
    /// A block with a name, inline items, and child statements on separate lines.
32 32
    Block { name: *[u8], items: *[Expr], children: *[Expr] },
33 33
}
34 34
35 35
/// Allocate an array of Expr in the arena.
36 -
pub fn allocExprs(arena: *mut alloc::Arena, len: u32) -> *mut [Expr] throws (alloc::AllocError) {
36 +
export fn allocExprs(arena: *mut alloc::Arena, len: u32) -> *mut [Expr] throws (alloc::AllocError) {
37 37
    if len == 0 {
38 38
        throw alloc::AllocError::OutOfMemory;
39 39
    }
40 40
    let ptr = try alloc::allocSlice(arena, @sizeOf(Expr), @alignOf(Expr), len);
41 41
    return ptr as *mut [Expr];
42 42
}
43 43
44 44
/// Allocate and copy items into the arena.
45 -
pub fn allocItems(a: *mut alloc::Arena, items: *[Expr]) -> *[Expr] {
45 +
export fn allocItems(a: *mut alloc::Arena, items: *[Expr]) -> *[Expr] {
46 46
    if items.len == 0 {
47 47
        return &[];
48 48
    }
49 49
    let buf = try! allocExprs(a, items.len);
50 50
    for item, i in items {
52 52
    }
53 53
    return buf;
54 54
}
55 55
56 56
/// Shorthand for creating a symbol.
57 -
pub fn sym(s: *[u8]) -> Expr {
57 +
export fn sym(s: *[u8]) -> Expr {
58 58
    return Expr::Sym(s);
59 59
}
60 60
61 61
/// Shorthand for creating a string literal.
62 -
pub fn str(s: *[u8]) -> Expr {
62 +
export fn str(s: *[u8]) -> Expr {
63 63
    return Expr::Str(s);
64 64
}
65 65
66 66
/// Shorthand for creating a list.
67 -
pub fn list(a: *mut alloc::Arena, head: *[u8], tail: *[Expr]) -> Expr {
67 +
export fn list(a: *mut alloc::Arena, head: *[u8], tail: *[Expr]) -> Expr {
68 68
    return Expr::List { head, tail: allocItems(a, tail), multiline: false };
69 69
}
70 70
71 71
/// Shorthand for creating a bracket-delimited vector.
72 -
pub fn vec(a: *mut alloc::Arena, items: *[Expr]) -> Expr {
72 +
export fn vec(a: *mut alloc::Arena, items: *[Expr]) -> Expr {
73 73
    return Expr::Vec { items: allocItems(a, items) };
74 74
}
75 75
76 76
/// Shorthand for creating a block with inline items and child expressions.
77 -
pub fn block(a: *mut alloc::Arena, name: *[u8], items: *[Expr], children: *[Expr]) -> Expr {
77 +
export fn block(a: *mut alloc::Arena, name: *[u8], items: *[Expr], children: *[Expr]) -> Expr {
78 78
    return Expr::Block { name, items: allocItems(a, items), children: allocItems(a, children) };
79 79
}
80 80
81 81
/// Write a string to the output target.
82 -
pub fn write(out: *mut Output, s: *[u8]) {
82 +
export fn write(out: *mut Output, s: *[u8]) {
83 83
    match *out {
84 84
        case Output::Stdout => io::print(s),
85 85
        case Output::Buffer { buf, pos } => {
86 86
            let remaining = buf.len - *pos;
87 87
            let toWrite = remaining if s.len > remaining else s.len;
99 99
        write(out, "  ");
100 100
    }
101 101
}
102 102
103 103
/// Print a single character with escaping to the output target.
104 -
pub fn printEscapedTo(out: *mut Output, c: u8) {
104 +
export fn printEscapedTo(out: *mut Output, c: u8) {
105 105
    match c {
106 106
        case '\n' => write(out, "\\n"),
107 107
        case '\r' => write(out, "\\r"),
108 108
        case '\t' => write(out, "\\t"),
109 109
        case '\\' => write(out, "\\\\"),
111 111
        else => write(out, &[c]),
112 112
    }
113 113
}
114 114
115 115
/// Print a quoted string with escape sequences to the output target.
116 -
pub fn printStringTo(out: *mut Output, s: *[u8]) {
116 +
export fn printStringTo(out: *mut Output, s: *[u8]) {
117 117
    write(out, "\"");
118 118
    for i in 0..s.len {
119 119
        printEscapedTo(out, s[i]);
120 120
    }
121 121
    write(out, "\"");
122 122
}
123 123
124 124
/// Print a character literal with escape sequences to the output target.
125 -
pub fn printCharTo(out: *mut Output, c: u8) {
125 +
export fn printCharTo(out: *mut Output, c: u8) {
126 126
    write(out, "'");
127 127
    printEscapedTo(out, c);
128 128
    write(out, "'");
129 129
}
130 130
131 131
/// Print an S-expression to the given output target at the given depth.
132 -
pub fn printTo(expr: Expr, depth: u32, out: *mut Output) {
132 +
export fn printTo(expr: Expr, depth: u32, out: *mut Output) {
133 133
    match expr {
134 134
        case Expr::Null => {},
135 135
        case Expr::Sym(s) => write(out, s),
136 136
        case Expr::Str(s) => printStringTo(out, s),
137 137
        case Expr::Char(c) => printCharTo(out, c),
193 193
        }
194 194
    }
195 195
}
196 196
197 197
/// Print an S-expression to stdout at the given indentation depth.
198 -
pub fn print(expr: Expr, depth: u32) {
198 +
export fn print(expr: Expr, depth: u32) {
199 199
    let mut out = Output::Stdout;
200 200
    printTo(expr, depth, &mut out);
201 201
}
202 202
203 203
/// Emit indentation for `depth` levels to stdout.
204 -
pub fn indent(depth: u32) {
204 +
export fn indent(depth: u32) {
205 205
    let mut out = Output::Stdout;
206 206
    indentTo(&mut out, depth);
207 207
}
208 208
lib/std/lang/strings.rad +3 -3
14 14
15 15
/// String interning pool using open-addressed hash table.
16 16
///
17 17
/// Each unique string content is stored only once, allowing pointer equality
18 18
/// to be used instead of content comparison for symbol lookups and module names.
19 -
pub record Pool {
19 +
export record Pool {
20 20
    /// Hash table slots.
21 21
    table: [*[u8]; TABLE_SIZE],
22 22
    /// Number of strings currently in the pool.
23 23
    count: u32,
24 24
}
48 48
    }
49 49
}
50 50
51 51
/// Look up a string in the pool.
52 52
/// Returns the canonical interned pointer if the string exists.
53 -
pub fn find(sp: *Pool, str: *[u8]) -> ?*[u8] {
53 +
export fn find(sp: *Pool, str: *[u8]) -> ?*[u8] {
54 54
    match lookup(sp, str) {
55 55
        case Lookup::Found(entry) => return entry,
56 56
        case Lookup::Empty(_) => return nil,
57 57
    }
58 58
}
59 59
60 60
/// Intern a string, returning a canonical pointer for equality comparison.
61 61
///
62 62
/// If the string content already exists in the pool, returns the existing pointer.
63 63
/// Otherwise, adds the string pointer to the pool and returns it.
64 -
pub fn intern(sp: *mut Pool, str: *[u8]) -> *[u8] {
64 +
export fn intern(sp: *mut Pool, str: *[u8]) -> *[u8] {
65 65
    match lookup(sp, str) {
66 66
        case Lookup::Found(entry) => return entry,
67 67
        case Lookup::Empty(idx) => {
68 68
            assert sp.count < TABLE_SIZE / 2, "intern: string pool is full";
69 69
            sp.table[idx] = str;
lib/std/mem.rad +7 -7
3 3
    /// Buffer is too small.
4 4
    BufferTooSmall,
5 5
}
6 6
7 7
/// Copy bytes between two slices. Returns the number of bytes copied.
8 -
pub fn copy(into: *mut [u8], from: *[u8]) -> u32 throws (MemoryError) {
8 +
export fn copy(into: *mut [u8], from: *[u8]) -> u32 throws (MemoryError) {
9 9
    if into.len < from.len {
10 10
        throw MemoryError::BufferTooSmall;
11 11
    }
12 12
    for x, i in from {
13 13
        into[i] = x;
15 15
    return from.len;
16 16
}
17 17
18 18
/// Strip a byte-level prefix from the input, and return the suffix.
19 19
/// Returns `nil` if the prefix wasn't found.
20 -
pub fn stripPrefix(prefix: *[u8], input: *[u8]) -> ?*[u8] {
20 +
export fn stripPrefix(prefix: *[u8], input: *[u8]) -> ?*[u8] {
21 21
    if prefix.len == 0 {
22 22
        return input;
23 23
    }
24 24
    if prefix.len > input.len {
25 25
        return nil;
31 31
    }
32 32
    return &input[prefix.len..];
33 33
}
34 34
35 35
/// Align value up to alignment boundary.
36 -
pub fn alignUp(value: u32, alignment: u32) -> u32 {
36 +
export fn alignUp(value: u32, alignment: u32) -> u32 {
37 37
    return (value + alignment - 1) & ~(alignment - 1);
38 38
}
39 39
40 40
/// Align signed value up to alignment boundary.
41 -
pub fn alignUpI32(value: i32, alignment: i32) -> i32 {
41 +
export fn alignUpI32(value: i32, alignment: i32) -> i32 {
42 42
    return (value + alignment - 1) & ~(alignment - 1);
43 43
}
44 44
45 45
/// Count number of set bits in a 32-bit value.
46 -
pub fn popCount(x: u32) -> i32 {
46 +
export fn popCount(x: u32) -> i32 {
47 47
    let mut n = x;
48 48
    let mut count: i32 = 0;
49 49
    while n != 0 {
50 50
        count += (n & 1) as i32;
51 51
        n >>= 1;
52 52
    }
53 53
    return count;
54 54
}
55 55
56 56
/// Check whether two byte slices have the same length and contents.
57 -
pub fn eq(a: *[u8], b: *[u8]) -> bool {
57 +
export fn eq(a: *[u8], b: *[u8]) -> bool {
58 58
    if a.len != b.len {
59 59
        return false;
60 60
    }
61 61
    for i in 0..a.len {
62 62
        if a[i] != b[i] {
67 67
}
68 68
69 69
/// Compare two byte slices lexicographically.
70 70
///
71 71
/// Returns `-1` when `a < b`, `1` when `a > b`, and `0` when equal.
72 -
pub fn cmp(a: *[u8], b: *[u8]) -> i32 {
72 +
export fn cmp(a: *[u8], b: *[u8]) -> i32 {
73 73
    let aLen = a.len;
74 74
    let bLen = b.len;
75 75
76 76
    let common = bLen if bLen < aLen else aLen;
77 77
    for i in 0..common {
lib/std/sys.rad +2 -2
1 1
//! System utilities.
2 -
pub mod unix;
2 +
export mod unix;
3 3
4 4
/// Process environment passed to the program entry point.
5 -
pub record Env {
5 +
export record Env {
6 6
    /// Command-line arguments.
7 7
    args: *[*[u8]],
8 8
}
lib/std/sys/unix.rad +18 -18
1 1
//! Unix-specific system calls and utilities.
2 2
use std::intrinsics;
3 3
4 4
/// File access modes.
5 -
pub record OpenFlags(i64);
5 +
export record OpenFlags(i64);
6 6
7 7
/// Open file for reading only.
8 -
pub const O_RDONLY: OpenFlags = OpenFlags(0);
8 +
export const O_RDONLY: OpenFlags = OpenFlags(0);
9 9
10 10
/// Open file for writing only.
11 -
pub const O_WRONLY: OpenFlags = OpenFlags(1);
11 +
export const O_WRONLY: OpenFlags = OpenFlags(1);
12 12
13 13
/// Open file for reading and writing.
14 -
pub const O_RDWR: OpenFlags = OpenFlags(2);
14 +
export const O_RDWR: OpenFlags = OpenFlags(2);
15 15
16 16
/// Create file if it doesn't exist.
17 -
pub const O_CREAT: OpenFlags = OpenFlags(64);
17 +
export const O_CREAT: OpenFlags = OpenFlags(64);
18 18
19 19
/// Truncate file to zero length.
20 -
pub const O_TRUNC: OpenFlags = OpenFlags(512);
20 +
export const O_TRUNC: OpenFlags = OpenFlags(512);
21 21
22 22
/// Standard file descriptor for stdin.
23 -
pub const STDIN: i64 = 0;
23 +
export const STDIN: i64 = 0;
24 24
25 25
/// Standard file descriptor for stdout.
26 -
pub const STDOUT: i64 = 1;
26 +
export const STDOUT: i64 = 1;
27 27
28 28
/// Standard file descriptor for stderr.
29 -
pub const STDERR: i64 = 2;
29 +
export const STDERR: i64 = 2;
30 30
31 31
/// Special value representing current working directory for `openat()`.
32 32
const AT_FDCWD: i64 = -100;
33 33
34 34
/// Opens a file at the given path and returns a file descriptor.
35 35
/// Returns a negative value on error.
36 -
pub fn open(path: *[u8], flags: OpenFlags) -> i64 {
36 +
export fn open(path: *[u8], flags: OpenFlags) -> i64 {
37 37
    return intrinsics::ecall(56, AT_FDCWD, path.ptr as i64, *flags, 0);
38 38
}
39 39
40 40
/// Opens a file at the given path with mode, returns a file descriptor.
41 -
pub fn openOpts(path: *[u8], flags: OpenFlags, mode: i64) -> i64 {
41 +
export fn openOpts(path: *[u8], flags: OpenFlags, mode: i64) -> i64 {
42 42
    return intrinsics::ecall(56, AT_FDCWD, path.ptr as i64, *flags, mode);
43 43
}
44 44
45 45
/// Reads from a file descriptor into the provided buffer.
46 46
/// Returns the number of bytes read, or a negative value on error.
47 -
pub fn read(fd: i64, buf: *mut [u8]) -> i64 {
47 +
export fn read(fd: i64, buf: *mut [u8]) -> i64 {
48 48
    return intrinsics::ecall(63, fd, buf.ptr as i64, buf.len as i64, 0);
49 49
}
50 50
51 51
/// Reads from a file descriptor until EOF or buffer is full.
52 52
/// Returns the total number of bytes read, or a negative value on error.
53 -
pub fn readToEnd(fd: i64, buf: *mut [u8]) -> i64 {
53 +
export fn readToEnd(fd: i64, buf: *mut [u8]) -> i64 {
54 54
    let mut total: u32 = 0;
55 55
    while total < buf.len {
56 56
        let chunk = &mut buf[total..];
57 57
        let n = read(fd, chunk);
58 58
67 67
    return total as i64;
68 68
}
69 69
70 70
/// Writes to a file descriptor from the provided buffer.
71 71
/// Returns the number of bytes written, or a negative value on error.
72 -
pub fn write(fd: i64, buf: *[u8]) -> i64 {
72 +
export fn write(fd: i64, buf: *[u8]) -> i64 {
73 73
    return intrinsics::ecall(64, fd, buf.ptr as i64, buf.len as i64, 0);
74 74
}
75 75
76 76
/// Closes a file descriptor.
77 77
/// Returns `0` on success, or a negative value on error.
78 -
pub fn close(fd: i64) -> i64 {
78 +
export fn close(fd: i64) -> i64 {
79 79
    return intrinsics::ecall(57, fd, 0, 0, 0);
80 80
}
81 81
82 82
/// Reads the entire contents of a file at the given path into the provided buffer.
83 83
/// Returns a slice containing the data read, or `nil` on error.
84 -
pub fn readFile(path: *[u8], buf: *mut [u8]) -> ?*[u8] {
84 +
export fn readFile(path: *[u8], buf: *mut [u8]) -> ?*[u8] {
85 85
    let fd = open(path, O_RDONLY);
86 86
    if fd < 0 {
87 87
        return nil;
88 88
    }
89 89
    let n = readToEnd(fd, buf);
96 96
    }
97 97
    return &buf[..n as u32];
98 98
}
99 99
100 100
/// Exit the current process with the given status code.
101 -
pub fn exit(status: i64) {
101 +
export fn exit(status: i64) {
102 102
    intrinsics::ecall(93, status, 0, 0, 0);
103 103
}
104 104
105 105
/// Writes the entire contents of a buffer to a file at the given path.
106 106
/// Creates the file if it doesn't exist, truncates if it does.
107 107
/// Returns `true` on success.
108 -
pub fn writeFile(path: *[u8], data: *[u8]) -> bool {
108 +
export fn writeFile(path: *[u8], data: *[u8]) -> bool {
109 109
    let flags = OpenFlags(*O_WRONLY | *O_CREAT | *O_TRUNC);
110 110
    let fd = openOpts(path, flags, 420); // 0644 in octal.
111 111
    if fd < 0 {
112 112
        return false;
113 113
    }
lib/std/testing.rad +7 -7
8 8
    passed: u32,
9 9
    failed: u32,
10 10
}
11 11
12 12
/// Error thrown when a test assertion fails.
13 -
pub union TestError {
13 +
export union TestError {
14 14
    Failed,
15 15
}
16 16
17 17
/// Descriptor for a single test case, holding its module path, name, and entry point.
18 -
pub record TestInfo {
18 +
export record TestInfo {
19 19
    module: *[u8],
20 20
    name: *[u8],
21 21
    func: fn() throws (TestError),
22 22
}
23 23
24 24
/// Construct a [`TestInfo`]. Used by the compiler's synthetic test harness.
25 -
pub fn test(module: *[u8], name: *[u8], func: fn() throws (TestError)) -> TestInfo {
25 +
export fn test(module: *[u8], name: *[u8], func: fn() throws (TestError)) -> TestInfo {
26 26
    return TestInfo { module, name, func };
27 27
}
28 28
29 29
/// Run all tests and return `0` on success or `1` if any test failed.
30 -
pub fn runAllTests(tests: *[TestInfo]) -> i32 {
30 +
export fn runAllTests(tests: *[TestInfo]) -> i32 {
31 31
    let mut ctx: Ctx = Ctx { passed: 0, failed: 0 };
32 32
33 33
    io::print("Running ");
34 34
    io::printU32(tests.len);
35 35
    io::print(" test(s)...\n\n");
83 83
        io::printLn(" passed; 0 failed");
84 84
    }
85 85
}
86 86
87 87
/// Assert that two byte slices are equal.
88 -
pub fn expectBytesEq(left: *[u8], right: *[u8])
88 +
export fn expectBytesEq(left: *[u8], right: *[u8])
89 89
    throws (TestError)
90 90
{
91 91
    try expect(mem::eq(left, right));
92 92
}
93 93
94 94
/// Assert that a boolean condition is true.
95 -
pub fn expect(cond: bool)
95 +
export fn expect(cond: bool)
96 96
    throws (TestError)
97 97
{
98 98
    if not cond {
99 99
        throw TestError::Failed;
100 100
    }
101 101
}
102 102
103 103
/// Assert that a boolean condition is false.
104 -
pub fn expectNot(cond: bool)
104 +
export fn expectNot(cond: bool)
105 105
    throws (TestError)
106 106
{
107 107
    if cond {
108 108
        throw TestError::Failed;
109 109
    }
lib/std/vec.rad +9 -9
5 5
//! to the element type's requirements.
6 6
7 7
/// Raw vector metadata structure.
8 8
///
9 9
/// Does not own storage, points to user-provided arena.
10 -
pub record RawVec {
10 +
export record RawVec {
11 11
    /// Pointer to user-provided byte arena.
12 12
    data: *mut [u8],
13 13
    /// Current number of elements stored.
14 14
    len: u32,
15 15
    /// Size of each element in bytes (stride between elements).
21 21
/// Create a new raw vector with external arena.
22 22
///
23 23
/// * `arena` is a pointer to static array backing storage.
24 24
/// * `stride` is the size of each element.
25 25
/// * `alignment` is the required alignment for elements.
26 -
pub fn new(arena: *mut [u8], stride: u32, alignment: u32) -> RawVec {
26 +
export fn new(arena: *mut [u8], stride: u32, alignment: u32) -> RawVec {
27 27
    assert stride > 0;
28 28
    assert alignment > 0;
29 29
    assert (arena.ptr as u32) % alignment == 0;
30 30
    assert (arena.len % stride) == 0;
31 31
32 32
    return RawVec { data: arena, len: 0, stride, alignment };
33 33
}
34 34
35 35
/// Get the current number of elements in the vector.
36 -
pub fn len(vec: *RawVec) -> u32 {
36 +
export fn len(vec: *RawVec) -> u32 {
37 37
    return vec.len;
38 38
}
39 39
40 40
/// Get the maximum capacity of the vector.
41 -
pub fn capacity(vec: *RawVec) -> u32 {
41 +
export fn capacity(vec: *RawVec) -> u32 {
42 42
    return vec.data.len / vec.stride;
43 43
}
44 44
45 45
/// Reset the vector to empty (does not clear memory).
46 -
pub fn reset(vec: *mut RawVec) {
46 +
export fn reset(vec: *mut RawVec) {
47 47
    vec.len = 0;
48 48
}
49 49
50 50
/// Get a pointer to the element at the given index.
51 51
///
52 52
/// Returns nil if index is out of bounds.
53 -
pub fn get(vec: *RawVec, index: u32) -> ?*opaque {
53 +
export fn get(vec: *RawVec, index: u32) -> ?*opaque {
54 54
    if index >= vec.len {
55 55
        return nil;
56 56
    }
57 57
    let offset: u32 = index * vec.stride;
58 58
    let ptr: *u8 = &vec.data[offset];
61 61
}
62 62
63 63
/// Push an element onto the end of the vector.
64 64
///
65 65
/// Returns false if the vector is at capacity.
66 -
pub fn push(vec: *mut RawVec, elem: *opaque) -> bool {
66 +
export fn push(vec: *mut RawVec, elem: *opaque) -> bool {
67 67
    if vec.len >= capacity(vec) {
68 68
        return false;
69 69
    }
70 70
    let off: u32 = vec.len * vec.stride;
71 71
    let dst: *mut u8 = &mut vec.data[off];
79 79
80 80
/// Pop an element from the end of the vector.
81 81
///
82 82
/// Copies the element into the provided output pointer.
83 83
/// Returns false if the vector is empty.
84 -
pub fn pop(vec: *mut RawVec, out: *mut opaque) -> bool {
84 +
export fn pop(vec: *mut RawVec, out: *mut opaque) -> bool {
85 85
    if vec.len == 0 {
86 86
        return false;
87 87
    }
88 88
    vec.len -= 1;
89 89
97 97
}
98 98
99 99
/// Set the element at the given index.
100 100
///
101 101
/// Returns false if index is out of bounds.
102 -
pub fn set(vec: *mut RawVec, index: u32, elem: *opaque) -> bool {
102 +
export fn set(vec: *mut RawVec, index: u32, elem: *opaque) -> bool {
103 103
    if index >= vec.len {
104 104
        return false;
105 105
    }
106 106
    let off: u32 = index * vec.stride;
107 107
    let dst: *mut u8 = &mut vec.data[off];
test/tests/edge.cases.2.rad +1 -1
1 1
//! returns: 0
2 2
//! Test scanner peek with optional return.
3 3
4 -
pub record Scanner {
4 +
export record Scanner {
5 5
    source: *[u8],
6 6
}
7 7
8 8
fn peek(s: *Scanner) -> ?u8 {
9 9
    if 0 + 0 >= s.source.len {
test/tests/edge.cases.3.rad +2 -2
1 1
//! returns: 97
2 2
//! Test scanner current character access.
3 3
4 -
pub record Scanner {
4 +
export record Scanner {
5 5
    source: *[u8],
6 6
    cursor: u32,
7 7
}
8 8
9 9
fn isEof(s: *Scanner) -> bool {
10 10
    return s.cursor >= s.source.len;
11 11
}
12 12
13 -
pub fn current(s: *Scanner) -> ?u8 {
13 +
export fn current(s: *Scanner) -> ?u8 {
14 14
    if isEof(s) {
15 15
        return nil;
16 16
    }
17 17
    return s.source[s.cursor];
18 18
}
test/tests/edge.cases.rad +1 -1
1 1
//! returns: 0
2 2
3 -
pub record Scanner {
3 +
export record Scanner {
4 4
    source: *[u8],
5 5
}
6 6
7 7
fn peek(s: *Scanner)  -> i32 {
8 8
    assert 0 + 0 <= s.source.len;
test/tests/method.pub.rad +1 -1
3 3
4 4
record Foo {
5 5
    x: i32,
6 6
}
7 7
8 -
pub fn (f: *Foo) getX() -> i32 {
8 +
export fn (f: *Foo) getX() -> i32 {
9 9
    return f.x;
10 10
}
11 11
12 12
@default fn main() -> i32 {
13 13
    let f = Foo { x: 42 };
test/tests/union.bitfield.rad +7 -7
1 1
//! returns: 42
2 2
3 3
union Attribute {
4 4
    None = 0b0,
5 -
    Pub = 0b10,
5 +
    Export = 0b10,
6 6
    Default = 0b100,
7 7
    Extern = 0b1000,
8 8
    Test = 0b10000,
9 9
}
10 10
11 11
fn hasFlag(bits: i32, flag: i32) -> bool {
12 12
    return (bits & flag) == flag;
13 13
}
14 14
15 15
fn testCombineDefaults() -> bool {
16 -
    let combined: i32 = (Attribute::Pub as i32) | (Attribute::Default as i32);
16 +
    let combined: i32 = (Attribute::Export as i32) | (Attribute::Default as i32);
17 17
18 -
    return hasFlag(combined, Attribute::Pub as i32) and
18 +
    return hasFlag(combined, Attribute::Export as i32) and
19 19
           hasFlag(combined, Attribute::Default as i32) and
20 20
           not hasFlag(combined, Attribute::Extern as i32);
21 21
}
22 22
23 23
fn testToggleExternFlag() -> bool {
24 -
    let initial: i32 = (Attribute::Pub as i32) | (Attribute::Extern as i32);
24 +
    let initial: i32 = (Attribute::Export as i32) | (Attribute::Extern as i32);
25 25
    let toggled: i32 = initial ^ (Attribute::Extern as i32);
26 26
27 -
    return hasFlag(toggled, Attribute::Pub as i32)
27 +
    return hasFlag(toggled, Attribute::Export as i32)
28 28
        and not hasFlag(toggled, Attribute::Extern as i32);
29 29
}
30 30
31 31
fn testMaskTestFlag() -> bool {
32 -
    let mask: i32 = (Attribute::Pub as i32) | (Attribute::Test as i32);
32 +
    let mask: i32 = (Attribute::Export as i32) | (Attribute::Test as i32);
33 33
    let cleared: i32 = mask & (~(Attribute::Test as i32));
34 34
35 -
    return hasFlag(cleared, Attribute::Pub as i32)
35 +
    return hasFlag(cleared, Attribute::Export as i32)
36 36
        and not hasFlag(cleared, Attribute::Test as i32);
37 37
}
38 38
39 39
@default fn main() -> i32 {
40 40
    if testCombineDefaults()