Skip to content

Handle AArch32 immediates and literal/displacement ranges#23

Open
fhunleth wants to merge 1 commit into
stritzinger:arm32-jitfrom
fhunleth:fh-arm32-jit-fix-atoms
Open

Handle AArch32 immediates and literal/displacement ranges#23
fhunleth wants to merge 1 commit into
stritzinger:arm32-jitfrom
fhunleth:fh-arm32-jit-fix-atoms

Conversation

@fhunleth

Copy link
Copy Markdown

To see a crash that this fixes, run memory(atom). This was the key change that got the ARM32 JIT running on a small Nerves project.

@ziopio

ziopio commented Jun 30, 2026

Copy link
Copy Markdown

I do not experience the crash on my emulator, but this could be something really dependant on your call args and maybe also the fact that is a real system.

Is this happening only on the hardware or did it happen also in the emulator?
What is your development branch? I might inspect it to see further differences in your build config.
Can you give me the command line you used to trigger the error?
also if you did use another configuration file, could you give it to me?

I tried to build with debug and I cannot reproduce the error, I get instead:

1> memory(atom).
** exception error: notsup
     in function  erlang:memory/1
        called as erlang:memory(atom)
        *** argument 1: invalid memory type option
2> 

This is happening because many allocators are disabled by default with -DSMALL_MEMORY in the configure settings

So I tried to add -Mea max -MTe true to the boot args to enable everything and I got it to work:

Eshell V15.0 (press Ctrl+G to abort, type help(). for help)
1> memory(atom).
209005

@ziopio ziopio left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

There are multiple changes that hide the real FIX, most of them are good addition, but need to be separated:
Ideally we need to separate:

  • actual fix
  • eventual refactoring if needed
  • optimizations/enhancements
  • cleanups

See my comments, I can help of course to move code and refactor the code where necessary, I would really like to reproduce the crash you are getting...

}
}

void safe_str(a32::Gp gp, arm::Mem mem) {

@ziopio ziopio Jun 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Are these safe_* wrappers really needed in BeamAssembler? in case they are, I would like to reduce code repetition as much as possible.

void safe_str(a32::Gp gp, arm::Mem mem) {
size_t abs_offset = std::abs(mem.offset());
auto offset = mem.offset();
constexpr size_t max_disp = 4095;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Check the Displacement enum, it is better to use disp4KB, I know it is not defined for the BeamAssembler, if needed we would have to refactor this to avoid code repetitions.

/* Save our return address in c_p->i so we can tell where we crashed if we
* did so during GC. */
a.str(a32::lr, arm::Mem(c_p, offsetof(Process, i)));
safe_str(a32::lr, arm::Mem(c_p, offsetof(Process, i)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You do not need safe_str here, offsetof(Process, i) is a predictable integer and we know it can be rappresented by an immediate with 12bits.

It is really important that the safe_str, safe_ldr ecc... functions are NOT spammed through the code base, I should probably comment a disclaimer on them.

They make the LDR/STR OP safe regarding the displacement handling. But they are also very dangerous if abused.

These functions use either TMP or VAR as temporary storage to compute and load the address. TMP and VAR are not an esclusive of these wrappers. This means that you could have callers of these functions storing important data on TMP and/or VAR and then use LDR and STR. In these cases, if you swap a a.ldr with a safe_ldr to fix a bug you could introduce a very HARD to detect register corruption...

So in your case I do not know which safe_* function solved your problem.
I really doubt every change here is necessary, so I would like to pin point the real fix and the check it is safe. It probably is safe since it works, but we cannot give it for granted...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I should rename it like: dynamic_str or something like that... on arm64 they have a really safe version, I kept the name, but did not get the luxury of safety...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ah also sub and add can overwrite TMP and must be used with caution

a.orr(TMP, TMP, imm(F_DELAY_GC));
a.str(TMP, flags);
a.str(ARG1, arm::Mem(c_p, offsetof(Process, i)));
safe_str(ARG1, arm::Mem(c_p, offsetof(Process, i)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is a perfect example: using safe_str here is not needed, if you would actually need it, it would corrupt TMP...

bool rhs_is_arm_literal = RHS.isSmall() &&
canEncodeAArch32Imm(
RHS.as<ArgSmall>().get() &
~_TAG_IMMED1_MASK);

@ziopio ziopio Jun 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is very cool! This enlarges and widely corrects the behaviour, but it is an optimization right? This allows to emit the faster branch in many more cases. Actually, since the tag is 0xF, the jit was always outputting suboptimal code.

But this would not fix a crash, I would like it to be a different commit since it looks like an optimization fix.

static bool canEncodeAArch32Imm(T value) {
uint32_t encoded;
return arm::Utils::encodeAArch32Imm(value, &encoded);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nice

Comment thread erts/configure

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This file looks like a cleanup to make the configure script more coherent. This should be a different commit. I see this file is generated by autoconf, does autoconf needs to be updated too or is this just a configure update that you committed while the configure.ac is already fixed ?

a.align(AlignMode::kCode, 4);
a.bind(pointer);
a.embedLabel(veneer.target);
a.embedLabel(veneer.target, 4);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

this is defensive, as the asmjit should deduce that a label is 4 bytes by the target architecture, I prefer to not write it as it should not fix anything.

a.embedUInt32(value.as<ArgWord>().get());
} else if (value.isLabel()) {
a.embedLabel(rawLabels.at(value.as<ArgLabel>().get()));
a.embedLabel(rawLabels.at(value.as<ArgLabel>().get()), 4);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

same

Constant{.latestOffset = std::max<ssize_t>(
currOffset,
maxOffset -
STUB_CHECK_INTERVAL),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is a safer and less optimal offset that will embed constants sooner and more often. This may or may not be part of the fix. It is not bad but I would like to know.

@fhunleth

Copy link
Copy Markdown
Author

@ziopio Thanks for the super thorough review. I think it's best if you just pluck out what you like and then we throw the rest away.

The command that broke for me was running runtime_info at the IEx prompt. I really thought it was the call to get atom memory, but maybe it was a following one? https://github.com/elixir-lang/elixir/blob/b680bea383f93318ebebf275a0865d9f20c807bd/lib/iex/lib/iex/helpers.ex#L661

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.

2 participants