odin-lang / Odin

Odin Programming Language
https://odin-lang.org
BSD 3-Clause "New" or "Revised" License
6.89k stars 606 forks source link

js_wasm32 cannot run hellope.odin on higher optimization modes #1846

Closed Astavie closed 2 years ago

Astavie commented 2 years ago

Context

Odin: dev-2022-06-nightly:ba5f7c4e
OS:   Arch Linux, Linux 5.18.3-arch1-1
CPU:  11th Gen Intel(R) Core(TM) i5-11400F @ 2.60GHz
RAM:  15868 MiB

Expected Behavior

"Hellope!" to be displayed in the browser console

Current Behavior

I get the error Uncaught (in promise) LinkError: import object field '__truncdfhf2' is not a Function

Failure Information (for bugs)

Looking inside the generated .wasm file, it seems it is expecting four functions to be defined in the env namespace:

...
  (import "env" "__truncdfhf2" (func $__truncdfhf2 (type $t5)))
  (import "env" "__extendhfsf2" (func $__extendhfsf2 (type $t6)))
  (import "env" "__umodti3" (func $__umodti3 (type $t7)))
  (import "env" "__udivti3" (func $__udivti3 (type $t7)))
...

This happens with -opt:1, -opt:2, -opt:3, -o:size, or -o:speed.

When looking at the output without any of these build settings, it seems these functions are simply defined internally:

...
  (func $__truncdfhf2 (type $t21) (param $p0 f64) (result i32)
    (local $l1 f32) (local $l2 i32)
    local.get $p0
    f32.demote_f64
    local.set $l1
    local.get $l1
    call $__truncsfhf2
    local.set $l2
    local.get $l2
    return)
...

Steps to Reproduce

  1. Simple hellope.odin file:
package main

import "core:fmt"

main :: proc() {
    fmt.println("Hellope!")
}
  1. Building it with odin build . -target:js_wasm32 -o:size gives a .wasm file

  2. Try to run the .wasm file using the following html (runtime.js grabbed from vendor:wasm)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>odin wasm test</title>
</head>
<body>
    <script type="text/javascript" src="runtime.js"></script>
    <script type="text/javascript">
        odin.runWasm(".wasm");
    </script>
</body>
</html>

Failure Logs

image

Astavie commented 2 years ago

It seems these functions are part of compiler-rt / libgcc

EDIT: they usually are, but it seems odin also defines them internally

Astavie commented 2 years ago

Doing more research...

llvm generates a call to __truncdfhf2 when encountering trunc i128, which is used in @strconv.append_bits_128 among other places

These processes are defined in core:runtime/internal.odin

When optimalization is off, these can even be seen in the .ll file:

...

define internal i16 @__truncdfhf2(double %0) {
decls:
  br label %entry

entry:                                            ; preds = %decls
  %1 = fptrunc double %0 to float
  %2 = call i16 @__truncsfhf2(float %1)
  ret i16 %2
}

...

However, turning optimalization on removes them from the .ll file. My theory is now that llvm removes them as they are deemed unused?

Astavie commented 2 years ago

Theory likely true: compiling on linux with odin build . -no-crt -o:speed gives a linking error:

odin_package:(.text+0x2466f): undefined reference to `__truncdfhf2'

Of course, this will never be able to run either way because of -no-crt, but it does sort of compile if you remove the -o:speed flag.

So in summary, llvm generates calls to specific functions when encountering some operators with 128-bit operands. These are usually defined in compiler-rt / libgcc, but not every architecture has those libraries. I assume this is why odin also defines these functions itself in core:runtime/internal.odin.

However, right now this only seems to work at the lowest settings of optimization where LLVM does not remove unused definitions. In other cases, these internally-defined procs get removed before they are linked. This usually goes unnoticed since they then get linked to compiler-rt / libgcc.

The error happens when odin builds with high optimization and without being linked to the C Run Time, which happens when building to WebAssembly or enabling the -no-crt flag.

This could potentially be fixed by adding the procs inside internal.odin to the ‘llvm.compiler.used’ Global Variable, to prevent the compiler from touching them until linking. Given the amount of time I've put into this now today, I might even make my first PR.