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
//! The LLVM target machine.
use crate::optimizer::settings::size_level::SizeLevel as OptimizerSettingsSizeLevel;
use crate::optimizer::settings::Settings as OptimizerSettings;
use self::target::Target;
pub mod target;
/// The LLVM target machine.
#[derive(Debug)]
pub struct TargetMachine {
/// The LLVM target.
target: Target,
/// The LLVM target machine reference.
target_machine: inkwell::targets::TargetMachine,
/// The optimizer settings.
optimizer_settings: OptimizerSettings,
}
impl TargetMachine {
/// The LLVM target name.
pub const VM_TARGET_NAME: &'static str = "riscv64";
/// The LLVM target triple.
pub const VM_TARGET_TRIPLE: &'static str = "riscv64-unknown-unknown-elf";
pub const VM_TARGET_CPU: &'static str = "generic-rv64";
/// LLVM target features.
pub const VM_FEATURES: &'static str =
"+e,+m,+a,+c,+zbb,+auipc-addi-fusion,+ld-add-fusion,+lui-addi-fusion,+xtheadcondmov,+relax";
/// RISC-V backend feature that lets the code generator emit single (potentially unaligned)
/// scalar memory accesses instead of splitting them into byte-wise sequences. Enabled for
/// newyork modules only; the stock Yul path omits it, matching the upstream target
/// configuration. Enabling it on the Yul path was observed to introduce an end-to-end
/// regression (`complex/internal_function_pointers/mixed_features_2`); the exact interaction
/// was not isolated, so the feature is restricted to the path it was validated against.
pub const VM_FEATURE_UNALIGNED: &'static str = ",+unaligned-scalar-mem";
/// A shortcut constructor.
/// A separate instance for every optimization level is created.
///
/// `unaligned_scalar_mem` appends [`Self::VM_FEATURE_UNALIGNED`]; pass `true` only for newyork
/// modules (see the constant's documentation).
pub fn new(
target: Target,
optimizer_settings: &OptimizerSettings,
unaligned_scalar_mem: bool,
) -> anyhow::Result<Self> {
let features = if unaligned_scalar_mem {
format!("{}{}", Self::VM_FEATURES, Self::VM_FEATURE_UNALIGNED)
} else {
Self::VM_FEATURES.to_string()
};
let target_machine = inkwell::targets::Target::from_name(target.name())
.ok_or_else(|| anyhow::anyhow!("LLVM target machine `{}` not found", target.name()))?
.create_target_machine(
&inkwell::targets::TargetTriple::create(target.triple()),
Self::VM_TARGET_CPU,
&features,
optimizer_settings.level_back_end,
inkwell::targets::RelocMode::PIC,
inkwell::targets::CodeModel::Default,
)
.ok_or_else(|| {
anyhow::anyhow!(
"LLVM target machine `{}` initialization error",
target.name(),
)
})?;
Ok(Self {
target,
target_machine,
optimizer_settings: optimizer_settings.to_owned(),
})
}
/// Writes the LLVM module to a memory buffer.
pub fn write_to_memory_buffer(
&self,
module: &inkwell::module::Module,
) -> Result<inkwell::memory_buffer::MemoryBuffer<'static>, inkwell::support::LLVMString> {
match self.target {
Target::PVM => self
.target_machine
.write_to_memory_buffer(module, inkwell::targets::FileType::Object),
}
}
/// Runs the optimization passes on `module`.
///
/// The pass builder's `MergeFunctions` insertion is disabled for both the stock and newyork
/// pipelines. It can collapse `@deploy` and `@call` into a single function body when their
/// lowered control flow becomes equivalent (e.g. a bare `invalid()` in both code sections);
/// the resulting ELF then has two exports pointing at the same block, which triggers a
/// non-determinism bug in polkavm-linker's reachability consistency check (the exports are
/// stored as `Vec<usize>` and compared by order, so equal-but-differently-ordered vectors
/// are rejected as inconsistent). Disabling the pass keeps the export targets distinct.
///
/// Disabling it on the stock path as well (not only for newyork) is intentional and not a
/// size regression: the polkavm-linker performs its own function deduplication/outlining, so
/// on the OpenZeppelin corpus dropping LLVM's `MergeFunctions` leaves real contracts slightly
/// *smaller* rather than larger (only trivial interface blobs differ, by a few bytes). The
/// newyork pipeline still merges where it helps, via the explicit `mergefunc` passes in its
/// pass string.
pub fn run_optimization_passes(
&self,
module: &inkwell::module::Module,
passes: &str,
) -> Result<(), inkwell::support::LLVMString> {
let pass_builder_options = inkwell::passes::PassBuilderOptions::create();
pass_builder_options.set_verify_each(self.optimizer_settings.is_verify_each_enabled);
pass_builder_options.set_debug_logging(self.optimizer_settings.is_debug_logging_enabled);
pass_builder_options.set_loop_unrolling(
self.optimizer_settings.level_middle_end_size == OptimizerSettingsSizeLevel::Zero,
);
pass_builder_options.set_merge_functions(false);
module.run_passes(passes, &self.target_machine, pass_builder_options)
}
/// Returns the target triple.
pub fn get_triple(&self) -> inkwell::targets::TargetTriple {
self.target_machine.get_triple()
}
/// Returns the target data.
pub fn get_target_data(&self) -> inkwell::targets::TargetData {
self.target_machine.get_target_data()
}
}