tinygo-org / tinygo

Go compiler for small places. Microcontrollers, WebAssembly (WASM/WASI), and command-line tools. Based on LLVM.
https://tinygo.org
Other
15.32k stars 905 forks source link

Minimal compilation to WASM leaves some memory function artifacts #2491

Closed jzabinski-dolios closed 2 years ago

jzabinski-dolios commented 2 years ago

New to TinyGo here.

I need a series of math functions in WebAssembly. These functions can all be called discretely by Javascript and they are completely self-sufficient: they need no other runtime features like garbage collection, debug information, etc. (They likely will not even need an import object defined in Javascript.)

I made a short example program, findJD.go.

When I compile like this: tinygo build -o tinygo.wasm -target=wasm -gc=none -scheduler=none -no-debug ./findJD.go

And then convert the WASM to WAT to look at the output (using another tool), I can see that the following is created (among other things):

(import "wasi_snapshot_preview1" "fd_write" (func $fimport$0 (param i32 i32 i32 i32) (result i32)))
(import "env" "runtime.alloc" (func $fimport$1 (param i32 i32 i32 i32) (result i32)))
(import "env" "main.main" (func $fimport$2 (param i32 i32)))
(memory $0 2)
(data (i32.const 65536) "panic: runtime error: index out of rangeunimplemented: reallocunimplemented: posix_memalignunimplemented: aligned_allocunimplemented: malloc_usable_size")
(data (i32.const 65688) "\a4\00\01\00\00\00\00\00")
(table $0 1 1 funcref)
(global $global$0 (mut i32) (i32.const 65536))
(export "memory" (memory $0))
(export "malloc" (func $5))
(export "free" (func $6))
(export "calloc" (func $7))
(export "realloc" (func $8))
(export "posix_memalign" (func $9))
(export "aligned_alloc" (func $10))
(export "malloc_usable_size" (func $11))
(export "_start" (func $12))
(export "findJD" (func $13))

Simply put, the only thing that I am going to actually use is:

(export "findJD" (func $13))

For reference, if I write my example program in WAT by hand, compile it and convert back to WAT, I get:

(type $i32_i32_f64_=>_f64 (func (param i32 i32 f64) (result f64)))
(export "findJD" (func $0))

I want to get as close to that simplicity as I can. How do I achieve this?

dgryski commented 2 years ago

What version of tinygo were you running this with? The dev branch has a number of wasm fixes that might solve this.

jzabinski-dolios commented 2 years ago

This was on v0.21.0 darwin/amd64. Let me try building dev to see how it does.

jzabinski-dolios commented 2 years ago

After cloning the package, checking out the dev branch and following these instructions to build everything, I ran this in the original folder:

../tinygo/build/tinygo build -o tinygo.wasm -target=wasm -gc=none -scheduler=none -no-debug ./findJD.go

The result:

(import "wasi_snapshot_preview1" "fd_write" (func $fimport$0 (param i32 i32 i32 i32) (result i32)))
(import "env" "runtime.alloc" (func $fimport$1 (param i32 i32 i32 i32) (result i32)))
(import "env" "runtime.realloc" (func $fimport$2 (param i32 i32 i32 i32) (result i32)))
(import "env" "main.main" (func $fimport$3 (param i32 i32)))
(memory $0 2)
(data (i32.const 65536) "panic: runtime error: index out of rangeunimplemented: posix_memalignunimplemented: aligned_allocunimplemented: malloc_usable_size")
(data (i32.const 65668) "\90\00\01\00\00\00\00\00")
(table $0 1 1 funcref)
(global $global$0 (mut i32) (i32.const 65536))
(export "memory" (memory $0))
(export "malloc" (func $16))
(export "free" (func $17))
(export "calloc" (func $18))
(export "realloc" (func $19))
(export "posix_memalign" (func $20))
(export "aligned_alloc" (func $21))
(export "malloc_usable_size" (func $22))
(export "_start" (func $23))
(export "findJD" (func $24))

Basically the issue reproduces. (runtime.reallocwas added.)

The original compilation target's code:

package main

