compiler/
lib/
examples/
std/
arch/
char/
collections/
lang/
alloc/
ast/
gen/
il/
module/
parser/
resolver/
scanner/
alloc.rad
7.1 KiB
ast.rad
26.7 KiB
gen.rad
513 B
il.rad
20.3 KiB
lower.rad
315.0 KiB
module.rad
14.9 KiB
package.rad
1.3 KiB
parser.rad
91.2 KiB
resolver.rad
452.9 KiB
scanner.rad
17.9 KiB
sexpr.rad
6.7 KiB
strings.rad
2.2 KiB
types.rad
1.6 KiB
sys/
arch.rad
68 B
char.rad
855 B
collections.rad
39 B
fmt.rad
8.3 KiB
intrinsics.rad
467 B
io.rad
1.7 KiB
lang.rad
276 B
mem.rad
2.3 KiB
sys.rad
179 B
testing.rad
2.4 KiB
tests.rad
15.7 KiB
vec.rad
3.2 KiB
std.rad
281 B
scripts/
seed/
sublime/
test/
vim/
.gitignore
336 B
.gitsigners
112 B
CONTRIBUTING
2.1 KiB
LICENSE
1.1 KiB
Makefile
5.4 KiB
README
2.5 KiB
STYLE
2.5 KiB
std.lib
1.5 KiB
std.lib.test
662 B
lib/std/lang/il.rad
raw
| 1 | //! Radiance Intermediate Language (RIL). |
| 2 | //! |
| 3 | //! A minimal, low-level, SSA-based intermediate language. |
| 4 | //! |
| 5 | //! > The single-assignment property simplifies reasoning about variables, |
| 6 | //! > since for every value instance its (single) defining assignment is known. |
| 7 | //! > Because every assignment creates a new value name it cannot kill |
| 8 | //! > (i.e., invalidate) expressions previously computed from other values. |
| 9 | //! > In particular, if two expressions are textually the same, they are sure |
| 10 | //! > to evaluate the same result. |
| 11 | //! > |
| 12 | //! > -- "Single-Pass Generation of Static Single-Assignment Form for |
| 13 | //! > Structured Languages", by Marc M. Brandis and Hanspeter Mossenbock |
| 14 | //! |
| 15 | //! # Type System |
| 16 | //! |
| 17 | //! Primitive types: `W8`, `W16`, `W32` (word sizes). |
| 18 | //! Signedness is encoded in operations (e.g., `Sdiv` vs `Udiv`), not types. |
| 19 | //! |
| 20 | //! Types on arithmetic operations indicate logical width but don't |
| 21 | //! change code generation. Arithmetic is always machine word sized, |
| 22 | //! no 8-bit or 16-bit add instruction. |
| 23 | //! |
| 24 | //! # Truncation and Narrowing |
| 25 | //! |
| 26 | //! There is no truncation instruction. |
| 27 | //! * Narrow values are stored in word-sized registers with undefined high bits. |
| 28 | //! * Truncation happens implicitly via narrow store instructions. |
| 29 | //! * To normalize a narrow value, mask explicitly via a bitwise `and`. |
| 30 | //! |
| 31 | //! # Widerning |
| 32 | //! |
| 33 | //! `Zext`/`Sext` widen values when loading from memory. |
| 34 | //! |
| 35 | //! # Aggregate Types |
| 36 | //! |
| 37 | //! The IL provides no aggregate types. Records, slices, optionals, and unions |
| 38 | //! are represented as bytes in memory with explicit layout computed during |
| 39 | //! lowering. Access is via explicit address arithmetic plus load/store/blit. |
| 40 | //! |
| 41 | //! # Lowering Strategy |
| 42 | //! |
| 43 | //! * Slices lower to a pointer+length layout in memory. |
| 44 | //! * Optionals lower to a tag+value layout in memory. |
| 45 | //! * Tagged unions lower to a tag+payload layout in memory. |
| 46 | //! * Results lower to a tag+payload layout in memory. |
| 47 | //! |
| 48 | //! Field access is expressed as address arithmetic plus load/store. |
| 49 | //! |
| 50 | //! Bounds check elimination and nullable pointer optimization happen during |
| 51 | //! this lowering phase. |
| 52 | //! |
| 53 | //! # Control Flow |
| 54 | //! |
| 55 | //! Uses block parameters instead of phi nodes (like MLIR/SIL). Each block |
| 56 | //! can declare parameters, and jumps/branches pass arguments to their targets. |
| 57 | |
| 58 | // TODO: Labels should have their own type. |
| 59 | // TODO: Blocks should have an instruction in `Instr`. |
| 60 | |
| 61 | export mod printer; |
| 62 | export mod binary; |
| 63 | @test mod tests; |
| 64 | |
| 65 | use std::lang::alloc; |
| 66 | |
| 67 | /// Source location for debug info. |
| 68 | /// |
| 69 | /// Associates an IL instruction with its originating source module and |
| 70 | /// byte offset. |
| 71 | export record SrcLoc: Copy { |
| 72 | /// Module identifier. |
| 73 | moduleId: u16, |
| 74 | /// Byte offset into the module's source file. |
| 75 | offset: u32, |
| 76 | } |
| 77 | |
| 78 | /////////////////////// |
| 79 | // Name Formatting // |
| 80 | /////////////////////// |
| 81 | |
| 82 | /// Separator for qualified symbol names. |
| 83 | export constant PATH_SEPARATOR: *[u8] = "::"; |
| 84 | |
| 85 | /// Format a qualified symbol name: `pkg::mod::path::name`. |
| 86 | export unsafe fn formatQualifiedName(arena: &mut alloc::Arena, path: &[*[u8]], name: *[u8]) -> *[u8] { |
| 87 | return qualifiedName(path, name, alloc::arenaAllocator(arena)); |
| 88 | } |
| 89 | |
| 90 | /// Assemble qualified name bytes in owned storage. |
| 91 | fn qualifiedName(path: &[*[u8]], name: &[u8], allocator: alloc::Allocator) -> *[u8] { |
| 92 | let mut buf: *mut [u8] = &mut []; |
| 93 | for segment in path { |
| 94 | for byte in segment { |
| 95 | buf.append(byte, allocator); |
| 96 | } |
| 97 | for byte in PATH_SEPARATOR { |
| 98 | buf.append(byte, allocator); |
| 99 | } |
| 100 | } |
| 101 | for byte in name { |
| 102 | buf.append(byte, allocator); |
| 103 | } |
| 104 | return buf; |
| 105 | } |
| 106 | |
| 107 | /////////// |
| 108 | // Types // |
| 109 | /////////// |
| 110 | |
| 111 | /// IL type representation. Integer signedness is encoded in operations, not types. |
| 112 | export union Type: Copy { |
| 113 | /// 8-bit value. |
| 114 | W8, |
| 115 | /// 16-bit value. |
| 116 | W16, |
| 117 | /// 32-bit value. |
| 118 | W32, |
| 119 | /// 64-bit value (used for pointers on RV64). |
| 120 | W64, |
| 121 | } |
| 122 | |
| 123 | /// Get the size of a type in bytes. |
| 124 | export fn typeSize(t: Type) -> u32 { |
| 125 | match t { |
| 126 | case Type::W8 => return 1, |
| 127 | case Type::W16 => return 2, |
| 128 | case Type::W32 => return 4, |
| 129 | case Type::W64 => return 8, |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | /// SSA register reference. |
| 134 | export record Reg: Copy { n: u32 } |
| 135 | |
| 136 | /// Instruction value. |
| 137 | export union Val: Copy { |
| 138 | /// Register reference. |
| 139 | Reg(Reg), |
| 140 | /// Immediate integer value. |
| 141 | Imm(i64), |
| 142 | /// Data symbol address (globals, constants, string literals). |
| 143 | DataSym(*[u8]), |
| 144 | /// Function address by symbol name. Used directly in call instructions |
| 145 | /// for direct calls, or stored first for indirect calls. |
| 146 | FnAddr(*[u8]), |
| 147 | /// Undefined value. Used when the value is to be discarded, |
| 148 | /// eg. in void returns. |
| 149 | Undef, |
| 150 | } |
| 151 | |
| 152 | /// Comparison operation for compare-and-branch. |
| 153 | export union CmpOp: Copy { Eq, Ne, Slt, Ult } |
| 154 | |
| 155 | /// Binary ALU operation kind. |
| 156 | export union BinOp: Copy { |
| 157 | // Arithmetic. |
| 158 | Add, Sub, Mul, Sdiv, Udiv, Srem, Urem, |
| 159 | // Comparison. |
| 160 | Eq, Ne, Slt, Sge, Ult, Uge, |
| 161 | // Bitwise. |
| 162 | And, Or, Xor, Shl, Sshr, Ushr, |
| 163 | } |
| 164 | |
| 165 | /// Unary ALU operation kind. |
| 166 | export union UnOp: Copy { |
| 167 | Neg, Not, |
| 168 | } |
| 169 | |
| 170 | ////////////////// |
| 171 | // Instructions // |
| 172 | ////////////////// |
| 173 | |
| 174 | /// Block parameter. |
| 175 | export record Param: Copy { |
| 176 | /// SSA register. |
| 177 | value: Reg, |
| 178 | /// Parameter type. |
| 179 | type: Type, |
| 180 | } |
| 181 | |
| 182 | /// Switch case mapping a constant value to a branch target. |
| 183 | export record SwitchCase: Copy { |
| 184 | /// The constant value to match against. |
| 185 | value: i64, |
| 186 | /// The target block index. |
| 187 | target: u32, |
| 188 | /// Arguments to pass to the target block. |
| 189 | args: *unsafe mut [Val], |
| 190 | } |
| 191 | |
| 192 | /// IL instruction. |
| 193 | /// SSA registers are represented as `Reg`, values as `Val`. |
| 194 | export union Instr: Copy { |
| 195 | /////////////////////// |
| 196 | // Memory operations // |
| 197 | /////////////////////// |
| 198 | |
| 199 | /// Allocate space on the stack: `reserve %dst <size> <alignment>;` |
| 200 | /// Size can be a register for dynamic stack allocation (eg. VLAs). |
| 201 | Reserve { dst: Reg, size: Val, alignment: u32 }, |
| 202 | /// Load a value from memory (zero-extending): `load <type> %dst %src <offset>;` |
| 203 | Load { typ: Type, dst: Reg, src: Reg, offset: i32 }, |
| 204 | /// Signed load from memory (sign-extending): `sload <type> %dst %src <offset>;` |
| 205 | Sload { typ: Type, dst: Reg, src: Reg, offset: i32 }, |
| 206 | /// Store a value to memory: `store <type> <src> %dst <offset>;` |
| 207 | Store { |
| 208 | /// Type being stored. |
| 209 | typ: Type, |
| 210 | /// Value being stored. |
| 211 | src: Val, |
| 212 | /// Destination address. |
| 213 | dst: Reg, |
| 214 | /// Byte offset from the base address. |
| 215 | offset: i32 |
| 216 | }, |
| 217 | /// Copy memory region: `blit %dst %src <size>;` |
| 218 | /// Size can be an immediate or a register (eg. for generics). |
| 219 | Blit { |
| 220 | /// Destination address. |
| 221 | dst: Reg, |
| 222 | /// Source address. |
| 223 | src: Reg, |
| 224 | /// Size to copy in bytes. |
| 225 | size: Val |
| 226 | }, |
| 227 | /// Copy a value into a register: `copy %dst <val>;`. |
| 228 | Copy { dst: Reg, val: Val }, |
| 229 | |
| 230 | ///////////////////// |
| 231 | // ALU operations // |
| 232 | ///////////////////// |
| 233 | |
| 234 | /// Binary ALU operation: `<op> <type> %dst <a> <b>;` |
| 235 | BinOp { op: BinOp, typ: Type, dst: Reg, a: Val, b: Val }, |
| 236 | /// Unary ALU operation: `<op> <type> %dst <a>;` |
| 237 | UnOp { op: UnOp, typ: Type, dst: Reg, a: Val }, |
| 238 | |
| 239 | ///////////////// |
| 240 | // Conversions // |
| 241 | ///////////////// |
| 242 | |
| 243 | /// Zero-extend from narrower type to word: `zext <type> %dst <val>;` |
| 244 | Zext { |
| 245 | /// Source type (I8 or I16). |
| 246 | typ: Type, |
| 247 | /// Destination register, always I32. |
| 248 | dst: Reg, |
| 249 | /// Source value. |
| 250 | val: Val, |
| 251 | }, |
| 252 | /// Sign-extend from narrower type to word: `sext <type> %dst <val>;` |
| 253 | Sext { |
| 254 | /// Source type (I8 or I16). |
| 255 | typ: Type, |
| 256 | /// Destination register, always I32. |
| 257 | dst: Reg, |
| 258 | /// Source value. |
| 259 | val: Val, |
| 260 | }, |
| 261 | |
| 262 | //////////////////// |
| 263 | // Function calls // |
| 264 | //////////////////// |
| 265 | |
| 266 | /// Call a function: `call <type> %dst $func(<arg>...);` |
| 267 | Call { |
| 268 | /// Return type. |
| 269 | retTy: Type, |
| 270 | /// Holds return value, for non-void function. |
| 271 | dst: ?Reg, |
| 272 | /// Function value, either a symbol or an address in a register. |
| 273 | func: Val, |
| 274 | /// Function arguments. |
| 275 | args: *unsafe [Val] |
| 276 | }, |
| 277 | |
| 278 | ///////////////// |
| 279 | // Terminators // |
| 280 | ///////////////// |
| 281 | |
| 282 | /// Return from function: `ret <val>;` or `ret;` |
| 283 | Ret { val: ?Val }, |
| 284 | /// Unconditional jump: `jmp @block(<arg>...);` |
| 285 | Jmp { target: u32, args: *unsafe mut [Val] }, |
| 286 | /// Compare-and-branch: `br.<op> <type> <a> <b> @then @else;` |
| 287 | Br { op: CmpOp, typ: Type, a: Val, b: Val, thenTarget: u32, thenArgs: *unsafe mut [Val], elseTarget: u32, elseArgs: *unsafe mut [Val] }, |
| 288 | /// Multi-way branch: `switch <val> (<n> @block) ... @default;` |
| 289 | Switch { val: Val, defaultTarget: u32, defaultArgs: *unsafe mut [Val], cases: *unsafe mut [SwitchCase] }, |
| 290 | /// Unreachable code marker: `unreachable;` |
| 291 | /// Indicates control flow cannot reach this point to allow for optimizations. |
| 292 | Unreachable, |
| 293 | |
| 294 | ///////////////// |
| 295 | // Intrinsics // |
| 296 | ///////////////// |
| 297 | |
| 298 | /// Environment call: `ecall %dst <num> <a0> <a1> <a2> <a3>;` |
| 299 | Ecall { dst: Reg, num: Val, a0: Val, a1: Val, a2: Val, a3: Val }, |
| 300 | /// Environment break: `ebreak;`. |
| 301 | /// Triggers a breakpoint exception for debugging. |
| 302 | Ebreak, |
| 303 | /// Full acquire/release memory fence. |
| 304 | MemoryFence, |
| 305 | /// Read one register through a live Device handle. |
| 306 | DeviceRead { |
| 307 | /// Register access width. |
| 308 | typ: Type, |
| 309 | /// Destination for the zero-extended register value. |
| 310 | dst: Reg, |
| 311 | /// Domain-relative Device capability. |
| 312 | handle: Val, |
| 313 | /// Byte offset within the device region. |
| 314 | offset: Val, |
| 315 | }, |
| 316 | /// Write one register through a live Device handle. |
| 317 | DeviceWrite { |
| 318 | /// Register access width. |
| 319 | typ: Type, |
| 320 | /// Domain-relative Device capability. |
| 321 | handle: Val, |
| 322 | /// Byte offset within the device region. |
| 323 | offset: Val, |
| 324 | /// Value whose low bits are written at the selected width. |
| 325 | value: Val, |
| 326 | }, |
| 327 | } |
| 328 | |
| 329 | ////////////////////////// |
| 330 | // Blocks and Functions // |
| 331 | ////////////////////////// |
| 332 | |
| 333 | /// A basic block in a function. |
| 334 | /// |
| 335 | /// Basic blocks are instruction sequences with a single entry point and no branch |
| 336 | /// instruction, except possibly at the end of the sequence, where a terminator |
| 337 | /// may be found, ie. an instruction that terminates the sequence by jumping |
| 338 | /// to another sequence. |
| 339 | export record Block: Copy { |
| 340 | /// Block label. |
| 341 | label: *[u8], |
| 342 | /// Block parameters. |
| 343 | params: *unsafe [Param], |
| 344 | /// Instructions in the block. The last instruction must be a terminator. |
| 345 | instrs: *unsafe mut [Instr], |
| 346 | /// Source locations for debugging and error reporting. |
| 347 | /// One entry per instruction when debug info is enabled, empty otherwise. |
| 348 | locs: *unsafe [SrcLoc], |
| 349 | /// Predecessor block indices. Used for control flow analysis. |
| 350 | preds: *unsafe [u32], |
| 351 | /// Loop nesting depth. |
| 352 | /// Used for spill cost weighting in register allocation. |
| 353 | loopDepth: u32, |
| 354 | } |
| 355 | |
| 356 | /// An IL function. |
| 357 | export record Fn: Copy { |
| 358 | /// Qualified function name (e.g. `$mod$path$func`). |
| 359 | name: *[u8], |
| 360 | /// Function parameters. |
| 361 | params: *unsafe [Param], |
| 362 | /// Return type. |
| 363 | returnType: Type, |
| 364 | /// Whether the function is extern (no body). |
| 365 | isExtern: bool, |
| 366 | /// Whether the function is a leaf (contains no call or ecall instructions). |
| 367 | isLeaf: bool, |
| 368 | /// Basic blocks. Empty for extern functions. |
| 369 | blocks: *unsafe [Block], |
| 370 | } |
| 371 | |
| 372 | ///////////// |
| 373 | // Program // |
| 374 | ///////////// |
| 375 | |
| 376 | /// Data initializer item. |
| 377 | export union DataItem: Copy { |
| 378 | /// Typed value: `w32 42;` or `w8 255;` |
| 379 | Val { typ: Type, val: i64 }, |
| 380 | /// Symbol reference: `$symbol` |
| 381 | Sym(*[u8]), |
| 382 | /// Function reference: `$fnName` |
| 383 | Fn(*[u8]), |
| 384 | /// String literal bytes: `"hello"` (no auto null-terminator) |
| 385 | Str(*[u8]), |
| 386 | /// Undefined value. Used for padding and `void` returns. |
| 387 | Undef, |
| 388 | } |
| 389 | |
| 390 | /// Data initializer value with optional repeat count. |
| 391 | export record DataValue: Copy { |
| 392 | /// The item contained in the value. |
| 393 | item: DataItem, |
| 394 | /// The number of times the item should be repeated. |
| 395 | count: u32, |
| 396 | } |
| 397 | |
| 398 | /// Global data definition. |
| 399 | export record Data: Copy { |
| 400 | /// Data name. |
| 401 | name: *[u8], |
| 402 | /// Size in bytes. |
| 403 | size: u32, |
| 404 | /// Alignment requirement. |
| 405 | alignment: u32, |
| 406 | /// Whether this is read-only data. |
| 407 | /// Typically, `constant` declaration are read-only, while `static` |
| 408 | /// declarations are not. |
| 409 | readOnly: bool, |
| 410 | /// Whether writable data can be represented by zero-filled memory. |
| 411 | /// Zero-initialized data doesn't need to be written to the sidecar image. |
| 412 | isZeroInit: bool, |
| 413 | /// Initializer values. |
| 414 | values: *[DataValue], |
| 415 | } |
| 416 | |
| 417 | /// An IL program (compilation unit). |
| 418 | export record Program: Copy { |
| 419 | /// Immutable global data. |
| 420 | data: *[Data], |
| 421 | /// Functions. |
| 422 | fns: *unsafe [*unsafe Fn], |
| 423 | } |
| 424 | |
| 425 | /////////////////////// |
| 426 | // Utility Functions // |
| 427 | /////////////////////// |
| 428 | |
| 429 | /// Compiler-generated metadata call for one MMIO access. |
| 430 | /// a0 is the handle, a1 the offset, and a2 the byte width plus bit 8 for a write. |
| 431 | /// a3 holds a write value and is preserved. Success returns the address in a0. |
| 432 | /// Any failure terminates the caller before the user-mode register access. |
| 433 | export constant DEVICE_ACCESS: u32 = 74; |
| 434 | |
| 435 | /// Get the destination register of an instruction, if any. |
| 436 | export fn instrDst(instr: Instr) -> ?Reg { |
| 437 | match instr { |
| 438 | case Instr::Reserve { dst, .. } => return dst, |
| 439 | case Instr::Load { dst, .. } => return dst, |
| 440 | case Instr::Sload { dst, .. } => return dst, |
| 441 | case Instr::Copy { dst, .. } => return dst, |
| 442 | case Instr::BinOp { dst, .. } => return dst, |
| 443 | case Instr::UnOp { dst, .. } => return dst, |
| 444 | case Instr::Zext { dst, .. } => return dst, |
| 445 | case Instr::Sext { dst, .. } => return dst, |
| 446 | case Instr::Call { dst, .. } => return dst, |
| 447 | case Instr::Ecall { dst, .. } => return dst, |
| 448 | case Instr::DeviceRead { dst, .. } => return dst, |
| 449 | else => return nil, |
| 450 | } |
| 451 | } |
| 452 | |
| 453 | /// Check if an instruction is a function call. |
| 454 | export fn isCall(instr: Instr) -> bool { |
| 455 | match instr { |
| 456 | case Instr::Call { .. }, |
| 457 | Instr::Ecall { .. }, Instr::DeviceRead { .. }, Instr::DeviceWrite { .. } => return true, |
| 458 | else => return false, |
| 459 | } |
| 460 | } |
| 461 | |
| 462 | /// Position within an instruction's source operands. |
| 463 | export record RegCursor: Copy { |
| 464 | /// Registers in the instruction's fixed operands, in operand order. |
| 465 | fixed: [Reg; 5], |
| 466 | /// Number of initialized fixed registers. |
| 467 | count: u32, |
| 468 | /// Next fixed register. |
| 469 | next: u32, |
| 470 | /// Next argument in the current argument group. |
| 471 | argument: u32, |
| 472 | /// Argument group: zero for the first group, then one per switch case. |
| 473 | branch: u32, |
| 474 | /// Whether the instruction has variable argument groups left to scan. |
| 475 | arguments: bool, |
| 476 | } |
| 477 | |
| 478 | /// Start a source-register scan in instruction operand order. |
| 479 | export fn registers(instr: &Instr) -> RegCursor { |
| 480 | let mut cursor = RegCursor { fixed: [Reg { n: 0 }; 5], count: 0, next: 0, argument: 0, branch: 0, arguments: false }; |
| 481 | match *instr { |
| 482 | case Instr::Reserve { size, .. } => addOperand(&mut cursor, size), |
| 483 | case Instr::Load { src, .. } => addOperand(&mut cursor, Val::Reg(src)), |
| 484 | case Instr::Sload { src, .. } => addOperand(&mut cursor, Val::Reg(src)), |
| 485 | case Instr::Store { src, dst, .. } => { |
| 486 | addOperand(&mut cursor, src); |
| 487 | addOperand(&mut cursor, Val::Reg(dst)); |
| 488 | } |
| 489 | case Instr::Blit { dst, src, size } => { |
| 490 | addOperand(&mut cursor, Val::Reg(dst)); |
| 491 | addOperand(&mut cursor, Val::Reg(src)); |
| 492 | addOperand(&mut cursor, size); |
| 493 | } |
| 494 | case Instr::Copy { val, .. } => addOperand(&mut cursor, val), |
| 495 | case Instr::BinOp { a, b, .. } => { |
| 496 | addOperand(&mut cursor, a); |
| 497 | addOperand(&mut cursor, b); |
| 498 | } |
| 499 | case Instr::UnOp { a, .. } => addOperand(&mut cursor, a), |
| 500 | case Instr::Zext { val, .. } => addOperand(&mut cursor, val), |
| 501 | case Instr::Sext { val, .. } => addOperand(&mut cursor, val), |
| 502 | case Instr::Call { func, .. } => { |
| 503 | addOperand(&mut cursor, func); |
| 504 | set cursor.arguments = true; |
| 505 | } |
| 506 | case Instr::Ret { val } => { |
| 507 | if let value = val { |
| 508 | addOperand(&mut cursor, value); |
| 509 | } |
| 510 | } |
| 511 | case Instr::Jmp { .. } => set cursor.arguments = true, |
| 512 | case Instr::Br { a, b, .. } => { |
| 513 | addOperand(&mut cursor, a); |
| 514 | addOperand(&mut cursor, b); |
| 515 | set cursor.arguments = true; |
| 516 | } |
| 517 | case Instr::Switch { val, .. } => { |
| 518 | addOperand(&mut cursor, val); |
| 519 | set cursor.arguments = true; |
| 520 | } |
| 521 | case Instr::Ecall { num, a0, a1, a2, a3, .. } => { |
| 522 | addOperand(&mut cursor, num); |
| 523 | addOperand(&mut cursor, a0); |
| 524 | addOperand(&mut cursor, a1); |
| 525 | addOperand(&mut cursor, a2); |
| 526 | addOperand(&mut cursor, a3); |
| 527 | } |
| 528 | case Instr::DeviceRead { handle, offset, .. } => { |
| 529 | addOperand(&mut cursor, handle); |
| 530 | addOperand(&mut cursor, offset); |
| 531 | } |
| 532 | case Instr::DeviceWrite { handle, offset, value, .. } => { |
| 533 | addOperand(&mut cursor, handle); |
| 534 | addOperand(&mut cursor, offset); |
| 535 | addOperand(&mut cursor, value); |
| 536 | } |
| 537 | case Instr::Unreachable, Instr::Ebreak, Instr::MemoryFence => { |
| 538 | } |
| 539 | } |
| 540 | return cursor; |
| 541 | } |
| 542 | |
| 543 | /// Append a fixed register operand to the cursor. |
| 544 | fn addOperand(cursor: &mut RegCursor, value: Val) { |
| 545 | if let case Val::Reg(reg) = value { |
| 546 | set cursor.fixed[cursor.count] = reg; |
| 547 | set cursor.count += 1; |
| 548 | } |
| 549 | } |
| 550 | |
| 551 | /// Return the next source register, including repeated uses. |
| 552 | /// Supply the unchanged instruction for every step of a scan. |
| 553 | export unsafe fn nextReg(cursor: &mut RegCursor, instr: &Instr) -> ?Reg { |
| 554 | return advanceReg(cursor, instr); |
| 555 | } |
| 556 | |
| 557 | /// Advance fixed operands and argument groups in source-register order. |
| 558 | fn advanceReg(cursor: &mut RegCursor, instr: &Instr) -> ?Reg { |
| 559 | if cursor.next < cursor.count { |
| 560 | let reg = cursor.fixed[cursor.next]; |
| 561 | set cursor.next += 1; |
| 562 | return reg; |
| 563 | } |
| 564 | if not cursor.arguments { |
| 565 | return nil; |
| 566 | } |
| 567 | match instr { |
| 568 | case Instr::Call { args, .. } => { |
| 569 | unsafe { |
| 570 | if let reg = nextArgument(cursor, *args) { |
| 571 | return reg; |
| 572 | } |
| 573 | } |
| 574 | } |
| 575 | case Instr::Jmp { args, .. } => { |
| 576 | unsafe { |
| 577 | if let reg = nextArgument(cursor, *args) { |
| 578 | return reg; |
| 579 | } |
| 580 | } |
| 581 | } |
| 582 | case Instr::Br { thenArgs, elseArgs, .. } => { |
| 583 | unsafe { |
| 584 | if let reg = nextBranchArgument(cursor, *thenArgs, *elseArgs) { |
| 585 | return reg; |
| 586 | } |
| 587 | } |
| 588 | } |
| 589 | case Instr::Switch { defaultArgs, cases, .. } => { |
| 590 | unsafe { |
| 591 | if let reg = nextSwitchArgument(cursor, *defaultArgs, *cases) { |
| 592 | return reg; |
| 593 | } |
| 594 | } |
| 595 | } |
| 596 | else => panic "nextReg: expected instruction argument groups", |
| 597 | } |
| 598 | set cursor.arguments = false; |
| 599 | return nil; |
| 600 | } |
| 601 | |
| 602 | /// Scan the then arguments before the else arguments. |
| 603 | fn nextBranchArgument(cursor: &mut RegCursor, thenArgs: &[Val], elseArgs: &[Val]) -> ?Reg { |
| 604 | if cursor.branch == 0 { |
| 605 | if let reg = nextArgument(cursor, thenArgs) { |
| 606 | return reg; |
| 607 | } |
| 608 | set cursor.branch = 1; |
| 609 | set cursor.argument = 0; |
| 610 | } |
| 611 | return nextArgument(cursor, elseArgs); |
| 612 | } |
| 613 | |
| 614 | /// Scan default arguments and then each case's arguments in table order. |
| 615 | fn nextSwitchArgument(cursor: &mut RegCursor, defaultArgs: &[Val], cases: &[SwitchCase]) -> ?Reg { |
| 616 | if cursor.branch == 0 { |
| 617 | if let reg = nextArgument(cursor, defaultArgs) { |
| 618 | return reg; |
| 619 | } |
| 620 | set cursor.branch = 1; |
| 621 | set cursor.argument = 0; |
| 622 | } |
| 623 | while cursor.branch - 1 < cases.len { |
| 624 | let entry = &cases[cursor.branch - 1]; |
| 625 | unsafe { |
| 626 | if let reg = nextArgument(cursor, entry.args) { |
| 627 | return reg; |
| 628 | } |
| 629 | } |
| 630 | set cursor.branch += 1; |
| 631 | set cursor.argument = 0; |
| 632 | } |
| 633 | return nil; |
| 634 | } |
| 635 | |
| 636 | /// Scan an argument group from the cursor's current position. |
| 637 | fn nextArgument(cursor: &mut RegCursor, args: &[Val]) -> ?Reg { |
| 638 | while cursor.argument < args.len { |
| 639 | let value = args[cursor.argument]; |
| 640 | set cursor.argument += 1; |
| 641 | if let case Val::Reg(reg) = value { |
| 642 | return reg; |
| 643 | } |
| 644 | } |
| 645 | return nil; |
| 646 | } |