golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
124.11k stars 17.68k forks source link

proposal: softfloat support for GOARCH=wasm #62470

Open qizhou opened 1 year ago

qizhou commented 1 year ago

We are working on proving the execution of go-compiled WASM code using zero-knowledge (ZK) (see https://github.com/DelphinusLab/zkWasm). However, the current zkWASM prover cannot support floating point instructions (f32.xxx/f64.xxx). We tried to use go build -gcflags=all=-d=softfloat and we found most of the FP instructions are translated to INT ones except f64.load, f64.store, f64.ceil, f64.floor.

Q: Is there a way to support softfloat explicitly for WASM or any flags to fully support softfloat?

Test code (fp.go):

import "math"

func main() {
    a := 3.5
    b := 2.7
    println(math.Ceil(a))
    println(math.Floor(a))
    println(a / b)
}

Commands

$ go version
go version go1.21.0 linux/amd64
$ GOOS=js GOARCH=wasm go build -gcflags=all=-d=softfloat fp.go
$ wasm2wat fp | grep 'f32\.'
$ wasm2wat fp | grep 'f64\.'
    f64.load offset=8
    f64.floor
    f64.store offset=16
    f64.load offset=8
    f64.ceil
    f64.store offset=16
ianlancetaylor commented 1 year ago

CC @golang/wasm

ianlancetaylor commented 1 year ago

This would presumably need to be an option in the GOWASM environment variable.

johanbrandhorst commented 1 year ago

Have we introduced a GOWASM variable or are you proposing a new variable?

rsc commented 1 year ago

It seems truly ironic for a WASM implementation not to support floating point.

qizhou commented 1 year ago

It seems truly ironic for a WASM implementation not to support floating point.

ZK technology that supports floating point instructions is much harder than integer ones - especially since most ZK/blockchain applications use high-resolution integers (mostly 256-bit) to perform financial applications such as balance transfer, token swap, compound rate, etc.

LiuJiazheng commented 1 year ago

Hi there, I am working with @qizhou trying to deliver a provable WASM program. It would be greatly appreciated if we could be guided the pointers or links which implement f32/f64 as software float simulation (i.e. how the softfloat takes effect, it seems right now that the support is partially), thus we might do some handcraft change ourselves : )

LiuJiazheng commented 1 year ago

Plus can confirm f32 is gone after using -d=softfloat fp.go

func main() {
        a := float32(3.5)
        b := float32(2.7)
        println(a / b)
}

commands

% env GO111MODULE=on GOOS=wasip1 GOARCH=wasm go build -gcflags=all=-d=softfloat fp.go
% wasmtime fp
+1.296296e+000
% wasm2wat fp | grep 'f32\.' | wc -l
       0
% wasm2wat fp | grep 'f64\.' | wc -l
       0
0x1cc commented 1 year ago

I think this is a problem caused by the incompatibility of the math library with the softfloat compilation option. Take math.Floor in (src/math/floor.go) as an example.

// src/math/floor.go Line 14

func Floor(x float64) float64 {
    if haveArchFloor {
        return archFloor(x)
    }
    return floor(x)
}

When the compilation architecture is WASM , it will directly uses the corresponding underlying instructions of WASM (src/math/floor_wasm.s). Therefore, softfloat does not work!

// src/math/floor_asm.go

//go:build 386 || amd64 || arm64 || ppc64 || ppc64le || s390x || wasm

package math

const haveArchFloor = true
LiuJiazheng commented 1 year ago

Okay now it looks more like a bug to me.

Is there a way to pass the gcflag into math package in runtime? Or a build flag could help this.

randall77 commented 1 year ago

-gcflags=all=-d=softfloat only affects the compiler (gc = Go Compiler). It does not affect any assembly code in the math package or elsewhere. Thus it isn't the equivalent of a GOWASM=softfloat build option.

LiuJiazheng commented 1 year ago

@randall77 Thanks. That makes sense. I agree the proposal of adding GOWASM=softfloat would be an elegant way to implement that. https://github.com/golang/go/blob/06138963a2a16e0ac982ac85877092920a21e60f/src/internal/buildcfg/cfg.go#L137

johanbrandhorst commented 1 year ago

Is this something we need a build option for, or could it just be the default behavior?

LiuJiazheng commented 1 year ago

Based on what I learned from Qi's proposal and conversation, my understanding is to, add a new build option. Default behavior should (consistently) be using hardware floating point arithmetic for sake of performance, plus all modern browsers supporting it. This pr empowers users who are operating on weird platform(for example, zkWASM) which may be hard handling floating point.

qizhou commented 1 year ago

We have temporarily solved the issue by replacing f64.floor and f64.ceil using the following code (by @0x1cc ):

+// golang issue: https://github.com/golang/go/issues/62470
+
+package math
+
+import "math"
+
+// Borrow from `src/math/floor.go`
+func Floor(x float64) float64 {
+       if x == 0 || math.IsNaN(x) || math.IsInf(x, 0) {
+               return x
+       }
+       if x < 0 {
+               d, fract := math.Modf(-x)
+               if fract != 0.0 {
+                       d = d + 1
+               }
+               return -d
+       }
+       d, _ := math.Modf(x)
+       return d
+}
+
+func Ceil(x float64) float64 {
+       return -Floor(-x)
+}
johanbrandhorst commented 1 year ago

Should this be something we add a wasm arch build tagged file for in the math package?

0x1cc commented 1 year ago

Not only for WASM, indeed, math package does not support softfloat well for all platform.

In the example (fp.go) provided by Qi, even GO386=softfloat does not work in the math package. This looks like a bug.

$ GOOS=linux GOARCH=386 GO386=softfloat go build fp.go 
$ go tool objdump -S fp | grep FRNDINT                 
  0x80a56be             d9fc                    FRNDINT
  0x80a56ee             d9fc                    FRNDINT