// FloorDiv returns the integer floor of the fractional value (x / y).
//
// It uses integer math only, so is more efficient than using floating point
// intermediate values.  This function can be used in many places where INT()
// appears in AA.  As with built in integer division, it panics with y == 0.
func FloorDiv(x, y int) (q int) {
    q = x / y
    if (x < 0) != (y < 0) && x%y != 0 {
        q--
    }
    return
}

// FloorDiv64 returns the integer floor of the fractional value (x / y).
//
// It uses integer math only, so is more efficient than using floating point
// intermediate values.  This function can be used in many places where INT()
// appears in AA.  As with built in integer division, it panics with y == 0.
func FloorDiv64(x, y int64) (q int64) {
    q = x / y
    if (x < 0) != (y < 0) && x%y != 0 {
        q--
    }
    return
}

// CalendarGregorianToJD converts a Gregorian year, month, and day of month
// to Julian day.
//
// Negative years are valid, back to JD 0.  The result is not valid for
// dates before JD 0.
//export findJD
func CalendarGregorianToJD(y, m int, d float64) float64 {
    switch m {
    case 1, 2:
        y--
        m += 12
    }
    a := FloorDiv(y, 100)
    b := 2 - a + FloorDiv(a, 4)
    // (7.1) p. 61
    return float64(FloorDiv64(36525*(int64(y+4716)), 100)) +
        float64(FloorDiv(306*(m+1), 10)+b) + d - 1524.5
}
dgryski commented 2 years ago

Hmm. I was hoping the wasm-opt pass would handle some of that tree shaking. I'll investigate this a bit more.

fgsch commented 2 years ago

Part of this is related to panic handling. You can remove those passing -panic trap to tinygo build.

jzabinski-dolios commented 2 years ago

That does improve things somewhat.

Ran this: tinygo build -o tinygo.wasm -target=wasm -gc=none -scheduler=none -no-debug -panic=trap ./findJD.go

The file size went from around 1000 bytes to 555 bytes, and the quoted section becomes:

(import "env" "runtime.alloc" (func $fimport$0 (param i32 i32 i32 i32) (result i32)))
(import "env" "main.main" (func $fimport$1 (param i32 i32)))
(memory $0 1)
(table $0 1 1 funcref)
(global $global$0 (mut i32) (i32.const 65536))
(export "memory" (memory $0))
(export "malloc" (func $0))
(export "free" (func $1))
(export "calloc" (func $2))
(export "realloc" (func $3))
(export "posix_memalign" (func $4))
(export "aligned_alloc" (func $5))
(export "malloc_usable_size" (func $6))
(export "_start" (func $7))
(export "findJD" (func $8))
aykevl commented 2 years ago

I have removed a few in https://github.com/tinygo-org/tinygo/pull/2549.

At the moment we only really support WASI or browser environments, not stand alone environments. This might be something we could add though (with limitations such as not being able to use println and having to call a runtime initializer at least once).

jzabinski-dolios commented 2 years ago

@aykevl : could you please clarify what you mean by 'stand alone environments'?

I think that you mean that TinyGo isn't designed to support WASM files that only contain functions exported via the export keyword. Some runtime functionality will always be needed (for things like linear memory initialization).

Of course such environments need to be embedded somewhere like a browser, so to me 'stand alone environment' can't be exclusive of an embedding browser or WASI environment. Thus my question.

ramilexe commented 2 years ago

HI @aykevl I am interested to build wasm for a standalone environment + some helper to pass string/bytes between wasm and js (read/write from memory). Could you give me some thought about what needs to be implemented for that?

codefromthecrypt commented 2 years ago

freestanding seems to be the latest on getting this solved. https://github.com/tinygo-org/tinygo/issues/3068

@jzabinski-dolios if you agree, do you mind closing this out for that one, mainly as it is more elaborated and with a work-in-progress PR?

jzabinski-dolios commented 2 years ago

Don't mind at all, thanks for asking

aykevl commented 2 years ago

I think this issue is slightly different. I agree the memory function artifacts are a problem. I have a fix for it here: https://github.com/tinygo-org/tinygo/pull/3142. It reduces binary size significantly for small binaries that don't allocate (and use -scheduler=none).