Allow globals-only i128 backend
Authored by
mfwolffe <wolffemf@dukes.jmu.edu>
- SHA
209204cc64e2e5d9e0ca4e2da8b3a133091a4bad- Parents
-
52cb9a8 - Tree
47eee7d
209204c
209204cc64e2e5d9e0ca4e2da8b3a133091a4bad52cb9a8
47eee7d| Status | File | + | - |
|---|---|---|---|
| M |
src/driver/mod.rs
|
2 | 2 |
| M |
src/ir/inst.rs
|
44 | 0 |
| M |
src/testing.rs
|
9 | 4 |
| A |
tests/fixtures/integer16_globals_backend.f90
|
8 | 0 |
| A |
tests/i128_backend_data.rs
|
95 | 0 |
src/driver/mod.rsmodified@@ -230,7 +230,7 @@ pub fn compile(opts: &Options) -> Result<(), String> { | |||
| 230 | return Err(format!("internal error: IR verification failed:\n{}", msg)); | 230 | return Err(format!("internal error: IR verification failed:\n{}", msg)); |
| 231 | } | 231 | } |
| 232 | let module_has_i128 = ir_module.contains_i128(); | 232 | let module_has_i128 = ir_module.contains_i128(); |
| 233 | - if module_has_i128 && opts.opt_level != OptLevel::O0 { | 233 | + if ir_module.contains_i128_outside_globals() && opts.opt_level != OptLevel::O0 { |
| 234 | return Err( | 234 | return Err( |
| 235 | "integer(16) / i128 optimization is not yet supported; use -O0 --emit-ir for now" | 235 | "integer(16) / i128 optimization is not yet supported; use -O0 --emit-ir for now" |
| 236 | .into(), | 236 | .into(), |
@@ -265,7 +265,7 @@ pub fn compile(opts: &Options) -> Result<(), String> { | |||
| 265 | return Ok(()); | 265 | return Ok(()); |
| 266 | } | 266 | } |
| 267 | 267 | ||
| 268 | - if module_has_i128 { | 268 | + if module_has_i128 && !ir_module.i128_backend_data_only_supported() { |
| 269 | return Err( | 269 | return Err( |
| 270 | "backend does not yet support integer(16) / i128 codegen; use --emit-ir for now" | 270 | "backend does not yet support integer(16) / i128 codegen; use --emit-ir for now" |
| 271 | .into(), | 271 | .into(), |
src/ir/inst.rsmodified@@ -410,6 +410,34 @@ impl Module { | |||
| 410 | }) | 410 | }) |
| 411 | }) | 411 | }) |
| 412 | } | 412 | } |
| 413 | + | ||
| 414 | + /// True when `i128` appears anywhere outside module-global storage. | ||
| 415 | + /// | ||
| 416 | + /// Globals-only `i128` is the first staged backend surface we support: | ||
| 417 | + /// the optimizer may ignore it and the emitter can lay it out as raw data. | ||
| 418 | + /// Parameters, returns, instruction results, block params, and extern | ||
| 419 | + /// signatures still imply unsupported ABI or codegen work. | ||
| 420 | + pub fn contains_i128_outside_globals(&self) -> bool { | ||
| 421 | + self.extern_funcs.iter().any(|func| sig_contains_i128(self, &func.sig)) | ||
| 422 | + || self.functions.iter().any(|func| { | ||
| 423 | + type_contains_i128(self, &func.return_type) | ||
| 424 | + || func.params.iter().any(|param| type_contains_i128(self, ¶m.ty)) | ||
| 425 | + || func.blocks.iter().any(|block| { | ||
| 426 | + block.params.iter().any(|param| type_contains_i128(self, ¶m.ty)) | ||
| 427 | + || block.insts.iter().any(|inst| type_contains_i128(self, &inst.ty)) | ||
| 428 | + }) | ||
| 429 | + }) | ||
| 430 | + } | ||
| 431 | + | ||
| 432 | + /// True when every `i128` use in the module is a global data shape that | ||
| 433 | + /// the current backend emitter can lay out directly. | ||
| 434 | + pub fn i128_backend_data_only_supported(&self) -> bool { | ||
| 435 | + !self.contains_i128_outside_globals() | ||
| 436 | + && self | ||
| 437 | + .globals | ||
| 438 | + .iter() | ||
| 439 | + .all(|global| global_i128_backend_data_supported(self, global)) | ||
| 440 | + } | ||
| 413 | } | 441 | } |
| 414 | 442 | ||
| 415 | fn sig_contains_i128(module: &Module, sig: &super::types::FuncSig) -> bool { | 443 | fn sig_contains_i128(module: &Module, sig: &super::types::FuncSig) -> bool { |
@@ -429,3 +457,19 @@ fn type_contains_i128(module: &Module, ty: &IrType) -> bool { | |||
| 429 | _ => false, | 457 | _ => false, |
| 430 | } | 458 | } |
| 431 | } | 459 | } |
| 460 | + | ||
| 461 | +fn global_i128_backend_data_supported(module: &Module, global: &Global) -> bool { | ||
| 462 | + match &global.ty { | ||
| 463 | + IrType::Int(IntWidth::I128) => matches!( | ||
| 464 | + global.initializer, | ||
| 465 | + Some(GlobalInit::Int(_)) | Some(GlobalInit::Zero) | None | ||
| 466 | + ), | ||
| 467 | + IrType::Array(elem_ty, _) if matches!(elem_ty.as_ref(), IrType::Int(IntWidth::I128)) => { | ||
| 468 | + matches!( | ||
| 469 | + global.initializer, | ||
| 470 | + Some(GlobalInit::IntArray(_)) | Some(GlobalInit::Zero) | None | ||
| 471 | + ) | ||
| 472 | + } | ||
| 473 | + _ => !type_contains_i128(module, &global.ty), | ||
| 474 | + } | ||
| 475 | +} | ||
src/testing.rsmodified@@ -366,7 +366,7 @@ pub fn capture_from_path(request: &CaptureRequest) -> Result<CaptureResult, Capt | |||
| 366 | }) | 366 | }) |
| 367 | && request.opt_level != OptLevel::O0; | 367 | && request.opt_level != OptLevel::O0; |
| 368 | 368 | ||
| 369 | - if module_has_i128 && needs_optimized_pipeline { | 369 | + if ir_module.contains_i128_outside_globals() && needs_optimized_pipeline { |
| 370 | return Err(CaptureFailure { | 370 | return Err(CaptureFailure { |
| 371 | input: input.clone(), | 371 | input: input.clone(), |
| 372 | opt_level: request.opt_level, | 372 | opt_level: request.opt_level, |
@@ -411,7 +411,7 @@ pub fn capture_from_path(request: &CaptureRequest) -> Result<CaptureResult, Capt | |||
| 411 | } | 411 | } |
| 412 | 412 | ||
| 413 | let backend_ir = optimized_module.as_ref().unwrap_or(&ir_module); | 413 | let backend_ir = optimized_module.as_ref().unwrap_or(&ir_module); |
| 414 | - if module_has_i128 { | 414 | + if module_has_i128 && !backend_ir.i128_backend_data_only_supported() { |
| 415 | return Err(CaptureFailure { | 415 | return Err(CaptureFailure { |
| 416 | input: input.clone(), | 416 | input: input.clone(), |
| 417 | opt_level: request.opt_level, | 417 | opt_level: request.opt_level, |
@@ -443,7 +443,7 @@ pub fn capture_from_path(request: &CaptureRequest) -> Result<CaptureResult, Capt | |||
| 443 | ); | 443 | ); |
| 444 | } | 444 | } |
| 445 | 445 | ||
| 446 | - let asm_text = emit_module_asm(&allocated); | 446 | + let asm_text = emit_module_asm(backend_ir, &allocated); |
| 447 | if wants(Stage::Asm) { | 447 | if wants(Stage::Asm) { |
| 448 | stages.insert(Stage::Asm, CapturedStage::Text(asm_text.clone())); | 448 | stages.insert(Stage::Asm, CapturedStage::Text(asm_text.clone())); |
| 449 | } | 449 | } |
@@ -792,7 +792,7 @@ fn escape_const_bytes(bytes: &[u8]) -> String { | |||
| 792 | out | 792 | out |
| 793 | } | 793 | } |
| 794 | 794 | ||
| 795 | -fn emit_module_asm(allocated: &[MachineFunction]) -> String { | 795 | +fn emit_module_asm(module: &crate::ir::inst::Module, allocated: &[MachineFunction]) -> String { |
| 796 | let mut asm_text = String::new(); | 796 | let mut asm_text = String::new(); |
| 797 | asm_text.push_str(".section __TEXT,__text,regular,pure_instructions\n"); | 797 | asm_text.push_str(".section __TEXT,__text,regular,pure_instructions\n"); |
| 798 | for mf in allocated { | 798 | for mf in allocated { |
@@ -801,6 +801,11 @@ fn emit_module_asm(allocated: &[MachineFunction]) -> String { | |||
| 801 | asm_text.push('\n'); | 801 | asm_text.push('\n'); |
| 802 | } | 802 | } |
| 803 | 803 | ||
| 804 | + if !module.globals.is_empty() { | ||
| 805 | + asm_text.push_str(&emit::emit_globals(&module.globals)); | ||
| 806 | + asm_text.push('\n'); | ||
| 807 | + } | ||
| 808 | + | ||
| 804 | if let Some(user_func) = allocated.first() { | 809 | if let Some(user_func) = allocated.first() { |
| 805 | if user_func.name != "main" { | 810 | if user_func.name != "main" { |
| 806 | let _ = write!( | 811 | let _ = write!( |
tests/fixtures/integer16_globals_backend.f90added@@ -0,0 +1,8 @@ | |||
| 1 | +module integer16_globals_backend | ||
| 2 | + implicit none | ||
| 3 | + integer(16), save :: big_scalar = 18446744073709551616_16 | ||
| 4 | + integer(16), save :: big_array(2) = [1_16, 9223372036854775808_16] | ||
| 5 | +contains | ||
| 6 | + subroutine touch() | ||
| 7 | + end subroutine touch | ||
| 8 | +end module integer16_globals_backend | ||
tests/i128_backend_data.rsadded@@ -0,0 +1,95 @@ | |||
| 1 | +use std::collections::BTreeSet; | ||
| 2 | +use std::path::PathBuf; | ||
| 3 | + | ||
| 4 | +use armfortas::driver::OptLevel; | ||
| 5 | +use armfortas::testing::{capture_from_path, CaptureRequest, CapturedStage, Stage}; | ||
| 6 | + | ||
| 7 | +fn fixture(name: &str) -> PathBuf { | ||
| 8 | + let path = PathBuf::from("tests/fixtures").join(name); | ||
| 9 | + assert!(path.exists(), "missing test fixture {}", path.display()); | ||
| 10 | + path | ||
| 11 | +} | ||
| 12 | + | ||
| 13 | +fn capture_text(request: CaptureRequest, stage: Stage) -> String { | ||
| 14 | + let result = capture_from_path(&request).expect("capture should succeed"); | ||
| 15 | + match result.get(stage) { | ||
| 16 | + Some(CapturedStage::Text(text)) => text.clone(), | ||
| 17 | + Some(CapturedStage::Run(_)) => panic!("expected text stage for {}", stage.as_str()), | ||
| 18 | + None => panic!("missing requested stage {}", stage.as_str()), | ||
| 19 | + } | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +#[test] | ||
| 23 | +fn globals_only_i128_backend_emits_expected_words_in_asm() { | ||
| 24 | + let asm = capture_text( | ||
| 25 | + CaptureRequest { | ||
| 26 | + input: fixture("integer16_globals_backend.f90"), | ||
| 27 | + requested: BTreeSet::from([Stage::Asm]), | ||
| 28 | + opt_level: OptLevel::O0, | ||
| 29 | + }, | ||
| 30 | + Stage::Asm, | ||
| 31 | + ); | ||
| 32 | + | ||
| 33 | + assert!( | ||
| 34 | + asm.contains(".section __DATA,__data"), | ||
| 35 | + "asm should emit a data section for i128 globals:\n{}", | ||
| 36 | + asm | ||
| 37 | + ); | ||
| 38 | + assert!( | ||
| 39 | + asm.matches(".p2align 4").count() >= 2, | ||
| 40 | + "scalar and array i128 globals should request 16-byte alignment:\n{}", | ||
| 41 | + asm | ||
| 42 | + ); | ||
| 43 | + assert!( | ||
| 44 | + asm.contains(".quad 0x0000000000000000\n .quad 0x0000000000000001"), | ||
| 45 | + "scalar i128 global should emit low/high 64-bit words:\n{}", | ||
| 46 | + asm | ||
| 47 | + ); | ||
| 48 | + assert!( | ||
| 49 | + asm.contains(".quad 0x0000000000000001\n .quad 0x0000000000000000"), | ||
| 50 | + "array i128 initializer should emit the first element's word pair:\n{}", | ||
| 51 | + asm | ||
| 52 | + ); | ||
| 53 | + assert!( | ||
| 54 | + asm.contains(".quad 0x8000000000000000\n .quad 0x0000000000000000"), | ||
| 55 | + "array i128 initializer should emit the second element's word pair:\n{}", | ||
| 56 | + asm | ||
| 57 | + ); | ||
| 58 | +} | ||
| 59 | + | ||
| 60 | +#[test] | ||
| 61 | +fn globals_only_i128_object_snapshot_is_deterministic_at_o2() { | ||
| 62 | + let source = fixture("integer16_globals_backend.f90"); | ||
| 63 | + let first = capture_text( | ||
| 64 | + CaptureRequest { | ||
| 65 | + input: source.clone(), | ||
| 66 | + requested: BTreeSet::from([Stage::Obj]), | ||
| 67 | + opt_level: OptLevel::O2, | ||
| 68 | + }, | ||
| 69 | + Stage::Obj, | ||
| 70 | + ); | ||
| 71 | + let second = capture_text( | ||
| 72 | + CaptureRequest { | ||
| 73 | + input: source, | ||
| 74 | + requested: BTreeSet::from([Stage::Obj]), | ||
| 75 | + opt_level: OptLevel::O2, | ||
| 76 | + }, | ||
| 77 | + Stage::Obj, | ||
| 78 | + ); | ||
| 79 | + | ||
| 80 | + assert!( | ||
| 81 | + first.contains("_afs_mod_integer16_globals_backend_big_scalar"), | ||
| 82 | + "object snapshot should retain the i128 scalar global symbol:\n{}", | ||
| 83 | + first | ||
| 84 | + ); | ||
| 85 | + assert!( | ||
| 86 | + first.contains("_afs_mod_integer16_globals_backend_big_array"), | ||
| 87 | + "object snapshot should retain the i128 array global symbol:\n{}", | ||
| 88 | + first | ||
| 89 | + ); | ||
| 90 | + | ||
| 91 | + assert_eq!( | ||
| 92 | + first, second, | ||
| 93 | + "globals-only i128 object snapshots should be deterministic at O2" | ||
| 94 | + ); | ||
| 95 | +} | ||