Add explicit startup assembly support
9e960a2c8581b3860388241907c77118a1af50ca71df41876c66fc8f9a3cb697
Add a `-start <input.ras>` option for the entry package so callers can provide their own startup text before generated functions. When explicit startup is present, the compiler skips the synthesized entry jump and exposes the semantic default function through the `::default` text symbol. Thread startup assembly data into the existing assembly data prefix and allow unresolved assembly jumps/address loads to be resolved by whole-program RV64 patching. Add executable tests for both a startup handoff to `@default` and startup code that exits directly.
1 parent
8a64c0a3
Makefile
+3 -1
| 70 | 70 | # Only tests with `//! returns:` are compiled to binaries and executed. |
|
| 71 | 71 | BIN_TEST_EXE_SRC := $(shell grep -rl '^//! returns:' $(BIN_TEST_DIR)) |
|
| 72 | 72 | BIN_TEST_RAD_EXE_SRC := $(filter %.rad,$(BIN_TEST_EXE_SRC)) |
|
| 73 | 73 | BIN_TEST_RAS_EXE_SRC := $(filter %.ras,$(BIN_TEST_EXE_SRC)) |
|
| 74 | 74 | BIN_TEST_RAD_ASM_SRC := $(wildcard $(BIN_TEST_RAD_EXE_SRC:.rad=.ras)) |
|
| 75 | + | BIN_TEST_RAD_START_SRC := $(wildcard $(BIN_TEST_RAD_EXE_SRC:.rad=.start.ras)) |
|
| 75 | 76 | BIN_TEST_EXE_BIN := $(patsubst %.rad,%.rv64,$(BIN_TEST_RAD_EXE_SRC)) \ |
|
| 76 | 77 | $(patsubst %.ras,%.rv64,$(BIN_TEST_RAS_EXE_SRC)) |
|
| 77 | 78 | BIN_RUNNER := test/runner.rv64 |
|
| 78 | 79 | BIN_TEST_RUN := test/run |
|
| 79 | 80 |
| 86 | 87 | @echo "radiance test/runner.rad => $@" |
|
| 87 | 88 | @$(RADIANCE) $(STD) -pkg runner -mod test/runner.rad -entry runner -o $@ |
|
| 88 | 89 | ||
| 89 | 90 | # A `.rad` executable test can have a same-basename `.ras` module. |
|
| 90 | 91 | $(patsubst %.ras,%.rv64,$(BIN_TEST_RAD_ASM_SRC)): %.rv64: %.ras |
|
| 92 | + | $(patsubst %.start.ras,%.rv64,$(BIN_TEST_RAD_START_SRC)): %.rv64: %.start.ras |
|
| 91 | 93 | ||
| 92 | 94 | # Compile each executable test to a binary. |
|
| 93 | 95 | $(BIN_TEST_DIR)/%.rv64: $(BIN_TEST_DIR)/%.rad $(RAD_BIN) |
|
| 94 | 96 | @echo "radiance $< => $@" |
|
| 95 | - | @$(RADIANCE) -pkg test -mod $< $(patsubst %,-mod %,$(wildcard $(@:.rv64=.ras))) -o $@ |
|
| 97 | + | @$(RADIANCE) -pkg test $(patsubst %,-start %,$(wildcard $(@:.rv64=.start.ras))) -mod $< $(patsubst %,-mod %,$(wildcard $(@:.rv64=.ras))) -o $@ |
|
| 96 | 98 | ||
| 97 | 99 | $(BIN_TEST_DIR)/%.rv64: $(BIN_TEST_DIR)/%.ras $(BIN_RUNNER) |
|
| 98 | 100 | @echo "asm $< => $@" |
|
| 99 | 101 | @$(EMU) $(EMU_FLAGS) -run $(BIN_RUNNER) -- assemble $< $@ |
|
| 100 | 102 |
compiler/radiance.rad
+51 -12
| 90 | 90 | /// Accumulated assembly read-only data. |
|
| 91 | 91 | static ASM_RO_DATA_BUF: [u8; MAX_RO_DATA_SIZE] = undefined; |
|
| 92 | 92 | ||
| 93 | 93 | /// Assembly source file extension. |
|
| 94 | 94 | constant ASM_SOURCE_EXT: *[u8] = ".ras"; |
|
| 95 | + | /// Symbol name exported for startup code to call the semantic entry function. |
|
| 96 | + | constant DEFAULT_ENTRY_SYMBOL: *[u8] = "::default"; |
|
| 95 | 97 | ||
| 96 | 98 | /// Usage string. |
|
| 97 | 99 | constant USAGE: *[u8] = |
|
| 98 | - | "usage: radiance -pkg <name> -mod <input>.. [-pkg <name> -mod <input>..] -entry <pkg> -o <output>\n"; |
|
| 100 | + | "usage: radiance -pkg <name> [-start <input.ras>] -mod <input>.. [-pkg <name> -mod <input>..] -entry <pkg> -o <output>\n"; |
|
| 99 | 101 | ||
| 100 | 102 | /// Compiler error. |
|
| 101 | 103 | union Error { |
|
| 102 | 104 | Other, |
|
| 103 | 105 | } |
| 126 | 128 | ||
| 127 | 129 | /// Source inputs belonging to one command-line package. |
|
| 128 | 130 | record PackageInput { |
|
| 129 | 131 | /// Package name from the `-pkg` argument. |
|
| 130 | 132 | name: *[u8], |
|
| 133 | + | /// Optional startup assembly emitted before generated text. |
|
| 134 | + | startupPath: ?*[u8], |
|
| 131 | 135 | /// Radiance source paths for this package. |
|
| 132 | 136 | radPaths: [*[u8]; MAX_LOADED_MODULES], |
|
| 133 | 137 | /// Number of Radiance source paths. |
|
| 134 | 138 | radPathCount: u32, |
|
| 135 | 139 | /// Assembly source paths for this package. |
| 168 | 172 | ||
| 169 | 173 | /// State carried by the streaming lowerer/codegen callback. |
|
| 170 | 174 | record CodegenSinkContext { |
|
| 171 | 175 | /// RV64 generator receiving lowered functions. |
|
| 172 | 176 | generator: *mut rv64::Generator, |
|
| 173 | - | /// Arena used for codegen scratch allocations. |
|
| 174 | - | codegenArena: *mut alloc::Arena, |
|
| 175 | 177 | /// Arena holding the current function's lowered IL. |
|
| 176 | 178 | fnArena: *mut alloc::Arena, |
|
| 177 | 179 | } |
|
| 178 | 180 | ||
| 179 | 181 | /// Entry handling for streamed code generation. |
| 234 | 236 | ||
| 235 | 237 | /// Create an empty source input set for one package. |
|
| 236 | 238 | fn packageInput(name: *[u8]) -> PackageInput { |
|
| 237 | 239 | return PackageInput { |
|
| 238 | 240 | name, |
|
| 241 | + | startupPath: nil, |
|
| 239 | 242 | radPaths: undefined, |
|
| 240 | 243 | radPathCount: 0, |
|
| 241 | 244 | asmPaths: undefined, |
|
| 242 | 245 | asmPathCount: 0, |
|
| 243 | 246 | }; |
| 339 | 342 | throw error(&["too many modules specified for package"]); |
|
| 340 | 343 | } |
|
| 341 | 344 | set input.radPaths[input.radPathCount] = args[idx]; |
|
| 342 | 345 | set input.radPathCount += 1; |
|
| 343 | 346 | } |
|
| 347 | + | } else if mem::eq(arg, "-start") { |
|
| 348 | + | try nextArg(args, &mut idx, &["`-start` requires an assembly path"]); |
|
| 349 | + | let pkgIdx = currentPkgIdx else { |
|
| 350 | + | throw error(&["`-start` must follow a `-pkg` argument"]); |
|
| 351 | + | }; |
|
| 352 | + | let input = &mut inputs[pkgIdx]; |
|
| 353 | + | if input.startupPath <> nil { |
|
| 354 | + | throw error(&["package", input.name, "has more than one startup file"]); |
|
| 355 | + | } |
|
| 356 | + | if not hasExtension(args[idx], ASM_SOURCE_EXT) { |
|
| 357 | + | throw error(&["`-start` requires a `.ras` assembly file"]); |
|
| 358 | + | } |
|
| 359 | + | set input.startupPath = args[idx]; |
|
| 344 | 360 | } else if mem::eq(arg, "-entry") { |
|
| 345 | 361 | try nextArg(args, &mut idx, &["`-entry` requires a package name"]); |
|
| 346 | 362 | set entryPkgName = args[idx]; |
|
| 347 | 363 | } else if mem::eq(arg, "-test") { |
|
| 348 | 364 | set buildTest = true; |
| 397 | 413 | } |
|
| 398 | 414 | if entryPkgIdx == nil { |
|
| 399 | 415 | throw error(&["fatal:", "entry package", entryName, "not found"]); |
|
| 400 | 416 | } |
|
| 401 | 417 | } |
|
| 418 | + | let entryIdx = entryPkgIdx else { |
|
| 419 | + | panic "processCommand: no entry package"; |
|
| 420 | + | }; |
|
| 421 | + | for i in 0..pkgCount { |
|
| 422 | + | if i <> entryIdx and inputs[i].startupPath <> nil { |
|
| 423 | + | throw error(&["`-start` is only supported on the entry package"]); |
|
| 424 | + | } |
|
| 425 | + | } |
|
| 402 | 426 | let graph = module::moduleGraph(&mut MODULE_ENTRIES[..], &mut STRING_POOL, arena); |
|
| 403 | 427 | let mut ctx = CompileContext { |
|
| 404 | 428 | packages: undefined, |
|
| 405 | 429 | inputs, |
|
| 406 | 430 | packageCount: pkgCount, |
| 430 | 454 | throw error(&["no entry package specified"]); |
|
| 431 | 455 | }; |
|
| 432 | 456 | return &ctx.packages[entryIdx]; |
|
| 433 | 457 | } |
|
| 434 | 458 | ||
| 459 | + | /// Return the startup assembly path for the entry package, if one was supplied. |
|
| 460 | + | fn getEntryStartupPath(ctx: *CompileContext) -> ?*[u8] { |
|
| 461 | + | let entryIdx = ctx.entryPkgIdx else { |
|
| 462 | + | panic "getEntryStartupPath: no entry package"; |
|
| 463 | + | }; |
|
| 464 | + | return ctx.inputs[entryIdx].startupPath; |
|
| 465 | + | } |
|
| 466 | + | ||
| 435 | 467 | /// Get root module info from a package. |
|
| 436 | 468 | fn getRootModule(pkg: *package::Package, graph: *module::ModuleGraph) -> RootModule throws (Error) { |
|
| 437 | 469 | let rootId = pkg.rootModuleId else { |
|
| 438 | 470 | throw error(&["no root module found"]); |
|
| 439 | 471 | }; |
| 855 | 887 | fn generateLoweredFn(ctxPtr: *mut opaque, func: *il::Fn, role: lower::FnRole) { |
|
| 856 | 888 | let ctx = ctxPtr as *mut CodegenSinkContext; |
|
| 857 | 889 | ||
| 858 | 890 | match role { |
|
| 859 | 891 | case lower::FnRole::Default => { |
|
| 892 | + | rv64::recordFunctionAlias(ctx.generator, DEFAULT_ENTRY_SYMBOL); |
|
| 860 | 893 | match ctx.generator.entryPatch { |
|
| 861 | 894 | case rv64::EntryPatch::Reserved(_) => { |
|
| 862 | 895 | set ctx.generator.entryPatch = rv64::EntryPatch::Reserved(func.name); |
|
| 863 | 896 | } |
|
| 864 | - | else => panic "generateLoweredFn: entry jump was not reserved", |
|
| 897 | + | // No entry jump was reserved: startup assembly calls the |
|
| 898 | + | // default function through `DEFAULT_ENTRY_SYMBOL` instead. |
|
| 899 | + | case rv64::EntryPatch::None => {} |
|
| 865 | 900 | } |
|
| 866 | 901 | } |
|
| 867 | 902 | else => {} |
|
| 868 | 903 | } |
|
| 869 | - | rv64::generateFunction(ctx.generator, func, ctx.codegenArena); |
|
| 904 | + | rv64::generateFunction(ctx.generator, func, ctx.fnArena); |
|
| 870 | 905 | alloc::reset(ctx.fnArena); |
|
| 871 | 906 | } |
|
| 872 | 907 | ||
| 873 | 908 | /// Assemble one `.ras` input and merge it into the active code generator. |
|
| 874 | 909 | /// |
| 912 | 947 | ||
| 913 | 948 | /// Assemble all inputs collected in the package inputs. |
|
| 914 | 949 | fn assembleAsmInputs( |
|
| 915 | 950 | ctx: *CompileContext, |
|
| 916 | 951 | generator: *mut rv64::Generator, |
|
| 952 | + | asmDataLen: *mut u32, |
|
| 917 | 953 | arena: *mut alloc::Arena |
|
| 918 | 954 | ) -> *[u8] throws (Error) { |
|
| 919 | - | let mut asmDataLen: u32 = 0; |
|
| 920 | - | ||
| 921 | 955 | for i in 0..ctx.packageCount { |
|
| 922 | 956 | let input = &ctx.inputs[i]; |
|
| 923 | 957 | for j in 0..input.asmPathCount { |
|
| 924 | 958 | try assembleAsmModule( |
|
| 925 | 959 | generator, |
|
| 926 | 960 | &ctx.packages[i], |
|
| 927 | 961 | input.asmPaths[j], |
|
| 928 | - | &mut asmDataLen, |
|
| 962 | + | asmDataLen, |
|
| 929 | 963 | arena |
|
| 930 | 964 | ); |
|
| 931 | 965 | } |
|
| 932 | 966 | } |
|
| 933 | - | return &ASM_RO_DATA_BUF[..asmDataLen]; |
|
| 967 | + | return &ASM_RO_DATA_BUF[..*asmDataLen]; |
|
| 934 | 968 | } |
|
| 935 | 969 | ||
| 936 | 970 | /// Lower all packages while streaming each lowered function into RV64 codegen. |
|
| 937 | 971 | fn lowerAndGenerateAllPackages( |
|
| 938 | 972 | ctx: *mut CompileContext, |
| 960 | 994 | rv64::ProgramOptions { entryPatch, debug: codegenOptions.debug }, |
|
| 961 | 995 | &mut res.arena |
|
| 962 | 996 | ); |
|
| 963 | 997 | let mut codegenCtx = CodegenSinkContext { |
|
| 964 | 998 | generator: &mut generator, |
|
| 965 | - | codegenArena: &mut res.arena, |
|
| 966 | 999 | fnArena, |
|
| 967 | 1000 | }; |
|
| 968 | 1001 | let mut low = lower::lowerer( |
|
| 969 | 1002 | res, &ctx.graph, entryPkg.name, &mut res.arena, fnArena, options |
|
| 970 | 1003 | ); |
|
| 971 | 1004 | set low.output = lower::FnOutput::Stream(lower::FnSink { |
|
| 972 | 1005 | ctx: &mut codegenCtx as *mut opaque, |
|
| 973 | 1006 | emitFn: generateLoweredFn, |
|
| 974 | 1007 | }); |
|
| 1008 | + | let mut asmDataLen: u32 = 0; |
|
| 1009 | + | if let startupPath = getEntryStartupPath(ctx) { |
|
| 1010 | + | try assembleAsmModule(&mut generator, entryPkg, startupPath, &mut asmDataLen, &mut res.arena); |
|
| 1011 | + | } |
|
| 975 | 1012 | try lowerAllPackagesInto(ctx, res, &mut low); |
|
| 976 | - | let asmData = try assembleAsmInputs(ctx, &mut generator, &mut res.arena); |
|
| 1013 | + | let asmData = try assembleAsmInputs(ctx, &mut generator, &mut asmDataLen, &mut res.arena); |
|
| 977 | 1014 | ||
| 978 | 1015 | match generator.entryPatch { |
|
| 979 | 1016 | case rv64::EntryPatch::Reserved(targetName) => { |
|
| 980 | 1017 | if targetName == nil { |
|
| 981 | 1018 | throw error(&["fatal:", "no default function found"]); |
| 1022 | 1059 | return; |
|
| 1023 | 1060 | }; |
|
| 1024 | 1061 | let result = try lowerAndGenerateAllPackages(ctx, res, fnArena, CodegenOptions { |
|
| 1025 | 1062 | logPath: outPath, |
|
| 1026 | 1063 | debug: ctx.debug, |
|
| 1027 | - | entryMode: CodegenEntryMode::DefaultEntry, |
|
| 1064 | + | entryMode: CodegenEntryMode::None |
|
| 1065 | + | if getEntryStartupPath(ctx) <> nil |
|
| 1066 | + | else CodegenEntryMode::DefaultEntry, |
|
| 1028 | 1067 | }); |
|
| 1029 | 1068 | ||
| 1030 | 1069 | if not writeCode(result.code, outPath) { |
|
| 1031 | 1070 | throw error(&["fatal:", "failed to write output file"]); |
|
| 1032 | 1071 | } |
lib/std/arch/rv64.rad
+18 -1
| 239 | 239 | ||
| 240 | 240 | // Reclaim unused memory after instruction selection. |
|
| 241 | 241 | alloc::restore(arena, checkpoint); |
|
| 242 | 242 | } |
|
| 243 | 243 | ||
| 244 | + | /// Record an alternate name for the next function emitted. |
|
| 245 | + | export fn recordFunctionAlias(generator: *mut Generator, name: *[u8]) { |
|
| 246 | + | emit::recordFuncOffsetAt(&mut generator.e, name, generator.e.codeLen); |
|
| 247 | + | } |
|
| 248 | + | ||
| 244 | 249 | /// Add the text section of an assembled program to the generator. |
|
| 245 | 250 | /// |
|
| 246 | 251 | /// This function snapshots the generator's current code length as the base |
|
| 247 | 252 | /// index, converts each text symbol's byte offset to an instruction index, adds |
|
| 248 | 253 | /// that base, and records the final address for printing. Only `.export` text |
| 263 | 268 | if symbol.isExported { |
|
| 264 | 269 | emit::recordFuncOffsetAt(&mut generator.e, symbol.name, index); |
|
| 265 | 270 | } |
|
| 266 | 271 | } |
|
| 267 | 272 | } |
|
| 273 | + | for fixup in program.externalFixups { |
|
| 274 | + | match fixup.info { |
|
| 275 | + | case asm::FixupInfo::Jal { rd, index } => { |
|
| 276 | + | emit::recordJumpAt(&mut generator.e, fixup.symbol, rd, baseIndex + index); |
|
| 277 | + | } |
|
| 278 | + | case asm::FixupInfo::Addr { rd, index } => { |
|
| 279 | + | emit::recordAddrLoadAt(&mut generator.e, fixup.symbol, rd, baseIndex + index); |
|
| 280 | + | } |
|
| 281 | + | else => panic "addAssembly: invalid external fixup", |
|
| 282 | + | } |
|
| 283 | + | } |
|
| 268 | 284 | for word in program.text { |
|
| 269 | 285 | emit::emit(&mut generator.e, word); |
|
| 270 | 286 | } |
|
| 271 | 287 | } |
|
| 272 | 288 |
| 303 | 319 | emit::patch(&mut generator.e, 1, encode::jalr(ZERO, SCRATCH1, s.lo)); |
|
| 304 | 320 | } |
|
| 305 | 321 | else => {} |
|
| 306 | 322 | } |
|
| 307 | 323 | // Patch function calls and address loads now that all functions are emitted. |
|
| 324 | + | emit::patchJumps(&mut generator.e); |
|
| 308 | 325 | emit::patchCalls(&mut generator.e); |
|
| 309 | 326 | emit::patchAddrLoads(&mut generator.e, &dataSymMap); |
|
| 310 | 327 | ||
| 311 | 328 | // Emit data sections. |
|
| 312 | 329 | assert roDataPrefix.len <= roDataBuf.len, "finishProgram: rodata prefix buffer overflow"; |
| 318 | 335 | let rwDataSize = data::emitSection( |
|
| 319 | 336 | globalData, &dataSymMap, &generator.e.labels, codeBase, rwDataBuf, false |
|
| 320 | 337 | ); |
|
| 321 | 338 | return Program { |
|
| 322 | 339 | code: emit::getCode(&generator.e), |
|
| 323 | - | funcs: emit::getFuncs(&generator.e), |
|
| 340 | + | funcs: generator.e.funcs, |
|
| 324 | 341 | roDataSize, |
|
| 325 | 342 | rwDataSize, |
|
| 326 | 343 | debugEntries: emit::getDebugEntries(&generator.e), |
|
| 327 | 344 | }; |
|
| 328 | 345 | } |
lib/std/arch/rv64/asm.rad
+7 -0
| 78 | 78 | text: *[u32], |
|
| 79 | 79 | /// Raw bytes in the data section. |
|
| 80 | 80 | data: *[u8], |
|
| 81 | 81 | /// Symbols defined by the source. |
|
| 82 | 82 | symbols: *[Symbol], |
|
| 83 | + | /// Text references resolved by the whole-program emitter. |
|
| 84 | + | externalFixups: *[Fixup], |
|
| 83 | 85 | } |
|
| 84 | 86 | ||
| 85 | 87 | /// Errors reported while assembling source text. |
|
| 86 | 88 | export union Error { |
|
| 87 | 89 | /// Invalid syntax or operand form at a source offset. |
| 468 | 470 | constMap: dict::Dict, |
|
| 469 | 471 | /// Names marked by `.export`. |
|
| 470 | 472 | exportMap: dict::Dict, |
|
| 471 | 473 | /// Pending fixups. |
|
| 472 | 474 | fixups: *mut [Fixup], |
|
| 475 | + | /// Fixups that reference text outside this assembly fragment. |
|
| 476 | + | externalFixups: *mut [Fixup], |
|
| 473 | 477 | /// Absolute runtime address of data-section offset zero. |
|
| 474 | 478 | dataBase: u32, |
|
| 475 | 479 | } |
|
| 476 | 480 | ||
| 477 | 481 | /// Assemble source using `dataBase` as the runtime address of the data-section. |
| 487 | 491 | let slotCap = source.len + SOURCE_CAP_PADDING; |
|
| 488 | 492 | let tableCap = nextPowerOfTwo(slotCap * TABLE_CAPACITY_SCALE); |
|
| 489 | 493 | ||
| 490 | 494 | let symbols = try! alloc::allocSlice(arena, @sizeOf(Symbol), @alignOf(Symbol), slotCap); |
|
| 491 | 495 | let fixups = try! alloc::allocSlice(arena, @sizeOf(Fixup), @alignOf(Fixup), slotCap); |
|
| 496 | + | let externalFixups = try! alloc::allocSlice(arena, @sizeOf(Fixup), @alignOf(Fixup), slotCap); |
|
| 492 | 497 | let entries = try! alloc::allocSlice(arena, @sizeOf(dict::Entry), @alignOf(dict::Entry), tableCap); |
|
| 493 | 498 | let constEntries = try! alloc::allocSlice(arena, @sizeOf(dict::Entry), @alignOf(dict::Entry), tableCap); |
|
| 494 | 499 | let exportEntries = try! alloc::allocSlice(arena, @sizeOf(dict::Entry), @alignOf(dict::Entry), tableCap); |
|
| 495 | 500 | ||
| 496 | 501 | let mut a = Assembler { |
| 502 | 507 | symbols: @sliceOf((symbols as *mut [Symbol]).ptr, 0, (symbols as *mut [Symbol]).len), |
|
| 503 | 508 | symbolMap: dict::init(entries as *mut [dict::Entry]), |
|
| 504 | 509 | constMap: dict::init(constEntries as *mut [dict::Entry]), |
|
| 505 | 510 | exportMap: dict::init(exportEntries as *mut [dict::Entry]), |
|
| 506 | 511 | fixups: @sliceOf((fixups as *mut [Fixup]).ptr, 0, (fixups as *mut [Fixup]).len), |
|
| 512 | + | externalFixups: @sliceOf((externalFixups as *mut [Fixup]).ptr, 0, (externalFixups as *mut [Fixup]).len), |
|
| 507 | 513 | dataBase, |
|
| 508 | 514 | }; |
|
| 509 | 515 | // Parse assembly source and emit instructions. |
|
| 510 | 516 | try parser::parseProgram(&mut a); |
|
| 511 | 517 | // Resolve fixups and finalize program. |
| 513 | 519 | ||
| 514 | 520 | return Program { |
|
| 515 | 521 | text: a.text, |
|
| 516 | 522 | data: a.data, |
|
| 517 | 523 | symbols: a.symbols, |
|
| 524 | + | externalFixups: a.externalFixups, |
|
| 518 | 525 | }; |
|
| 519 | 526 | } |
|
| 520 | 527 | ||
| 521 | 528 | /// Return the next power of two at least as large as `value`. |
|
| 522 | 529 | fn nextPowerOfTwo(value: u32) -> u32 { |
lib/std/arch/rv64/asm/emit.rad
+18 -25
| 3 | 3 | use std::arch::rv64::encode; |
|
| 4 | 4 | use std::arch::rv64; |
|
| 5 | 5 | use std::fmt; |
|
| 6 | 6 | ||
| 7 | 7 | use std::collections::dict; |
|
| 8 | + | use std::lang::alloc; |
|
| 8 | 9 | use std::lang::gen; |
|
| 9 | 10 | ||
| 10 | 11 | /// Define a symbol at the current text or data offset. |
|
| 11 | 12 | export fn defineSymbol(a: *mut super::Assembler, name: *[u8]) { |
|
| 12 | - | if a.symbols.len >= a.symbols.cap { |
|
| 13 | - | panic "asm: symbol buffer full"; |
|
| 14 | - | } |
|
| 15 | 13 | let idx = a.symbols.len; |
|
| 16 | 14 | let offset: i32 = a.data.len as i32 |
|
| 17 | 15 | if a.section == super::Section::Data |
|
| 18 | 16 | else a.text.len as i32 * rv64::INSTR_SIZE; |
|
| 19 | 17 | ||
| 20 | - | set a.symbols = @sliceOf(a.symbols.ptr, idx + 1, a.symbols.cap); |
|
| 21 | - | set a.symbols[idx] = super::Symbol { |
|
| 18 | + | a.symbols.append(super::Symbol { |
|
| 22 | 19 | name, |
|
| 23 | 20 | section: a.section, |
|
| 24 | 21 | offset, |
|
| 25 | 22 | isExported: dict::get(&a.exportMap, name) <> nil, |
|
| 26 | - | }; |
|
| 23 | + | }, alloc::arenaAllocator(a.arena)); |
|
| 27 | 24 | dict::insert(&mut a.symbolMap, name, idx as i32); |
|
| 28 | 25 | } |
|
| 29 | 26 | ||
| 30 | 27 | /// Append one encoded instruction word to the text section. |
|
| 31 | 28 | export fn emitText(a: *mut super::Assembler, word: u32) throws (super::Error) { |
|
| 32 | - | if a.text.len >= a.text.cap { |
|
| 33 | - | throw super::Error::TextOverflow; |
|
| 34 | - | } |
|
| 35 | - | let idx = a.text.len; |
|
| 36 | - | set a.text = @sliceOf(a.text.ptr, idx + 1, a.text.cap); |
|
| 37 | - | set a.text[idx] = word; |
|
| 29 | + | a.text.append(word, alloc::arenaAllocator(a.arena)); |
|
| 38 | 30 | } |
|
| 39 | 31 | ||
| 40 | 32 | /// Append `words` no-op instructions to the text section. |
|
| 41 | 33 | export fn emitTextPadding(a: *mut super::Assembler, words: u32) throws (super::Error) { |
|
| 42 | 34 | for _ in 0..words { |
| 44 | 36 | } |
|
| 45 | 37 | } |
|
| 46 | 38 | ||
| 47 | 39 | /// Append one byte to the data section. |
|
| 48 | 40 | export fn emitByte(a: *mut super::Assembler, byte: u8) throws (super::Error) { |
|
| 49 | - | if a.data.len >= a.data.cap { |
|
| 50 | - | throw super::Error::DataOverflow; |
|
| 51 | - | } |
|
| 52 | - | let idx = a.data.len; |
|
| 53 | - | set a.data = @sliceOf(a.data.ptr, idx + 1, a.data.cap); |
|
| 54 | - | set a.data[idx] = byte; |
|
| 41 | + | a.data.append(byte, alloc::arenaAllocator(a.arena)); |
|
| 55 | 42 | } |
|
| 56 | 43 | ||
| 57 | 44 | /// Emit a little-endian integer with `bytes` bytes. |
|
| 58 | 45 | fn emitDataInt(a: *mut super::Assembler, bits: u64, bytes: u32) throws (super::Error) { |
|
| 59 | 46 | for i in 0..bytes { |
| 91 | 78 | } |
|
| 92 | 79 | } |
|
| 93 | 80 | ||
| 94 | 81 | /// Record a pending symbol fixup. |
|
| 95 | 82 | fn recordFixup(a: *mut super::Assembler, symbol: *[u8], info: super::FixupInfo) { |
|
| 96 | - | if a.fixups.len >= a.fixups.cap { |
|
| 97 | - | panic "asm: fixup buffer full"; |
|
| 98 | - | } |
|
| 99 | - | let idx = a.fixups.len as u32; |
|
| 100 | - | set a.fixups = @sliceOf(a.fixups.ptr, idx + 1, a.fixups.cap); |
|
| 101 | - | set a.fixups[idx] = super::Fixup { symbol, info }; |
|
| 83 | + | a.fixups.append(super::Fixup { symbol, info }, alloc::arenaAllocator(a.arena)); |
|
| 84 | + | } |
|
| 85 | + | ||
| 86 | + | /// Record a text fixup that must be resolved after all program text is known. |
|
| 87 | + | fn recordExternalFixup(a: *mut super::Assembler, fixup: super::Fixup) { |
|
| 88 | + | a.externalFixups.append(fixup, alloc::arenaAllocator(a.arena)); |
|
| 102 | 89 | } |
|
| 103 | 90 | ||
| 104 | 91 | /// Record a text-section symbol fixup and reserve its instruction words. |
|
| 105 | 92 | export fn recordTextFixup(a: *mut super::Assembler, symbol: *[u8], info: super::FixupInfo, words: u32) throws (super::Error) { |
|
| 106 | 93 | recordFixup(a, symbol, info); |
| 125 | 112 | /// Resolve final symbol references and patch all delayed output. |
|
| 126 | 113 | export fn finishProgram(a: *mut super::Assembler) throws (super::Error) { |
|
| 127 | 114 | for i in 0..a.fixups.len { |
|
| 128 | 115 | let fixup = a.fixups[i]; |
|
| 129 | 116 | let symbol = findSymbol(a, fixup.symbol) else { |
|
| 130 | - | throw super::Error::Invalid { offset: 0, message: "undefined symbol" }; |
|
| 117 | + | match fixup.info { |
|
| 118 | + | case super::FixupInfo::Jal { .. }, super::FixupInfo::Addr { .. } => { |
|
| 119 | + | recordExternalFixup(a, fixup); |
|
| 120 | + | continue; |
|
| 121 | + | } |
|
| 122 | + | else => throw super::Error::Invalid { offset: 0, message: "undefined symbol" }, |
|
| 123 | + | } |
|
| 131 | 124 | }; |
|
| 132 | 125 | match fixup.info { |
|
| 133 | 126 | case super::FixupInfo::Branch { op, rs1, rs2, index } => { |
|
| 134 | 127 | if symbol.section <> super::Section::Text { |
|
| 135 | 128 | throw super::Error::Invalid { offset: 0, message: "branch target must be in text section" }; |
lib/std/arch/rv64/asm/parser.rad
+3 -8
| 566 | 566 | fn parseSpaceDirective(a: *mut super::Assembler) throws (super::Error) { |
|
| 567 | 567 | let count = try parseValue(a); |
|
| 568 | 568 | if count < 0 { |
|
| 569 | 569 | throw fail(a, "space size must be non-negative"); |
|
| 570 | 570 | } |
|
| 571 | - | let remaining = a.data.cap - a.data.len; |
|
| 572 | - | if count > remaining as i64 { |
|
| 571 | + | // The data section grows on demand; only reject sizes that cannot be |
|
| 572 | + | // represented as a section offset. |
|
| 573 | + | if count > super::U32_MAX_VALUE - a.data.len as i64 { |
|
| 573 | 574 | throw super::Error::DataOverflow; |
|
| 574 | 575 | } |
|
| 575 | 576 | for _ in 0..count as u32 { |
|
| 576 | 577 | try emit::emitByte(a, 0); |
|
| 577 | 578 | } |
| 598 | 599 | let bytes = a.text.len * rv64::INSTR_SIZE as u32; |
|
| 599 | 600 | let aligned = checkedAlignUp(bytes, amount) else { |
|
| 600 | 601 | throw super::Error::TextOverflow; |
|
| 601 | 602 | }; |
|
| 602 | 603 | let words = (aligned - bytes) / rv64::INSTR_SIZE as u32; |
|
| 603 | - | if words > a.text.cap - a.text.len { |
|
| 604 | - | throw super::Error::TextOverflow; |
|
| 605 | - | } |
|
| 606 | 604 | try emit::emitTextPadding(a, words); |
|
| 607 | 605 | } |
|
| 608 | 606 | case super::Section::Data => { |
|
| 609 | 607 | let aligned = checkedAlignUp(a.data.len, amount) else { |
|
| 610 | 608 | throw super::Error::DataOverflow; |
|
| 611 | 609 | }; |
|
| 612 | - | if aligned > a.data.cap { |
|
| 613 | - | throw super::Error::DataOverflow; |
|
| 614 | - | } |
|
| 615 | 610 | for _ in a.data.len..aligned { |
|
| 616 | 611 | try emit::emitByte(a, 0); |
|
| 617 | 612 | } |
|
| 618 | 613 | } |
|
| 619 | 614 | } |
lib/std/arch/rv64/emit.rad
+64 -44
| 56 | 56 | index: u32, |
|
| 57 | 57 | /// Target function name. |
|
| 58 | 58 | target: *[u8], |
|
| 59 | 59 | } |
|
| 60 | 60 | ||
| 61 | + | /// Assembly jump that needs offset patching after all text is emitted. |
|
| 62 | + | export record PendingJump { |
|
| 63 | + | /// Index in code buffer where the jump was emitted. |
|
| 64 | + | index: u32, |
|
| 65 | + | /// Target function name. |
|
| 66 | + | target: *[u8], |
|
| 67 | + | /// Destination register. |
|
| 68 | + | rd: gen::Reg, |
|
| 69 | + | } |
|
| 70 | + | ||
| 61 | 71 | /// Address load that needs patching after layout is known. |
|
| 62 | 72 | export record PendingAddrLoad { |
|
| 63 | 73 | /// Index in code buffer where the load was emitted. |
|
| 64 | 74 | index: u32, |
|
| 65 | 75 | /// Target function or data symbol name. |
| 86 | 96 | offset: i32, |
|
| 87 | 97 | } |
|
| 88 | 98 | ||
| 89 | 99 | /// Emission context. Tracks state during code generation. |
|
| 90 | 100 | export record Emitter { |
|
| 101 | + | /// Allocator for growing append-backed emitter lists. |
|
| 102 | + | allocator: alloc::Allocator, |
|
| 91 | 103 | /// Emitted instructions storage. |
|
| 92 | 104 | code: *mut [u32], |
|
| 93 | 105 | /// Current number of emitted instructions. |
|
| 94 | 106 | codeLen: u32, |
|
| 95 | 107 | /// Local branches needing offset patching. |
|
| 96 | 108 | pendingBranches: *mut [PendingBranch], |
|
| 97 | - | /// Number of pending local branches. |
|
| 98 | - | pendingBranchesLen: u32, |
|
| 99 | 109 | /// Function calls needing offset patching. |
|
| 100 | 110 | pendingCalls: *mut [PendingCall], |
|
| 101 | - | /// Number of pending calls. |
|
| 102 | - | pendingCallsLen: u32, |
|
| 111 | + | /// Assembly jumps needing offset patching. |
|
| 112 | + | pendingJumps: *mut [PendingJump], |
|
| 103 | 113 | /// Function address loads needing offset patching. |
|
| 104 | 114 | pendingAddrLoads: *mut [PendingAddrLoad], |
|
| 105 | - | /// Number of pending address loads. |
|
| 106 | - | pendingAddrLoadsLen: u32, |
|
| 107 | 115 | /// Block label tracking. |
|
| 108 | 116 | labels: labels::Labels, |
|
| 109 | 117 | /// Function start positions for printing. |
|
| 110 | 118 | funcs: *mut [types::FuncAddr], |
|
| 111 | - | /// Number of recorded functions. |
|
| 112 | - | funcsLen: u32, |
|
| 113 | 119 | /// Debug entries mapping PCs to source locations. |
|
| 114 | 120 | debugEntries: *mut [types::DebugEntry], |
|
| 115 | 121 | /// Number of debug entries recorded. |
|
| 116 | 122 | debugEntriesLen: u32, |
|
| 117 | 123 | } |
| 178 | 184 | /// Create a new emitter. |
|
| 179 | 185 | export fn emitter(arena: *mut alloc::Arena, debug: bool) -> Emitter throws (alloc::AllocError) { |
|
| 180 | 186 | let code = try alloc::allocSlice(arena, @sizeOf(u32), @alignOf(u32), MAX_INSTRS); |
|
| 181 | 187 | let pendingBranches = try alloc::allocSlice(arena, @sizeOf(PendingBranch), @alignOf(PendingBranch), MAX_PENDING); |
|
| 182 | 188 | let pendingCalls = try alloc::allocSlice(arena, @sizeOf(PendingCall), @alignOf(PendingCall), MAX_PENDING); |
|
| 189 | + | let pendingJumps = try alloc::allocSlice(arena, @sizeOf(PendingJump), @alignOf(PendingJump), MAX_PENDING); |
|
| 183 | 190 | let pendingAddrLoads = try alloc::allocSlice(arena, @sizeOf(PendingAddrLoad), @alignOf(PendingAddrLoad), MAX_PENDING); |
|
| 184 | 191 | let blockOffsets = try alloc::allocSlice(arena, @sizeOf(i32), @alignOf(i32), labels::MAX_BLOCKS_PER_FN); |
|
| 185 | 192 | let funcEntries = try alloc::allocSlice(arena, @sizeOf(dict::Entry), @alignOf(dict::Entry), labels::FUNC_TABLE_SIZE); |
|
| 186 | 193 | let funcs = try alloc::allocSlice(arena, @sizeOf(types::FuncAddr), @alignOf(types::FuncAddr), MAX_FUNCS); |
|
| 187 | 194 |
| 190 | 197 | set debugEntries = try alloc::allocSlice( |
|
| 191 | 198 | arena, @sizeOf(types::DebugEntry), @alignOf(types::DebugEntry), MAX_DEBUG_ENTRIES |
|
| 192 | 199 | ) as *mut [types::DebugEntry]; |
|
| 193 | 200 | } |
|
| 194 | 201 | return Emitter { |
|
| 202 | + | allocator: alloc::arenaAllocator(arena), |
|
| 195 | 203 | code: code as *mut [u32], |
|
| 196 | 204 | codeLen: 0, |
|
| 197 | - | pendingBranches: pendingBranches as *mut [PendingBranch], |
|
| 198 | - | pendingBranchesLen: 0, |
|
| 199 | - | pendingCalls: pendingCalls as *mut [PendingCall], |
|
| 200 | - | pendingCallsLen: 0, |
|
| 201 | - | pendingAddrLoads: pendingAddrLoads as *mut [PendingAddrLoad], |
|
| 202 | - | pendingAddrLoadsLen: 0, |
|
| 205 | + | pendingBranches: @sliceOf((pendingBranches as *mut [PendingBranch]).ptr, 0, MAX_PENDING), |
|
| 206 | + | pendingCalls: @sliceOf((pendingCalls as *mut [PendingCall]).ptr, 0, MAX_PENDING), |
|
| 207 | + | pendingJumps: @sliceOf((pendingJumps as *mut [PendingJump]).ptr, 0, MAX_PENDING), |
|
| 208 | + | pendingAddrLoads: @sliceOf((pendingAddrLoads as *mut [PendingAddrLoad]).ptr, 0, MAX_PENDING), |
|
| 203 | 209 | labels: labels::init(blockOffsets as *mut [i32], funcEntries as *mut [dict::Entry]), |
|
| 204 | - | funcs: funcs as *mut [types::FuncAddr], |
|
| 205 | - | funcsLen: 0, |
|
| 210 | + | funcs: @sliceOf((funcs as *mut [types::FuncAddr]).ptr, 0, MAX_FUNCS), |
|
| 206 | 211 | debugEntries, |
|
| 207 | 212 | debugEntriesLen: 0, |
|
| 208 | 213 | }; |
|
| 209 | 214 | } |
|
| 210 | 215 |
| 251 | 256 | recordFuncAt(e, name, e.codeLen); |
|
| 252 | 257 | } |
|
| 253 | 258 | ||
| 254 | 259 | /// Record a function's start position at `index` for printing. |
|
| 255 | 260 | export fn recordFuncAt(e: *mut Emitter, name: *[u8], index: u32) { |
|
| 256 | - | assert e.funcsLen < e.funcs.len, "recordFunc: funcs buffer full"; |
|
| 257 | - | set e.funcs[e.funcsLen] = types::FuncAddr { name, index }; |
|
| 258 | - | set e.funcsLen += 1; |
|
| 261 | + | e.funcs.append(types::FuncAddr { name, index }, e.allocator); |
|
| 259 | 262 | } |
|
| 260 | 263 | ||
| 261 | 264 | /// Record a local branch needing later patching. |
|
| 262 | 265 | /// Unconditional jumps use a single slot (J-type, +-1MB range). |
|
| 263 | 266 | /// Conditional branches use two slots (B-type has only +-4KB range, |
|
| 264 | 267 | /// so large functions may need the inverted-branch + JAL fallback). |
|
| 265 | 268 | export fn recordBranch(e: *mut Emitter, targetBlock: u32, kind: BranchKind) { |
|
| 266 | - | assert e.pendingBranchesLen < e.pendingBranches.len, "recordBranch: buffer full"; |
|
| 267 | - | set e.pendingBranches[e.pendingBranchesLen] = PendingBranch { |
|
| 269 | + | e.pendingBranches.append(PendingBranch { |
|
| 268 | 270 | index: e.codeLen, |
|
| 269 | 271 | target: targetBlock, |
|
| 270 | 272 | kind: kind, |
|
| 271 | - | }; |
|
| 272 | - | set e.pendingBranchesLen += 1; |
|
| 273 | + | }, e.allocator); |
|
| 273 | 274 | ||
| 274 | 275 | emit(e, encode::nop()); // First slot, always needed. |
|
| 275 | 276 | ||
| 276 | 277 | match kind { |
|
| 277 | 278 | case BranchKind::Jump => {}, |
| 281 | 282 | ||
| 282 | 283 | /// Record a function call needing later patching. |
|
| 283 | 284 | /// Emits placeholder instructions that will be patched later. |
|
| 284 | 285 | /// Uses two slots to support long-distance calls. |
|
| 285 | 286 | export fn recordCall(e: *mut Emitter, target: *[u8]) { |
|
| 286 | - | assert e.pendingCallsLen < e.pendingCalls.len, "recordCall: buffer full"; |
|
| 287 | - | set e.pendingCalls[e.pendingCallsLen] = PendingCall { |
|
| 287 | + | e.pendingCalls.append(PendingCall { |
|
| 288 | 288 | index: e.codeLen, |
|
| 289 | 289 | target, |
|
| 290 | - | }; |
|
| 291 | - | set e.pendingCallsLen += 1; |
|
| 290 | + | }, e.allocator); |
|
| 292 | 291 | ||
| 293 | 292 | emit(e, encode::nop()); // Placeholder for AUIPC. |
|
| 294 | 293 | emit(e, encode::nop()); // Placeholder for JALR. |
|
| 295 | 294 | } |
|
| 296 | 295 | ||
| 296 | + | /// Record a jump emitted by assembly that needs whole-program patching. |
|
| 297 | + | export fn recordJumpAt(e: *mut Emitter, target: *[u8], rd: gen::Reg, index: u32) { |
|
| 298 | + | e.pendingJumps.append(PendingJump { |
|
| 299 | + | index, |
|
| 300 | + | target, |
|
| 301 | + | rd, |
|
| 302 | + | }, e.allocator); |
|
| 303 | + | } |
|
| 304 | + | ||
| 297 | 305 | /// Record a function address load needing later patching. |
|
| 298 | 306 | /// Emits placeholder instructions that will be patched to load the function's address. |
|
| 299 | 307 | /// Uses two slots to compute long-distance addresses. |
|
| 300 | 308 | export fn recordAddrLoad(e: *mut Emitter, target: *[u8], rd: gen::Reg) { |
|
| 301 | - | assert e.pendingAddrLoadsLen < e.pendingAddrLoads.len, "recordAddrLoad: buffer full"; |
|
| 302 | - | set e.pendingAddrLoads[e.pendingAddrLoadsLen] = PendingAddrLoad { |
|
| 309 | + | e.pendingAddrLoads.append(PendingAddrLoad { |
|
| 303 | 310 | index: e.codeLen, |
|
| 304 | 311 | target, |
|
| 305 | 312 | rd: rd, |
|
| 306 | 313 | isData: false, |
|
| 307 | - | }; |
|
| 308 | - | set e.pendingAddrLoadsLen += 1; |
|
| 314 | + | }, e.allocator); |
|
| 309 | 315 | ||
| 310 | 316 | emit(e, encode::nop()); // Placeholder for AUIPC. |
|
| 311 | 317 | emit(e, encode::nop()); // Placeholder for ADDI. |
|
| 312 | 318 | } |
|
| 313 | 319 | ||
| 320 | + | /// Record a function address load already reserved by assembly. |
|
| 321 | + | export fn recordAddrLoadAt(e: *mut Emitter, target: *[u8], rd: gen::Reg, index: u32) { |
|
| 322 | + | e.pendingAddrLoads.append(PendingAddrLoad { |
|
| 323 | + | index, |
|
| 324 | + | target, |
|
| 325 | + | rd: rd, |
|
| 326 | + | isData: false, |
|
| 327 | + | }, e.allocator); |
|
| 328 | + | } |
|
| 329 | + | ||
| 314 | 330 | /// Record a data address load needing later patching. |
|
| 315 | 331 | /// Uses an absolute 32-bit load sequence matching the current data memory map. |
|
| 316 | 332 | export fn recordDataAddrLoad(e: *mut Emitter, target: *[u8], rd: gen::Reg) { |
|
| 317 | - | assert e.pendingAddrLoadsLen < e.pendingAddrLoads.len, "recordDataAddrLoad: buffer full"; |
|
| 318 | - | set e.pendingAddrLoads[e.pendingAddrLoadsLen] = PendingAddrLoad { |
|
| 333 | + | e.pendingAddrLoads.append(PendingAddrLoad { |
|
| 319 | 334 | index: e.codeLen, |
|
| 320 | 335 | target, |
|
| 321 | 336 | rd: rd, |
|
| 322 | 337 | isData: true, |
|
| 323 | - | }; |
|
| 324 | - | set e.pendingAddrLoadsLen += 1; |
|
| 338 | + | }, e.allocator); |
|
| 325 | 339 | ||
| 326 | 340 | emit(e, encode::nop()); // Placeholder for LUI. |
|
| 327 | 341 | emit(e, encode::nop()); // Placeholder for ADDIW. |
|
| 328 | 342 | } |
|
| 329 | 343 |
| 332 | 346 | /// Called after each function. |
|
| 333 | 347 | /// |
|
| 334 | 348 | /// Uses two-instruction sequences: short branches use `branch` and `nop`, |
|
| 335 | 349 | /// long branches use inverted branch and `jal` or `auipc` and `jalr`. |
|
| 336 | 350 | export fn patchLocalBranches(e: *mut Emitter) { |
|
| 337 | - | for i in 0..e.pendingBranchesLen { |
|
| 351 | + | for i in 0..e.pendingBranches.len { |
|
| 338 | 352 | let p = e.pendingBranches[i]; |
|
| 339 | 353 | let offset = labels::branchToBlock(&e.labels, p.index, p.target, super::INSTR_SIZE); |
|
| 340 | 354 | match p.kind { |
|
| 341 | 355 | case BranchKind::Cond { op, rs1, rs2 } => { |
|
| 342 | 356 | if encode::isBranchImm(offset) { |
| 363 | 377 | assert encode::isJumpImm(offset), "patchLocalBranches: jump offset too large"; |
|
| 364 | 378 | patch(e, p.index, encode::jal(super::ZERO, offset)); |
|
| 365 | 379 | }, |
|
| 366 | 380 | } |
|
| 367 | 381 | } |
|
| 368 | - | set e.pendingBranchesLen = 0; |
|
| 382 | + | set e.pendingBranches = @sliceOf(e.pendingBranches.ptr, 0, e.pendingBranches.cap); |
|
| 369 | 383 | } |
|
| 370 | 384 | ||
| 371 | 385 | /// Encode a conditional branch instruction. |
|
| 372 | 386 | fn encodeCondBranch(op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg, offset: i32) -> u32 { |
|
| 373 | 387 | match op { |
| 389 | 403 | } |
|
| 390 | 404 | ||
| 391 | 405 | /// Patch all pending function calls. |
|
| 392 | 406 | /// Called after all functions have been generated. |
|
| 393 | 407 | export fn patchCalls(e: *mut Emitter) { |
|
| 394 | - | for i in 0..e.pendingCallsLen { |
|
| 408 | + | for i in 0..e.pendingCalls.len { |
|
| 395 | 409 | let p = e.pendingCalls[i]; |
|
| 396 | 410 | let offset = branchOffsetToFunc(e, p.index, p.target); |
|
| 397 | 411 | let s = splitImm(offset); |
|
| 398 | 412 | ||
| 399 | 413 | // `AUIPC scratch, hi(offset)`. |
| 401 | 415 | // `JALR ra, scratch, lo(offset)`. |
|
| 402 | 416 | patch(e, p.index + 1, encode::jalr(super::RA, super::SCRATCH1, s.lo)); |
|
| 403 | 417 | } |
|
| 404 | 418 | } |
|
| 405 | 419 | ||
| 420 | + | /// Patch all pending assembly jumps. |
|
| 421 | + | export fn patchJumps(e: *mut Emitter) { |
|
| 422 | + | for i in 0..e.pendingJumps.len { |
|
| 423 | + | let p = e.pendingJumps[i]; |
|
| 424 | + | let offset = branchOffsetToFunc(e, p.index, p.target); |
|
| 425 | + | ||
| 426 | + | assert encode::isJumpImm(offset), "patchJumps: jump offset too large"; |
|
| 427 | + | patch(e, p.index, encode::jal(p.rd, offset)); |
|
| 428 | + | } |
|
| 429 | + | } |
|
| 430 | + | ||
| 406 | 431 | /// Patch all pending function and data address loads. |
|
| 407 | 432 | /// Called after all functions have been generated and data layout is known. |
|
| 408 | 433 | export fn patchAddrLoads(e: *mut Emitter, dataSymMap: *data::DataSymMap) { |
|
| 409 | - | for i in 0..e.pendingAddrLoadsLen { |
|
| 434 | + | for i in 0..e.pendingAddrLoads.len { |
|
| 410 | 435 | let p = e.pendingAddrLoads[i]; |
|
| 411 | 436 | if p.isData { |
|
| 412 | 437 | let addr = data::lookupAddr(dataSymMap, p.target) else { |
|
| 413 | 438 | panic "patchAddrLoads: data symbol not found"; |
|
| 414 | 439 | }; |
| 688 | 713 | /// Get emitted code as a slice. |
|
| 689 | 714 | export fn getCode(e: *Emitter) -> *[u32] { |
|
| 690 | 715 | return &e.code[..e.codeLen]; |
|
| 691 | 716 | } |
|
| 692 | 717 | ||
| 693 | - | /// Get function addresses for printing. |
|
| 694 | - | export fn getFuncs(e: *Emitter) -> *[types::FuncAddr] { |
|
| 695 | - | return &e.funcs[..e.funcsLen]; |
|
| 696 | - | } |
|
| 697 | - | ||
| 698 | 718 | /// Record a debug entry mapping the current PC to a source location. |
|
| 699 | 719 | /// Deduplicates consecutive entries with the same location. |
|
| 700 | 720 | export fn recordSrcLoc(e: *mut Emitter, loc: il::SrcLoc) { |
|
| 701 | 721 | let pc = e.codeLen * super::INSTR_SIZE as u32; |
|
| 702 | 722 |
lib/std/arch/rv64/tests.rad
+6 -1
| 41 | 41 | super::ProgramOptions { entryPatch: super::EntryPatch::None, debug: false }, |
|
| 42 | 42 | &mut arena |
|
| 43 | 43 | ); |
|
| 44 | 44 | super::addAssembly( |
|
| 45 | 45 | &mut generator, |
|
| 46 | - | asm::Program { text: &ASSEMBLY_TEXT_STORAGE[..], data: &[], symbols: symbolSlice } |
|
| 46 | + | asm::Program { |
|
| 47 | + | text: &ASSEMBLY_TEXT_STORAGE[..], |
|
| 48 | + | data: &[], |
|
| 49 | + | symbols: symbolSlice, |
|
| 50 | + | externalFixups: &[], |
|
| 51 | + | } |
|
| 47 | 52 | ); |
|
| 48 | 53 | ||
| 49 | 54 | try testing::expect(dict::get(&generator.e.labels.funcs, "local") == nil); |
|
| 50 | 55 | let exportedOffset = dict::get(&generator.e.labels.funcs, "exported") else { |
|
| 51 | 56 | throw testing::TestError::Failed; |
test/runner.rad
+9 -1
| 216 | 216 | io::printError("error: assembly failed: "); |
|
| 217 | 217 | io::printError(sourcePath); |
|
| 218 | 218 | io::printError("\n"); |
|
| 219 | 219 | return false; |
|
| 220 | 220 | }; |
|
| 221 | - | if not writeCode(program.text, outputPath) { |
|
| 221 | + | // A standalone assembly binary has no whole-program emitter to resolve |
|
| 222 | + | // external text references, so they are undefined symbols here. |
|
| 223 | + | if program.externalFixups.len > 0 { |
|
| 224 | + | io::printError("error: undefined symbol: "); |
|
| 225 | + | io::printError(program.externalFixups[0].symbol); |
|
| 226 | + | io::printError("\n"); |
|
| 227 | + | return false; |
|
| 228 | + | } |
|
| 229 | + | if not writeImage(program.text, program.data, &[], outputPath) { |
|
| 222 | 230 | io::printError("error: could not write output: "); |
|
| 223 | 231 | io::printError(outputPath); |
|
| 224 | 232 | io::printError("\n"); |
|
| 225 | 233 | return false; |
|
| 226 | 234 | } |
test/tests/start.default.rad
added
+5 -0
| 1 | + | //! returns: 43 |
|
| 2 | + | ||
| 3 | + | @default fn main() -> i32 { |
|
| 4 | + | return 43; |
|
| 5 | + | } |
test/tests/start.default.start.ras
added
+2 -0
| 1 | + | .text; |
|
| 2 | + | tail @"::default"; |
test/tests/start.exit.rad
added
+5 -0
| 1 | + | //! returns: 37 |
|
| 2 | + | ||
| 3 | + | fn unused() -> i32 { |
|
| 4 | + | return 1; |
|
| 5 | + | } |
test/tests/start.exit.start.ras
added
+4 -0
| 1 | + | .text; |
|
| 2 | + | li %a0 37; |
|
| 3 | + | li %a7 93; |
|
| 4 | + | ecall; |