FRNDINT (Round to Integer) requires hardware support FPU.

If we want to support softfloat for all platform (even for architectures that in general would have FPU), we need to modify the math package. In the current math package, when the architecture has an FPU, it will by default accelerate the computation using assembling code that takes advantage of the architecture's FPU. But this will lead to incompatibility with softfloat. As shown in the example above, GO386=softfloat does not work in the math package.

randall77 commented 1 year ago

@0x1cc : that is not a bug. FRNDINT is used by the 387 coprocessor to implement floating point operations. It is not an SSE instruction. We assume that 386 chips have at least Pentium MMX or better. See https://github.com/golang/go/wiki/MinimumRequirements#386 . That includes MMX instructions like FRNDINT but not SSE instructions.

randall77 commented 1 year ago

But in any case, that's a separate issue than this one. @0x1cc Please open an new issue if you disagree with how softfloat is implemented on 386 (this issue is about wasm).

0x1cc commented 1 year ago

@randall77 Thanks! Regarding the softfloat support for WASM, using -gcflags=all=-d=softfloat can convert floating-point calculations to softfloat within the compiler, but it doesn't impact any assembly code in the math package. To incorporate softfloat support for WASM, or possibly other architectures, should we consider introducing a new build option that disables the math package from utilizing FPU-related assembly code for performance optimization?

The math package can leverage assembly code for performance optimization by default. However, when we require softfloat support, we can utilize an option to deactivate this feature. This option will enable softfloat support with -gcflags=all=-d=softfloat for WASM and potentially all platforms.

qizhou commented 1 year ago

Hi Go community 👋,

By reusing this thread, I would like to introduce zkGo - a minimum modified Go compiler to produce Wasm code compatible with ZK prover. To prove the Wasm code generated by the Go compiler with ZK, we found some additional issues besides the support issue of floating point instruction as follows.

We create the zkGo compiler to address these issues with

  1. Replacing the Dependency on a JavaScript Host with an OS-like Host (GOOS=wasip1)
  2. Adapting the IO Model for zkWasm Compatibility
  3. Substituting Unsupported Instructions with softfloat and non-bulk memory instructions
  4. Replacing proc_exit with a Normal Exit using Go assembly code

The article with details (and motivation) can be found here https://perfect-amphibian-929.notion.site/Advancing-Towards-ZF-Fraud-Proof-zkGo-Compiling-L2-Geth-into-ZK-Compatible-Wasm-315a878af5754c549e5003568e1ee124?pvs=4

The repo of zkGo is here: https://github.com/ethstorage/go/tree/zkGo

johanbrandhorst commented 1 year ago

This seems irrelevant to this issue, and I don't think this an appropriate forum for this sort of announcement.

0x1cc commented 1 year ago

To support softfloat in the Go math package, we propose adding a new build tag, let's call it math_pure_go, that users can apply to disable the use of FPU-specific assembly code in the math package. We can use the math_pure_go build tag to disable the assembly in math package. With the build tag -tags=math_pure_go and -gcflags=all=-d=softfloat, we can support softfloat for all platforms.

go build -gcflags=all=-d=softfloat -tags=math_pure_go

For additional details, please refer to the corresponding issue: GitHub Issue #63270.

johanbrandhorst commented 1 year ago

Can we close this issue in favor of the new proposal?

bcmills commented 1 year ago

the current zkWASM prover cannot support floating point instructions (f32.xxx/f64.xxx)

Can you give more detail as to why you would prefer to add another Go platform variant rather than implement support in zkWASM for the necessary floating-point instructions?

It seems like either way you'd be emulating floating-point arithmetic using a framework designed for integers.

ianlancetaylor commented 1 year ago

@johanbrandhorst Using a GOWASM=softfloat variable seems more consistent with how other targets behave than the approach described in #63270. I don't think we should close this proposal yet.

Like others, I also don't understand how many people need this feature. Is there enough need here that this is a platform that we ought to support?

LiuJiazheng commented 1 year ago

There are two separate questions, as I could think up: First, is there necessary to support softfloat on golang? This is subtle. As many reply states modern browser and other archs should have hardware support for fp. But there is always exception ---- as Qi points out, if you want to do calculation on an elliptic curve group using a generic programming language, it requires to set up a large prime field (see a rust implementation here https://github.com/zcash/pasta_curves). It is generally dealing with a large int, you might imagine, but not any float number. For such applications, it is hard for developers to think about fp support because they do not really need it. Again, I DO NOT think this MUST BE DONE in upstream, which is go community. But I do want to demonstrate the (maybe niche) demand from users. Second, without any background, when I use -gcflags=all=-d=softfloat I naively believe this could help me eliminate all fp in target asm. This is quite confusing that the code written by customers could be fp-free but the standard library like math still holds quite an amount of them. Personally I don't think it is intuitive. And this inconsistency concerns users (at least me). Again, I hold same question about "should golang support softfloat" but I strongly believe that the 'inconsistency' (forgive me if you have a better wording) must be solved -- at least a note stating that, so that people with same demand might not encounter the situation again, hopefully.

ianlancetaylor commented 1 year ago

Note that -gcflags=all=-d=softfloat isn't documented at all. We make no promise that we will retain that feature.

The closest thing I can find to documentation is from go tool compile -d help which says, for softfloat: force compiler to emit soft-float code. It doesn't say anything about assembly code. It may not be intuitive but I don't think that very short comment about an undocumented feature needs to say anything about assembly code.

Where did you see a mention of -d=softfloat? Perhaps we could mention assembly code there.

qizhou commented 1 year ago

I find -d=softfloat in https://github.com/golang/go/issues/61588#issuecomment-1652120941, which faces the same issue for GOARM=6, 7.