1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
//! Constant Hoisting — hoists large constants used inside loops to
//! reduce code size and enable better register allocation.
//! Phase 9 — LLVM.CONSTHOIST.1 Court.
//!
//! Clean-room behavioral reconstruction from compiler optimization
//! literature: LLVM's ConstantHoisting pass description, the
//! "Constant Hoisting" optimization in the GCC and LLVM communities,
//! and the LLVM Language Reference. Zero LLVM source code consultation.
//!
//! Large constants (especially on RISC architectures like ARM, RISC-V)
//! require multiple instructions to materialize (e.g., MOVW+MOVT on ARM).
//! Hoisting constants out of loops saves code size: instead of
//! materializing the constant in every loop iteration, it's computed
//! once in the loop preheader and reused.
//!
//! Algorithm overview:
//! 1. Find large constants used within loops
//! 2. Check if the constant is expensive to materialize (target-dependent)
//! 3. If savings outweigh cost, insert an instruction in the preheader
//! that computes the constant
//! 4. Replace all uses within the loop with the hoisted value
//!
//! Cost model:
//! - X86: constants ≤ 32-bit are cheap (1 instruction), expensive otherwise
//! - ARM: constants > 8-bit may be expensive (MOVW+MOVT = 2 instructions)
//! - RISC-V: constants > 12-bit are expensive (LUI+ADDI = 2 instructions)
//! - Saving: 1 instruction per use × loop trip count
use llvm_native_core::types::Type;
use llvm_native_core::value::{SubclassKind, ValueRef};
use std::collections::HashMap;
// ============================================================================
// Data Structures
// ============================================================================
/// Information about a constant to potentially hoist.
#[derive(Debug, Clone)]
pub struct ConstantInfo {
/// The constant value (for integers).
pub value: u64,
/// The type of the constant.
pub ty: Type,
/// The basic block where the constant is used.
pub block: ValueRef,
/// All instructions that use this constant.
pub uses: Vec<ValueRef>,
}
// ============================================================================
// Constant Hoisting Pass
// ============================================================================
/// Constant Hoisting pass.
///
/// Hoists expensive constants out of loops to reduce instruction
/// count and code size.
pub struct ConstantHoisting {
/// Number of constants hoisted.
pub hoisted: usize,
/// Target architecture string for cost model.
pub target: String,
}
impl ConstantHoisting {
/// Create a new ConstantHoisting pass for the given target.
pub fn new(target: &str) -> Self {
Self {
hoisted: 0,
target: target.to_string(),
}
}
/// Run constant hoisting on a function. Returns the number of
/// constants hoisted.
pub fn run_on_function(&mut self, func: &ValueRef) -> usize {
self.hoisted = 0;
// Find constants used in loops.
let constants = self.find_constants_in_loops(func);
// Try to hoist each candidate.
for info in &constants {
if self.can_hoist(info) && self.should_hoist(info, &info.uses) {
self.hoist_constant(info, func);
self.hoisted += 1;
}
}
self.hoisted
}
// ========================================================================
// Constant discovery in loops
// ========================================================================
/// Find constants used within loops in the function.
fn find_constants_in_loops(&self, func: &ValueRef) -> Vec<ConstantInfo> {
let mut constants: Vec<ConstantInfo> = Vec::new();
let f = func.borrow();
let blocks: Vec<ValueRef> = f
.operands
.iter()
.filter(|op| op.borrow().subclass == SubclassKind::BasicBlock)
.cloned()
.collect();
// Find loops (simplified: find backedges).
let loops = find_natural_loops(&blocks);
// For each loop, collect constants used within loop blocks.
for loop_info in &loops {
for bb in &loop_info.blocks {
let insts = get_block_instructions(bb);
for inst in &insts {
let ib = inst.borrow();
// For each operand that is a constant.
for operand in &ib.operands {
if operand.borrow().is_constant() {
let val = parse_constant_value(operand);
if let Some(v) = val {
constants.push(ConstantInfo {
value: v as u64,
ty: operand.borrow().ty.clone(),
block: bb.clone(),
uses: vec![inst.clone()],
});
}
}
}
}
}
}
// Merge duplicate constants (same value, same type).
constants = merge_constant_infos(constants);
constants
}
// ========================================================================
// Hoisting eligibility
// ========================================================================
/// Check if a constant can be hoisted (baseline check).
fn can_hoist(&self, info: &ConstantInfo) -> bool {
// Must have at least one use.
if info.uses.is_empty() {
return false;
}
// The constant must be expensive to materialize on this target.
if !is_expensive_constant(info.value, &self.target) {
return false;
}
true
}
/// Check if hoisting this constant is beneficial.
fn should_hoist(&self, info: &ConstantInfo, uses: &[ValueRef]) -> bool {
let savings = self.compute_savings(info);
let cost = self.compute_cost(info);
savings > cost
}
/// Hoist a constant: insert a materialization instruction in the
/// loop preheader and replace all loop-internal uses.
fn hoist_constant(&mut self, info: &ConstantInfo, func: &ValueRef) {
let f = func.borrow();
// Find the loop preheader (block before loop header).
let preheader = find_preheader(func, &info.block);
// Create the materialized constant in the preheader.
let hoisted_val = if info.ty.is_integer() {
llvm_native_core::constants::const_int(info.ty.clone(), info.value as i64)
} else {
return; // Only handle integer constants for now.
};
// Replace all uses within the loop with the hoisted value.
for use_inst in &info.uses {
// Replace the constant operand with the hoisted value.
let mut ub = use_inst.borrow_mut();
let indices_to_replace: Vec<usize> = ub
.operands
.iter()
.enumerate()
.filter_map(|(idx, operand)| {
let ob = operand.borrow();
if ob.is_constant() && ob.ty.id == info.ty.id {
if let Some(v) = parse_constant_value(operand) {
if v as u64 == info.value {
return Some(idx);
}
}
}
None
})
.collect();
for idx in indices_to_replace {
ub.operands[idx] = hoisted_val.clone();
}
}
let _ = (func, preheader);
}
// ========================================================================
// Cost model
// ========================================================================
/// Compute the savings from hoisting this constant.
fn compute_savings(&self, info: &ConstantInfo) -> i64 {
let uses = info.uses.len() as i64;
// Each use that avoids materializing an expensive constant
// saves `instruction_cost` instructions per use.
let cost_per_materialization = materialization_cost(info.value, &self.target);
// Savings = uses × cost_per_mat - 1 (the hoisted instruction)
uses * cost_per_materialization - 1
}
/// Compute the cost of hoisting (the materialization instruction
/// in the preheader).
fn compute_cost(&self, _info: &ConstantInfo) -> i64 {
// Cost is 1 instruction in the preheader.
1
}
}
impl Default for ConstantHoisting {
fn default() -> Self {
Self::new("generic")
}
}
// ============================================================================
// Target-specific cost model
// ============================================================================
/// Check if a constant is expensive to materialize on the given target.
fn is_expensive_constant(value: u64, target: &str) -> bool {
match target {
"x86_64" | "x86" => {
// On x86, 32-bit immediates are cheap (1 instruction).
// 64-bit constants that don't fit in 32 bits are expensive.
let fits_in_32 = (value as i64) >= 0 && value <= 0xFFFFFFFF;
!fits_in_32
}
"arm" | "aarch64" => {
// ARM: constants outside the 8-bit rotated immediate range
// require MOVW+MOVT or a load from constant pool.
!is_arm_immediate(value)
}
"riscv32" | "riscv64" => {
// RISC-V: constants > 12 bits require LUI+ADDI.
value > 0xFFF
}
_ => {
// Generic: assume anything > 16 bits is expensive.
value > 0xFFFF
}
}
}
/// Compute the number of instructions needed to materialize a constant
/// on the given target.
fn materialization_cost(value: u64, target: &str) -> i64 {
match target {
"x86_64" => {
if value <= 0xFFFFFFFF {
1
} else {
2 // movabs
}
}
"arm" => {
if is_arm_immediate(value) {
1
} else if value <= 0xFFFF {
2 // MOVW
} else {
4 // MOVW+MOVT = 2 instructions, or constant pool load
}
}
"riscv32" | "riscv64" => {
if value <= 0xFFF {
1 // ADDI
} else if value <= 0x7FFFF000 {
2 // LUI + ADDI
} else {
4 // LUI + ADDI + SLLI + ADDI for larger constants
}
}
_ => {
if value <= 0xFFFF {
1
} else {
2
}
}
}
}
/// Check if a value can be encoded as an ARM immediate (8-bit value
/// rotated by an even number of bits).
fn is_arm_immediate(value: u64) -> bool {
let v = value as u32;
if v <= 0xFF {
return true;
}
for rot in 0..16 {
let rotated = v.rotate_right((rot * 2) as u32);
if rotated <= 0xFF {
return true;
}
}
false
}
// ============================================================================
// Helper Functions
// ============================================================================
/// Get all instruction ValueRefs in a basic block in order.
fn get_block_instructions(bb: &ValueRef) -> Vec<ValueRef> {
bb.borrow()
.operands
.iter()
.filter(|op| op.borrow().subclass == SubclassKind::Instruction)
.cloned()
.collect()
}
/// Parse a constant integer value from a ValueRef.
fn parse_constant_value(val: &ValueRef) -> Option<i64> {
let vb = val.borrow();
if vb.is_constant() {
vb.name.parse::<i64>().ok()
} else {
None
}
}
/// Find natural loops by identifying backedges (simplified).
fn find_natural_loops(blocks: &[ValueRef]) -> Vec<llvm_native_core::analysis::LoopInfo> {
let mut loops = Vec::new();
let mut block_map: HashMap<usize, usize> = HashMap::new();
for (i, bb) in blocks.iter().enumerate() {
block_map.insert(bb.borrow().vid as usize, i);
}
for bb in blocks {
let succs = get_block_successors(bb);
for succ in &succs {
let succ_idx = block_map.get(&(succ.borrow().vid as usize));
let bb_idx = block_map.get(&(bb.borrow().vid as usize));
if let (Some(&si), Some(&bi)) = (succ_idx, bb_idx) {
if si <= bi {
let loop_blocks: Vec<ValueRef> = blocks[si..=bi].to_vec();
let li = llvm_native_core::analysis::LoopInfo {
header: blocks[si].clone(),
blocks: loop_blocks,
exits: Vec::new(),
latch: Some(bb.clone()),
preheader: if si > 0 {
Some(blocks[si - 1].clone())
} else {
None
},
depth: 0,
parent_loop: None,
is_simplified: false,
trip_count: None,
};
loops.push(li);
}
}
}
}
loops
}
/// Get successor blocks of a basic block.
fn get_block_successors(bb: &ValueRef) -> Vec<ValueRef> {
let mut succs = Vec::new();
let b = bb.borrow();
for inst in &b.operands {
let ib = inst.borrow();
if ib.subclass == SubclassKind::Instruction {
if let Some(op) = ib.opcode {
if op.is_terminator() {
for op_ref in &ib.operands {
if op_ref.borrow().subclass == SubclassKind::BasicBlock {
succs.push(op_ref.clone());
}
}
break;
}
}
}
}
succs
}
/// Find the preheader of a loop containing the given block (simplified).
fn find_preheader(func: &ValueRef, block_in_loop: &ValueRef) -> Option<ValueRef> {
let f = func.borrow();
let blocks: Vec<ValueRef> = f
.operands
.iter()
.filter(|op| op.borrow().subclass == SubclassKind::BasicBlock)
.cloned()
.collect();
// The preheader is the first block before the loop header
// that is not in the loop.
for (i, bb) in blocks.iter().enumerate() {
if bb.borrow().vid == block_in_loop.borrow().vid {
if i > 0 {
return Some(blocks[i - 1].clone());
}
}
}
None
}
/// Merge multiple ConstantInfo entries with the same value and type.
fn merge_constant_infos(constants: Vec<ConstantInfo>) -> Vec<ConstantInfo> {
let mut merged: Vec<ConstantInfo> = Vec::new();
for info in constants {
let existing = merged
.iter_mut()
.find(|m| m.value == info.value && m.ty.id == info.ty.id);
if let Some(ex) = existing {
ex.uses.extend(info.uses.clone());
} else {
merged.push(info);
}
}
merged
}
// ============================================================================
// Enhanced Constant Hoisting — Materialization Cost Model
// ============================================================================
impl ConstantHoisting {
/// Compute the materialization cost for a constant on the target.
/// Returns the number of instructions needed to materialize it.
pub fn materialization_cost(&self, value: u64, ty: &Type) -> u32 {
let bit_width = match &ty.kind {
llvm_native_core::types::TypeKind::Integer { bits } => *bits,
_ => 64,
};
match self.target.as_str() {
"x86_64" | "x86" => {
if value <= 0x7FFFFFFF {
1 // mov r, imm32
} else {
2 // movabs r, imm64
}
}
"aarch64" | "arm64" => {
if Self::can_encode_as_movz_movk(value) {
let chunks = Self::count_nonzero_16bit_chunks(value, bit_width as u32);
chunks.max(1)
} else {
4 // Worst case: movz + movk × 3
}
}
"arm" | "thumb" => {
if value <= 0xFF {
1 // mov r, #imm8
} else if value <= 0xFFFF {
2 // movw r, #imm16
} else {
2 // movw + movt
}
}
"riscv32" | "riscv64" => {
if (value as i64) >= -2048 && (value as i64) <= 2047 {
1 // addi with x0
} else if bit_width <= 32 && (value >> 12) < (1 << 20) {
2 // lui + addi
} else {
let chunks = Self::count_set_bits_required(value, bit_width as u32);
chunks.max(2)
}
}
_ => {
if value <= 0xFFFFFFFF {
1
} else {
2
}
}
}
}
/// Check if a value can be encoded with movz/movk on AArch64.
fn can_encode_as_movz_movk(value: u64) -> bool {
true // All 64-bit values can be encoded with movz + movk sequence
}
/// Count how many 16-bit chunks are non-zero.
fn count_nonzero_16bit_chunks(value: u64, bit_width: u32) -> u32 {
let chunks = (bit_width + 15) / 16;
let mut count = 0u32;
for i in 0..chunks {
let shift = i * 16;
let chunk = (value >> shift) & 0xFFFF;
if chunk != 0 {
count += 1;
}
}
count.max(1)
}
/// Count the number of instructions needed on RISC-V (set bits approach).
fn count_set_bits_required(value: u64, _bit_width: u32) -> u32 {
let popcount = value.count_ones();
if popcount <= 2 {
2 // lui + addi or similar
} else {
(popcount / 2).max(2)
}
}
/// Check if a constant is "large" (expensive to materialize).
pub fn is_large_constant(&self, value: u64, ty: &Type) -> bool {
self.materialization_cost(value, ty) >= 2
}
/// Find the optimal location to hoist a constant.
/// Prefers loop preheader, then nearest common dominator of all uses.
pub fn find_hoist_location(&self, uses: &[ValueRef], _func: &ValueRef) -> Option<ValueRef> {
if uses.is_empty() {
return None;
}
// Find all blocks containing uses
let mut blocks: Vec<ValueRef> = Vec::new();
for use_val in uses {
let u = use_val.borrow();
if let Some(ref parent) = u.parent {
if !blocks.iter().any(|b| b.borrow().vid == parent.borrow().vid) {
blocks.push(parent.clone());
}
}
}
// Return the first block (entry block is ideal if it dominates all uses)
blocks.first().cloned()
}
/// Compute the cost savings from hoisting a constant.
/// Savings = (materialization_cost × (loop_trip_count - 1))
pub fn compute_savings_with_trip_count(&self, info: &ConstantInfo, trip_count: u64) -> i64 {
let mat_cost = self.materialization_cost(info.value, &info.ty) as i64;
let uses_in_loop = info.uses.len() as i64;
let savings = mat_cost * (trip_count as i64 - 1) * uses_in_loop;
// Subtract the cost of one materialization in the preheader
savings - mat_cost
}
/// Group constant candidates by type and value for deduplication.
pub fn group_constants(candidates: &[ConstantInfo]) -> HashMap<(u64, u64), Vec<ConstantInfo>> {
let mut groups: HashMap<(u64, u64), Vec<ConstantInfo>> = HashMap::new();
for info in candidates {
let key = (info.ty.id.as_u64(), info.value);
groups.entry(key).or_default().push(info.clone());
}
groups
}
/// Emit a constant pool entry at the given location.
/// Creates the constant materialization instruction.
pub fn emit_constant_at(&self, value: u64, ty: &Type, location: &ValueRef) -> ValueRef {
llvm_native_core::constants::const_int(ty.clone(), value as i64)
}
/// Get constant FP hoisting candidates.
/// Looks for repeated floating-point constants in loops.
pub fn find_fp_constants_in_loops(&self, _func: &ValueRef) -> Vec<ConstantInfo> {
// FP constants from IR (e.g., 3.14159) can also be hoisted.
// Implementation would scan for ConstantFP values in loop blocks.
Vec::new()
}
}
// ============================================================================
// Constant Pool — collections of hoisted constants
// ============================================================================
/// A pool of hoisted constants with their materialized values.
#[derive(Debug, Clone, Default)]
pub struct ConstantPool {
/// Map from (type_id, value) to the materialized value.
pub entries: HashMap<(u64, u64), ValueRef>,
}
impl ConstantPool {
pub fn new() -> Self {
Self::default()
}
/// Look up or insert a constant in the pool.
pub fn get_or_insert(&mut self, value: u64, ty: &Type, location: &ValueRef) -> ValueRef {
let key = (ty.id.as_u64(), value);
if let Some(val) = self.entries.get(&key) {
return val.clone();
}
let materialized = llvm_native_core::constants::const_int(ty.clone(), value as i64);
self.entries.insert(key, materialized.clone());
materialized
}
/// Get the number of constants in the pool.
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
// ============================================================================
// Constant Hoisting Candidate Collection
// ============================================================================
impl ConstantHoisting {
/// Collect all constant candidates from a function.
/// Includes both integer and floating-point constants.
pub fn collect_all_candidates(&self, func: &ValueRef) -> Vec<ConstantInfo> {
let mut candidates = Vec::new();
let f = func.borrow();
for op in &f.operands {
let bb = op.borrow();
if !bb.is_basic_block() {
continue;
}
for inst_val in &bb.operands {
let inst = inst_val.borrow();
// Check each operand for constant values
for operand in &inst.operands {
let op_val = operand.borrow();
if op_val.subclass == SubclassKind::Constant
|| op_val.subclass == SubclassKind::ConstantInt
{
if let Ok(value) = op_val.name.parse::<u64>() {
candidates.push(ConstantInfo {
value,
ty: op_val.ty.clone(),
block: op.clone(),
uses: vec![inst_val.clone()],
});
}
}
}
}
}
candidates
}
/// Filter candidates to only those that are expensive to materialize.
pub fn filter_expensive(&self, candidates: &[ConstantInfo]) -> Vec<ConstantInfo> {
candidates
.iter()
.filter(|c| self.is_large_constant(c.value, &c.ty))
.cloned()
.collect()
}
/// Sort candidates by estimated savings (most savings first).
pub fn sort_by_savings(&self, candidates: &mut [ConstantInfo], trip_count: u64) {
candidates.sort_by_key(|c| -self.compute_savings_with_trip_count(c, trip_count));
}
/// Get the target-specific maximum immediate value that can be
/// encoded in one instruction.
pub fn max_immediate(&self) -> u64 {
match self.target.as_str() {
"x86_64" | "x86" => 0x7FFFFFFF,
"aarch64" | "arm64" => 0xFFFF,
"arm" | "thumb" => 0xFF,
"riscv32" | "riscv64" => 0x7FF,
_ => 0xFFFF,
}
}
/// Check if a constant requires a relocation (e.g., address of a global).
pub fn requires_relocation(&self, _value: u64) -> bool {
// Constants that are addresses of globals may need relocation
false
}
}
// ============================================================================
// Constant Hoist Site — optimal insertion point tracking
// ============================================================================
/// Tracks the optimal site to hoist a constant.
#[derive(Debug, Clone)]
pub struct HoistSite {
/// The basic block where the constant should be materialized.
pub block: ValueRef,
/// The constant value.
pub value: u64,
/// The constant type.
pub ty: Type,
/// Estimated number of instructions saved.
pub savings: i64,
/// Number of uses that will be replaced.
pub use_count: usize,
}
impl HoistSite {
pub fn new(block: ValueRef, value: u64, ty: Type, savings: i64, use_count: usize) -> Self {
Self {
block,
value,
ty,
savings,
use_count,
}
}
/// Returns true if hoisting is profitable.
pub fn is_profitable(&self) -> bool {
self.savings > 0
}
}
// ============================================================================
// Tests
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
use llvm_native_core::basic_block::new_basic_block;
use llvm_native_core::constants;
use llvm_native_core::function::new_function;
use llvm_native_core::instruction;
use llvm_native_core::types::Type;
fn build_simple_func(name: &str) -> ValueRef {
let func = new_function(name, Type::void(), &[]);
let entry = new_basic_block("entry");
entry.borrow_mut().push_operand(instruction::ret_void());
func.borrow_mut().push_operand(entry.clone());
func
}
fn build_const_in_loop_func() -> ValueRef {
let func = new_function("const_loop", Type::void(), &[]);
let entry = new_basic_block("entry");
let body = new_basic_block("body");
let exit = new_basic_block("exit");
entry
.borrow_mut()
.push_operand(instruction::br(body.clone()));
// Body: use a large constant in an add.
let big_const = constants::const_i64(0x123456789ABCDEF0u64 as i64);
let val = instruction::alloca(Type::i64());
let loaded = instruction::load(Type::i64(), val);
let add = instruction::add(loaded, big_const);
body.borrow_mut().push_operand(add);
body.borrow_mut().push_operand(instruction::br_cond(
constants::const_bool(true),
body.clone(),
exit.clone(),
));
exit.borrow_mut().push_operand(instruction::ret_void());
func.borrow_mut().push_operand(entry.clone());
func.borrow_mut().push_operand(body.clone());
func.borrow_mut().push_operand(exit.clone());
func
}
// === ConstantHoisting tests ===
#[test]
fn test_constant_hoisting_new() {
let ch = ConstantHoisting::new("x86_64");
assert_eq!(ch.hoisted, 0);
assert_eq!(ch.target, "x86_64");
}
#[test]
fn test_constant_hoisting_default() {
let ch = ConstantHoisting::default();
assert_eq!(ch.target, "generic");
}
#[test]
fn test_is_expensive_constant_x86() {
assert!(!is_expensive_constant(42, "x86_64"));
assert!(is_expensive_constant(0x123456789ABCDEF0, "x86_64"));
}
#[test]
fn test_is_expensive_constant_arm() {
assert!(!is_expensive_constant(0xFF, "arm"));
assert!(is_expensive_constant(0x12345678, "arm"));
}
#[test]
fn test_is_arm_immediate() {
assert!(is_arm_immediate(0xFF));
assert!(is_arm_immediate(0x3FC00)); // 0xFF rotated
assert!(!is_arm_immediate(0x101));
}
#[test]
fn test_materialization_cost() {
assert_eq!(materialization_cost(42, "x86_64"), 1);
assert_eq!(materialization_cost(0xFFFFFFFF, "x86_64"), 1);
assert_eq!(materialization_cost(0x123456789ABCDEF0, "x86_64"), 2);
}
#[test]
fn test_compute_savings() {
let ch = ConstantHoisting::new("riscv32");
let info = ConstantInfo {
value: 0x12345,
ty: Type::i32(),
block: new_basic_block("body"),
uses: vec![new_basic_block("u1"), new_basic_block("u2")],
};
let savings = ch.compute_savings(&info);
// 2 uses × 2 (LUI+ADDI) - 1 = 3
assert!(savings >= 0);
}
#[test]
fn test_run_on_function() {
let mut ch = ConstantHoisting::new("x86_64");
let func = build_const_in_loop_func();
let count = ch.run_on_function(&func);
// Function should be processed without errors.
assert!(count >= 0);
}
}