HaxeFoundation / HaxeFoundation/haxe
[hl/c] hl.Bytes accesses generated with undefined behaviour
- Dominant language
- Haxe
- Stars
- 6.9k
- Forks
- 715
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 11
Description
Simplified down from an example found by @MeguminBOT: https://github.com/MeguminBOT/hlc-alias-repro
```haxe
function main() {
final b = new hl.Bytes(4);
b.setF32(0, 1.);
final tmp = b.getUI16(2);
b.setF32(0, 2.);
trace(tmp);
}
```
Compiling with gcc (via make template) with optimisations turned on:
```sh
$ haxe -m Main --hl bin/main.c -D hlgen.makefile
# ...
$ bin/main
0
```
With `--debug` mode (which disables optimisations) or in the jit it prints `16256`.
This is the code generated:
```C
float r3;
vdynamic *r5;
int r1, r4;
vbyte *r0;
r1 = 4;
r0 = hl_alloc_bytes(r1);
r1 = 0;
r3 = 1.f;
*(float*)(r0 + r1) = r3;
r1 = 2;
r1 = *(unsigned short*)(r0 + r1);
r4 = 0;
r3 = 2.f;
*(float*)(r0 + r4) = r3;
// ...
```
accessing the same data as a `float` or `unsigned short` is undefined behaviour because it breaks [strict aliasing](https://en.cppreference.com/c/language/object#Strict_aliasing), so it is optimised as if these accesses read/write at separate locations, and the read cannot see the earlier write.
Generating memcpy instead can allow the incompatible types to be read/written safely: https://en.cppreference.com/c/string/byte/memcpy#Notes.
Contributor guide
Research direction
Reproduce the example with `haxe -m Main --hl bin/main.c -D hlgen.makefile`, then inspect the generated hl/c access code shown in the issue and compare optimized and debug builds. Trace where `hl.Bytes` float and integer accesses are emitted; done means optimized C no longer relies on incompatible typed accesses and the example produces the expected value.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100