Skip to content

feat: preserve ABI types#1151

Open
djolertrk wants to merge 5 commits into
nextfrom
pr/preserve-func-records
Open

feat: preserve ABI types#1151
djolertrk wants to merge 5 commits into
nextfrom
pr/preserve-func-records

Conversation

@djolertrk

Copy link
Copy Markdown
Collaborator

Typed ABI/debug consumers need to distinguish the lowered execution ABI from the source-level/component ABI. Without preserving the semantic signature, tools only see flattened felt parameters/results and cannot recover types like account-id, word, or asset.

@djolertrk djolertrk requested review from bitwalker and greenhat May 29, 2026 12:46
@djolertrk

djolertrk commented May 29, 2026

Copy link
Copy Markdown
Collaborator Author

NOTE: For this to work end-to-end, we need a PR in VM, but since compiler is not migrated yet here is branch based on the latest supported version: https://github.com/walnuthq/miden-vm/tree/pr/debug-type-names-v0.22.3 (without this patch, we would see anonymous structs).

Basically, there we salvage ABI type names from user level into the .debug_types, instead of remembering raw felts only.

$ miden-objtool dump debug-info
...
  [  10] STRUCT miden:base/core-types@1.0.0/account-id (size: 8 bytes)
    +   0: prefix : struct miden:base/core-types@1.0.0/felt
    +   4: suffix : struct miden:base/core-types@1.0.0/felt

@bitwalker bitwalker left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is ok, but we really need integration test coverage here to make sure we don't end up encoding inaccurate signatures that we then later rely on for binding generation, etc.

.map(|result| masm::TypeExpr::from(result.ty.clone()))
.collect();
let signature = masm::FunctionType::new(sig.cc, args, results);
let signature =

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to make sure that this signature is the same as the Component ABI signature when the calling convention is component-model, or this signature will not be correct.

We at the very least need an integration test with a mixture of types that asserts that the ABI expressed by the "semantic" signature here, and the one actually expressed at the Component ABI level are the same, so that we can catch drift between them.

I'm expecting that in general we'll be relying on WIT for this anyway once I finish up the binding generation work - but I'm fine with the signature we encode in MASM/package metadata to match the Component ABI types, since we must always derive the flattened representation for codegen anyway.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! It is addressed!

@greenhat greenhat left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good overall! Please see my comments.

Comment thread codegen/masm/src/lower/component.rs Outdated
.with_span(SourceSpan::UNKNOWN),
)
}
Type::Ptr(ptr) => masm::TypeExpr::Ptr(masm::PointerType::new(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PointerType::new defaults to the element address space. Should we preserve ptr's address space?
Also, we have a similar (to masm_component_type_expr_from_hir) conversion already defined in miden-assembly-syntax:

impl From<Type> for TypeExpr {
    fn from(ty: Type) -> Self {
        match ty {
            Type::Array(t) => Self::Array(ArrayType::new(t.element_type().clone().into(), t.len())),
            Type::Struct(t) => Self::Struct(StructType::new(
                None,
                t.fields().iter().enumerate().map(|(i, ft)| {
                    let name = Ident::new(format!("field{i}")).unwrap();
                    StructField {
                        span: SourceSpan::UNKNOWN,
                        name,
                        ty: ft.ty.clone().into(),
                    }
                }),
            )),
            Type::Ptr(t) => Self::Ptr((*t).clone().into()),
            Type::Function(_) => {
                Self::Ptr(PointerType::new(TypeExpr::Primitive(Span::unknown(Type::Felt))))
            },
            Type::List(t) => Self::Ptr(
                PointerType::new((*t).clone().into()).with_address_space(AddressSpace::Byte),
            ),
            Type::I128 | Type::U128 => Self::Array(ArrayType::new(Type::U32.into(), 4)),
            Type::I64 | Type::U64 => Self::Array(ArrayType::new(Type::U32.into(), 2)),
            Type::Unknown | Type::Never | Type::F64 => panic!("unrepresentable type value: {ty}"),
            ty => Self::Primitive(Span::unknown(ty)),
        }
    }
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, nice catch! Thanks!!

0,
body,
);
start.extend_invoked(invoked);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The component export wrappers inject init and truncate_stack with raw Exec ops. We should track them as well.

body.push(masm::Op::Inst(Span::new(span, masm::Instruction::Exec(truncate_stack))));

.push(masm::Op::Inst(Span::new(span, masm::Instruction::Exec(init))));

@djolertrk djolertrk requested a review from greenhat June 5, 2026 14:17

@greenhat greenhat left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see my note regarding the conversions.

Comment thread codegen/masm/src/lower/component.rs Outdated
Some(masm::FunctionType::new(ty.calling_convention(), args, results))
}

fn masm_component_type_expr_from_hir(ty: &Type) -> masm::TypeExpr {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked again and I did not find any difference between this conversion and the one I posted above from the miden-assembly-syntax crate. Is there any reason for not using the one from miden-assembly-syntax?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the thing is that there is a difference, see, like

TypeExpr::from(Type::U64) becomes [u32; 2],

and the function I am adding (now renamed) component_abi_type_expr_from_hir(Type::U64) stays u64

Comment thread codegen/masm/src/lower/component.rs Outdated
}
}

fn masm_type_expr_from_hir(ty: &Type) -> masm::TypeExpr {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This conversion differs from the masm_component_type_expr_from_hir only by handling I64 and I128 types.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it is a duplicate, leftover -- I locally handled it

@greenhat greenhat left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@djolertrk

Copy link
Copy Markdown
Collaborator Author

can we merge this one? :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants