Allow serializing all cranelift-module data structures (#6172)
* Remove ModuleCompiledFunction The same information can be retrieved using ctx.compiled_code().unwrap().code_info().total_size In addition for Module implementations that don't immediately compile the given function there is no correct value that can be returned. * Don't give anonymous functions and data objects an internal name This internal name can conflict if a module is serialized and then deserialized into another module. It also wasn't used by any of the Module implementations anyway. * Allow serializing all cranelift-module data structures This allows a Module implementation to serialize it's internal state and deserialize it in another compilation session. For example to implement LTO or to load the module into cranelift-interpreter. * Use expect
This commit is contained in:
@@ -13,6 +13,7 @@ use crate::ModuleExtName;
|
||||
|
||||
/// This specifies how data is to be initialized.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
#[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Init {
|
||||
/// This indicates that no initialization has been specified yet.
|
||||
Uninitialized,
|
||||
@@ -41,6 +42,7 @@ impl Init {
|
||||
|
||||
/// A description of a data object.
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct DataDescription {
|
||||
/// How the data should be initialized.
|
||||
pub init: Init,
|
||||
|
||||
@@ -43,8 +43,7 @@ mod traps;
|
||||
pub use crate::data_context::{DataDescription, Init};
|
||||
pub use crate::module::{
|
||||
DataDeclaration, DataId, FuncId, FuncOrDataId, FunctionDeclaration, Linkage, Module,
|
||||
ModuleCompiledFunction, ModuleDeclarations, ModuleError, ModuleExtName, ModuleReloc,
|
||||
ModuleResult,
|
||||
ModuleDeclarations, ModuleError, ModuleExtName, ModuleReloc, ModuleResult,
|
||||
};
|
||||
pub use crate::traps::TrapSite;
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@ use crate::data_context::DataDescription;
|
||||
use core::fmt::Display;
|
||||
use cranelift_codegen::binemit::{CodeOffset, Reloc};
|
||||
use cranelift_codegen::entity::{entity_impl, PrimaryMap};
|
||||
use cranelift_codegen::ir::Function;
|
||||
use cranelift_codegen::ir::function::{Function, VersionMarker};
|
||||
use cranelift_codegen::settings::SetError;
|
||||
use cranelift_codegen::{binemit, MachReloc};
|
||||
use cranelift_codegen::MachReloc;
|
||||
use cranelift_codegen::{ir, isa, CodegenError, CompileError, Context};
|
||||
use cranelift_control::ControlPlane;
|
||||
use std::borrow::ToOwned;
|
||||
use std::borrow::{Cow, ToOwned};
|
||||
use std::string::String;
|
||||
|
||||
/// A module relocation.
|
||||
@@ -55,6 +55,7 @@ impl ModuleReloc {
|
||||
|
||||
/// A function identifier for use in the `Module` interface.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct FuncId(u32);
|
||||
entity_impl!(FuncId, "funcid");
|
||||
|
||||
@@ -82,6 +83,7 @@ impl FuncId {
|
||||
|
||||
/// A data object identifier for use in the `Module` interface.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct DataId(u32);
|
||||
entity_impl!(DataId, "dataid");
|
||||
|
||||
@@ -109,6 +111,7 @@ impl DataId {
|
||||
|
||||
/// Linkage refers to where an entity is defined and who can see it.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Linkage {
|
||||
/// Defined outside of a module.
|
||||
Import,
|
||||
@@ -167,6 +170,7 @@ impl Linkage {
|
||||
|
||||
/// A declared name may refer to either a function or data declaration
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
||||
#[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum FuncOrDataId {
|
||||
/// When it's a FuncId
|
||||
Func(FuncId),
|
||||
@@ -186,9 +190,10 @@ impl From<FuncOrDataId> for ModuleExtName {
|
||||
|
||||
/// Information about a function which can be called.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct FunctionDeclaration {
|
||||
#[allow(missing_docs)]
|
||||
pub name: String,
|
||||
pub name: Option<String>,
|
||||
#[allow(missing_docs)]
|
||||
pub linkage: Linkage,
|
||||
#[allow(missing_docs)]
|
||||
@@ -196,11 +201,29 @@ pub struct FunctionDeclaration {
|
||||
}
|
||||
|
||||
impl FunctionDeclaration {
|
||||
fn merge(&mut self, linkage: Linkage, sig: &ir::Signature) -> Result<(), ModuleError> {
|
||||
/// The linkage name of the function.
|
||||
///
|
||||
/// Synthesized from the given function id if it is an anonymous function.
|
||||
pub fn linkage_name(&self, id: FuncId) -> Cow<'_, str> {
|
||||
match &self.name {
|
||||
Some(name) => Cow::Borrowed(name),
|
||||
// Symbols starting with .L are completely omitted from the symbol table after linking.
|
||||
// Using hexadecimal instead of decimal for slightly smaller symbol names and often
|
||||
// slightly faster linking.
|
||||
None => Cow::Owned(format!(".Lfn{:x}", id.as_u32())),
|
||||
}
|
||||
}
|
||||
|
||||
fn merge(
|
||||
&mut self,
|
||||
id: FuncId,
|
||||
linkage: Linkage,
|
||||
sig: &ir::Signature,
|
||||
) -> Result<(), ModuleError> {
|
||||
self.linkage = Linkage::merge(self.linkage, linkage);
|
||||
if &self.signature != sig {
|
||||
return Err(ModuleError::IncompatibleSignature(
|
||||
self.name.clone(),
|
||||
self.linkage_name(id).into_owned(),
|
||||
self.signature.clone(),
|
||||
sig.clone(),
|
||||
));
|
||||
@@ -325,9 +348,10 @@ pub type ModuleResult<T> = Result<T, ModuleError>;
|
||||
|
||||
/// Information about a data object which can be accessed.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct DataDeclaration {
|
||||
#[allow(missing_docs)]
|
||||
pub name: String,
|
||||
pub name: Option<String>,
|
||||
#[allow(missing_docs)]
|
||||
pub linkage: Linkage,
|
||||
#[allow(missing_docs)]
|
||||
@@ -337,6 +361,19 @@ pub struct DataDeclaration {
|
||||
}
|
||||
|
||||
impl DataDeclaration {
|
||||
/// The linkage name of the data object.
|
||||
///
|
||||
/// Synthesized from the given data id if it is an anonymous function.
|
||||
pub fn linkage_name(&self, id: DataId) -> Cow<'_, str> {
|
||||
match &self.name {
|
||||
Some(name) => Cow::Borrowed(name),
|
||||
// Symbols starting with .L are completely omitted from the symbol table after linking.
|
||||
// Using hexadecimal instead of decimal for slightly smaller symbol names and often
|
||||
// slightly faster linking.
|
||||
None => Cow::Owned(format!(".Ldata{:x}", id.as_u32())),
|
||||
}
|
||||
}
|
||||
|
||||
fn merge(&mut self, linkage: Linkage, writable: bool, tls: bool) {
|
||||
self.linkage = Linkage::merge(self.linkage, linkage);
|
||||
self.writable = self.writable || writable;
|
||||
@@ -349,6 +386,7 @@ impl DataDeclaration {
|
||||
|
||||
/// A translated `ExternalName` into something global we can handle.
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum ModuleExtName {
|
||||
/// User defined function, converted from `ExternalName::User`.
|
||||
User {
|
||||
@@ -384,11 +422,236 @@ impl Display for ModuleExtName {
|
||||
/// into `FunctionDeclaration`s and `DataDeclaration`s.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ModuleDeclarations {
|
||||
/// A version marker used to ensure that serialized clif ir is never deserialized with a
|
||||
/// different version of Cranelift.
|
||||
// Note: This must be the first field to ensure that Serde will deserialize it before
|
||||
// attempting to deserialize other fields that are potentially changed between versions.
|
||||
_version_marker: VersionMarker,
|
||||
|
||||
names: HashMap<String, FuncOrDataId>,
|
||||
functions: PrimaryMap<FuncId, FunctionDeclaration>,
|
||||
data_objects: PrimaryMap<DataId, DataDeclaration>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "enable-serde")]
|
||||
mod serialize {
|
||||
// This is manually implementing Serialize and Deserialize to avoid serializing the names field,
|
||||
// which can be entirely reconstructed from the functions and data_objects fields, saving space.
|
||||
|
||||
use super::*;
|
||||
|
||||
use serde::de::{Deserialize, Deserializer, Error, MapAccess, SeqAccess, Unexpected, Visitor};
|
||||
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
||||
use std::fmt;
|
||||
|
||||
fn get_names<E: Error>(
|
||||
functions: &PrimaryMap<FuncId, FunctionDeclaration>,
|
||||
data_objects: &PrimaryMap<DataId, DataDeclaration>,
|
||||
) -> Result<HashMap<String, FuncOrDataId>, E> {
|
||||
let mut names = HashMap::new();
|
||||
for (func_id, decl) in functions.iter() {
|
||||
if let Some(name) = &decl.name {
|
||||
let old = names.insert(name.clone(), FuncOrDataId::Func(func_id));
|
||||
if old.is_some() {
|
||||
return Err(E::invalid_value(
|
||||
Unexpected::Other("duplicate name"),
|
||||
&"FunctionDeclaration's with no duplicate names",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (data_id, decl) in data_objects.iter() {
|
||||
if let Some(name) = &decl.name {
|
||||
let old = names.insert(name.clone(), FuncOrDataId::Data(data_id));
|
||||
if old.is_some() {
|
||||
return Err(E::invalid_value(
|
||||
Unexpected::Other("duplicate name"),
|
||||
&"DataDeclaration's with no duplicate names",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(names)
|
||||
}
|
||||
|
||||
impl Serialize for ModuleDeclarations {
|
||||
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
|
||||
let ModuleDeclarations {
|
||||
_version_marker,
|
||||
functions,
|
||||
data_objects,
|
||||
names: _,
|
||||
} = self;
|
||||
|
||||
let mut state = s.serialize_struct("ModuleDeclarations", 4)?;
|
||||
state.serialize_field("_version_marker", _version_marker)?;
|
||||
state.serialize_field("functions", functions)?;
|
||||
state.serialize_field("data_objects", data_objects)?;
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
enum ModuleDeclarationsField {
|
||||
VersionMarker,
|
||||
Functions,
|
||||
DataObjects,
|
||||
Ignore,
|
||||
}
|
||||
|
||||
struct ModuleDeclarationsFieldVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for ModuleDeclarationsFieldVisitor {
|
||||
type Value = ModuleDeclarationsField;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str("field identifier")
|
||||
}
|
||||
|
||||
fn visit_u64<E: Error>(self, val: u64) -> Result<Self::Value, E> {
|
||||
match val {
|
||||
0u64 => Ok(ModuleDeclarationsField::VersionMarker),
|
||||
1u64 => Ok(ModuleDeclarationsField::Functions),
|
||||
2u64 => Ok(ModuleDeclarationsField::DataObjects),
|
||||
_ => Ok(ModuleDeclarationsField::Ignore),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_str<E: Error>(self, val: &str) -> Result<Self::Value, E> {
|
||||
match val {
|
||||
"_version_marker" => Ok(ModuleDeclarationsField::VersionMarker),
|
||||
"functions" => Ok(ModuleDeclarationsField::Functions),
|
||||
"data_objects" => Ok(ModuleDeclarationsField::DataObjects),
|
||||
_ => Ok(ModuleDeclarationsField::Ignore),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_bytes<E: Error>(self, val: &[u8]) -> Result<Self::Value, E> {
|
||||
match val {
|
||||
b"_version_marker" => Ok(ModuleDeclarationsField::VersionMarker),
|
||||
b"functions" => Ok(ModuleDeclarationsField::Functions),
|
||||
b"data_objects" => Ok(ModuleDeclarationsField::DataObjects),
|
||||
_ => Ok(ModuleDeclarationsField::Ignore),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ModuleDeclarationsField {
|
||||
#[inline]
|
||||
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
|
||||
d.deserialize_identifier(ModuleDeclarationsFieldVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct ModuleDeclarationsVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ModuleDeclarationsVisitor {
|
||||
type Value = ModuleDeclarations;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str("struct ModuleDeclarations")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
|
||||
let _version_marker = match seq.next_element()? {
|
||||
Some(val) => val,
|
||||
None => {
|
||||
return Err(Error::invalid_length(
|
||||
0usize,
|
||||
&"struct ModuleDeclarations with 4 elements",
|
||||
));
|
||||
}
|
||||
};
|
||||
let functions = match seq.next_element()? {
|
||||
Some(val) => val,
|
||||
None => {
|
||||
return Err(Error::invalid_length(
|
||||
2usize,
|
||||
&"struct ModuleDeclarations with 4 elements",
|
||||
));
|
||||
}
|
||||
};
|
||||
let data_objects = match seq.next_element()? {
|
||||
Some(val) => val,
|
||||
None => {
|
||||
return Err(Error::invalid_length(
|
||||
3usize,
|
||||
&"struct ModuleDeclarations with 4 elements",
|
||||
));
|
||||
}
|
||||
};
|
||||
let names = get_names(&functions, &data_objects)?;
|
||||
Ok(ModuleDeclarations {
|
||||
_version_marker,
|
||||
names,
|
||||
functions,
|
||||
data_objects,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
|
||||
let mut _version_marker: Option<VersionMarker> = None;
|
||||
let mut functions: Option<PrimaryMap<FuncId, FunctionDeclaration>> = None;
|
||||
let mut data_objects: Option<PrimaryMap<DataId, DataDeclaration>> = None;
|
||||
while let Some(key) = map.next_key::<ModuleDeclarationsField>()? {
|
||||
match key {
|
||||
ModuleDeclarationsField::VersionMarker => {
|
||||
if _version_marker.is_some() {
|
||||
return Err(Error::duplicate_field("_version_marker"));
|
||||
}
|
||||
_version_marker = Some(map.next_value()?);
|
||||
}
|
||||
ModuleDeclarationsField::Functions => {
|
||||
if functions.is_some() {
|
||||
return Err(Error::duplicate_field("functions"));
|
||||
}
|
||||
functions = Some(map.next_value()?);
|
||||
}
|
||||
ModuleDeclarationsField::DataObjects => {
|
||||
if data_objects.is_some() {
|
||||
return Err(Error::duplicate_field("data_objects"));
|
||||
}
|
||||
data_objects = Some(map.next_value()?);
|
||||
}
|
||||
_ => {
|
||||
map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
let _version_marker = match _version_marker {
|
||||
Some(_version_marker) => _version_marker,
|
||||
None => return Err(Error::missing_field("_version_marker")),
|
||||
};
|
||||
let functions = match functions {
|
||||
Some(functions) => functions,
|
||||
None => return Err(Error::missing_field("functions")),
|
||||
};
|
||||
let data_objects = match data_objects {
|
||||
Some(data_objects) => data_objects,
|
||||
None => return Err(Error::missing_field("data_objects")),
|
||||
};
|
||||
let names = get_names(&functions, &data_objects)?;
|
||||
Ok(ModuleDeclarations {
|
||||
_version_marker,
|
||||
names,
|
||||
functions,
|
||||
data_objects,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ModuleDeclarations {
|
||||
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
|
||||
d.deserialize_struct(
|
||||
"ModuleDeclarations",
|
||||
&["_version_marker", "functions", "data_objects"],
|
||||
ModuleDeclarationsVisitor,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleDeclarations {
|
||||
/// Get the module identifier for a given name, if that name
|
||||
/// has been declared.
|
||||
@@ -439,7 +702,7 @@ impl ModuleDeclarations {
|
||||
Occupied(entry) => match *entry.get() {
|
||||
FuncOrDataId::Func(id) => {
|
||||
let existing = &mut self.functions[id];
|
||||
existing.merge(linkage, signature)?;
|
||||
existing.merge(id, linkage, signature)?;
|
||||
Ok((id, existing.linkage))
|
||||
}
|
||||
FuncOrDataId::Data(..) => {
|
||||
@@ -448,7 +711,7 @@ impl ModuleDeclarations {
|
||||
},
|
||||
Vacant(entry) => {
|
||||
let id = self.functions.push(FunctionDeclaration {
|
||||
name: name.to_owned(),
|
||||
name: Some(name.to_owned()),
|
||||
linkage,
|
||||
signature: signature.clone(),
|
||||
});
|
||||
@@ -464,11 +727,10 @@ impl ModuleDeclarations {
|
||||
signature: &ir::Signature,
|
||||
) -> ModuleResult<FuncId> {
|
||||
let id = self.functions.push(FunctionDeclaration {
|
||||
name: String::new(),
|
||||
name: None,
|
||||
linkage: Linkage::Local,
|
||||
signature: signature.clone(),
|
||||
});
|
||||
self.functions[id].name = format!(".L{:?}", id);
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
@@ -496,7 +758,7 @@ impl ModuleDeclarations {
|
||||
},
|
||||
Vacant(entry) => {
|
||||
let id = self.data_objects.push(DataDeclaration {
|
||||
name: name.to_owned(),
|
||||
name: Some(name.to_owned()),
|
||||
linkage,
|
||||
writable,
|
||||
tls,
|
||||
@@ -510,22 +772,15 @@ impl ModuleDeclarations {
|
||||
/// Declare an anonymous data object in this module.
|
||||
pub fn declare_anonymous_data(&mut self, writable: bool, tls: bool) -> ModuleResult<DataId> {
|
||||
let id = self.data_objects.push(DataDeclaration {
|
||||
name: String::new(),
|
||||
name: None,
|
||||
linkage: Linkage::Local,
|
||||
writable,
|
||||
tls,
|
||||
});
|
||||
self.data_objects[id].name = format!(".L{:?}", id);
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about the compiled function.
|
||||
pub struct ModuleCompiledFunction {
|
||||
/// The size of the compiled function.
|
||||
pub size: binemit::CodeOffset,
|
||||
}
|
||||
|
||||
/// A `Module` is a utility for collecting functions and data objects, and linking them together.
|
||||
pub trait Module {
|
||||
/// Return the `TargetIsa` to compile for.
|
||||
@@ -660,11 +915,7 @@ pub trait Module {
|
||||
/// Note: After calling this function the given `Context` will contain the compiled function.
|
||||
///
|
||||
/// [`define_function_with_control_plane`]: Self::define_function_with_control_plane
|
||||
fn define_function(
|
||||
&mut self,
|
||||
func: FuncId,
|
||||
ctx: &mut Context,
|
||||
) -> ModuleResult<ModuleCompiledFunction> {
|
||||
fn define_function(&mut self, func: FuncId, ctx: &mut Context) -> ModuleResult<()> {
|
||||
self.define_function_with_control_plane(func, ctx, &mut ControlPlane::default())
|
||||
}
|
||||
|
||||
@@ -678,7 +929,7 @@ pub trait Module {
|
||||
func: FuncId,
|
||||
ctx: &mut Context,
|
||||
ctrl_plane: &mut ControlPlane,
|
||||
) -> ModuleResult<ModuleCompiledFunction>;
|
||||
) -> ModuleResult<()>;
|
||||
|
||||
/// Define a function, taking the function body from the given `bytes`.
|
||||
///
|
||||
@@ -694,7 +945,7 @@ pub trait Module {
|
||||
alignment: u64,
|
||||
bytes: &[u8],
|
||||
relocs: &[MachReloc],
|
||||
) -> ModuleResult<ModuleCompiledFunction>;
|
||||
) -> ModuleResult<()>;
|
||||
|
||||
/// Define a data object, producing the data contents from the given `DataContext`.
|
||||
fn define_data(&mut self, data_id: DataId, data: &DataDescription) -> ModuleResult<()>;
|
||||
@@ -776,11 +1027,7 @@ impl<M: Module> Module for &mut M {
|
||||
(**self).declare_data_in_data(data_id, data)
|
||||
}
|
||||
|
||||
fn define_function(
|
||||
&mut self,
|
||||
func: FuncId,
|
||||
ctx: &mut Context,
|
||||
) -> ModuleResult<ModuleCompiledFunction> {
|
||||
fn define_function(&mut self, func: FuncId, ctx: &mut Context) -> ModuleResult<()> {
|
||||
(**self).define_function(func, ctx)
|
||||
}
|
||||
|
||||
@@ -789,7 +1036,7 @@ impl<M: Module> Module for &mut M {
|
||||
func: FuncId,
|
||||
ctx: &mut Context,
|
||||
ctrl_plane: &mut ControlPlane,
|
||||
) -> ModuleResult<ModuleCompiledFunction> {
|
||||
) -> ModuleResult<()> {
|
||||
(**self).define_function_with_control_plane(func, ctx, ctrl_plane)
|
||||
}
|
||||
|
||||
@@ -800,7 +1047,7 @@ impl<M: Module> Module for &mut M {
|
||||
alignment: u64,
|
||||
bytes: &[u8],
|
||||
relocs: &[MachReloc],
|
||||
) -> ModuleResult<ModuleCompiledFunction> {
|
||||
) -> ModuleResult<()> {
|
||||
(**self).define_function_bytes(func_id, func, alignment, bytes, relocs)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user