diff --git a/go.mod b/go.mod index b7f76b43ee4..de9ad59c82a 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.20.1 + helm.sh/helm/v4 v4.1.4 k8s.io/api v0.35.4 k8s.io/apiextensions-apiserver v0.35.4 k8s.io/apimachinery v0.35.4 @@ -61,7 +61,7 @@ require ( github.com/Masterminds/sprig/v3 v3.3.0 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/ProtonMail/go-crypto v1.3.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -69,7 +69,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect - github.com/cloudflare/circl v1.6.1 // indirect + github.com/cloudflare/circl v1.6.3 // indirect github.com/containerd/containerd v1.7.32 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/log v0.1.0 // indirect @@ -83,16 +83,19 @@ require ( github.com/docker/docker-credential-helpers v0.9.7 // indirect github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect + github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect + github.com/extism/go-sdk v1.7.1 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fluxcd/cli-utils v0.37.2-flux.1 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect - github.com/go-errors/errors v1.4.2 // indirect + github.com/go-errors/errors v1.5.1 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.7.0 // indirect github.com/go-git/go-git/v5 v5.16.4 // indirect @@ -116,6 +119,7 @@ require ( github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/gobwas/glob v0.2.3 // indirect + github.com/gofrs/flock v0.13.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/btree v1.1.3 // indirect @@ -135,6 +139,7 @@ require ( github.com/hashicorp/go-version v1.6.0 // indirect github.com/helm/chart-testing/v3 v3.10.1 // indirect github.com/huandu/xstrings v1.5.0 // indirect + github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect @@ -188,6 +193,8 @@ require ( github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/viper v1.20.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect + github.com/tetratelabs/wazero v1.11.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect @@ -199,6 +206,7 @@ require ( go.opentelemetry.io/otel v1.43.0 // indirect go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.podman.io/image/v5 v5.40.0 // indirect go.podman.io/storage v1.63.0 // indirect go.uber.org/multierr v1.11.0 // indirect @@ -212,13 +220,14 @@ require ( golang.org/x/term v0.43.0 // indirect golang.org/x/text v0.37.0 // indirect golang.org/x/time v0.14.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect + helm.sh/helm/v3 v3.17.1 // indirect k8s.io/apiserver v0.35.4 // indirect k8s.io/component-base v0.35.4 // indirect k8s.io/klog v1.0.0 // indirect @@ -228,8 +237,8 @@ require ( oras.land/oras-go v1.2.6 // indirect oras.land/oras-go/v2 v2.6.0 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect - sigs.k8s.io/kustomize/api v0.20.1 // indirect - sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect + sigs.k8s.io/kustomize/api v0.21.1 // indirect + sigs.k8s.io/kustomize/kyaml v0.21.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect ) diff --git a/go.sum b/go.sum index e9e92d949a1..09ac2d30c5a 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA= github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok= -github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= -github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= @@ -63,8 +63,8 @@ github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJ github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cloudevents/sdk-go/v2 v2.16.0 h1:wnunjgiLQCfYlyo+E4+mFlZtAh7pKn7vT8MMD3lSwCg= github.com/cloudevents/sdk-go/v2 v2.16.0/go.mod h1:5YWqklyhDSmGzBK/JENKKXdulbPq0JFf3c/KEnMLqgg= -github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= -github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups/v3 v3.1.2 h1:OSosXMtkhI6Qove637tg1XgK4q+DhR0mX8Wi8EhrHa4= github.com/containerd/cgroups/v3 v3.1.2/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw= @@ -142,6 +142,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a h1:UwSIFv5g5lIvbGgtf3tVwC7Ky9rmMFBp0RMs+6f6YqE= +github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a/go.mod h1:C8DzXehI4zAbrdlbtOByKX6pfivJTBiV9Jjqv56Yd9Q= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= @@ -154,10 +156,14 @@ github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjT github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= +github.com/extism/go-sdk v1.7.1 h1:lWJos6uY+tRFdlIHR+SJjwFDApY7OypS/2nMhiVQ9Sw= +github.com/extism/go-sdk v1.7.1/go.mod h1:IT+Xdg5AZM9hVtpFUA+uZCJMge/hbvshl8bwzLtFyKA= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fluxcd/cli-utils v0.37.2-flux.1 h1:tQ588ghtRN+E+kHq415FddfqA9v4brn/1WWgrP6rQR0= +github.com/fluxcd/cli-utils v0.37.2-flux.1/go.mod h1:LcWSu1NYET8d8U7O326RhEm5JkQXCMK6ITu4G1CT02c= github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0= github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -168,8 +174,8 @@ github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sa github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= +github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= @@ -238,6 +244,8 @@ github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIx github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= +github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -315,6 +323,8 @@ github.com/helm/chart-testing/v3 v3.10.1 h1:ByfJIFpvbau7u3Af1/Z2j01DdKGI4SBouSof github.com/helm/chart-testing/v3 v3.10.1/go.mod h1:OFtBbzuUE1BhFY1LgG2WLhtG/+2b3VkBC8+SGzQAlDA= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca h1:T54Ema1DU8ngI+aef9ZhAhNGQhcRTrWxVeG07F+c/Rw= +github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -558,6 +568,10 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q= +github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk= +github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA= +github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU= github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -723,8 +737,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= -gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/genproto v0.0.0-20260202165425-ce8ad4cf556b h1:mJ7ODqDXbGE8alZwxCKWc9OTvpFQkXB6KRHvjnb9W8Q= @@ -761,8 +775,10 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -helm.sh/helm/v3 v3.20.1 h1:T8PodUaH1UwNvE+imUA2mIKjJItY8g7CVvLVP5g4NzI= -helm.sh/helm/v3 v3.20.1/go.mod h1:Fl1kBaWCpkUrM6IYXPjQ3bdZQfFrogKArqptvueZ6Ww= +helm.sh/helm/v3 v3.17.1 h1:gzVoAD+qVuoJU6KDMSAeo0xRJ6N1znRxz3wyuXRmJDk= +helm.sh/helm/v3 v3.17.1/go.mod h1:nvreuhuR+j78NkQcLC3TYoprCKStLyw5P4T7E5itv2w= +helm.sh/helm/v4 v4.1.4 h1:zwTrNkalG4f7SYigRSdQnYrTj0QEz1qzetzAlYoDVSo= +helm.sh/helm/v4 v4.1.4/go.mod h1:5dSo8rRgn3OTkDAc/k0Ipw5/Q+BlqKIKZwa0XwSiINI= k8s.io/api v0.35.4 h1:P7nFYKl5vo9AGUp1Z+Pmd3p2tA7bX2wbFWCvDeRv988= k8s.io/api v0.35.4/go.mod h1:yl4lqySWOgYJJf9RERXKUwE9g2y+CkuwG+xmcOK8wXU= k8s.io/apiextensions-apiserver v0.35.4 h1:HeP+Upp7ItdvnyGmub0yoix+2z5+ev4M5cE5TCgtOUU= @@ -799,10 +815,10 @@ sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4i sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= -sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= -sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= -sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= +sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= +sigs.k8s.io/kustomize/api v0.21.1/go.mod h1:f3wkKByTrgpgltLgySCntrYoq5d3q7aaxveSagwTlwI= +sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI= +sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs= diff --git a/pkg/helm/actions/auth.go b/pkg/helm/actions/auth.go index c7c646ba508..dd0eb46aac0 100644 --- a/pkg/helm/actions/auth.go +++ b/pkg/helm/actions/auth.go @@ -10,7 +10,7 @@ import ( configv1 "github.com/openshift/api/config/v1" "github.com/openshift/api/helm/v1beta1" - "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v4/pkg/action" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" ) diff --git a/pkg/helm/actions/auth_test.go b/pkg/helm/actions/auth_test.go index d53d6613a40..521efa1d4ed 100644 --- a/pkg/helm/actions/auth_test.go +++ b/pkg/helm/actions/auth_test.go @@ -7,7 +7,7 @@ import ( configv1 "github.com/openshift/api/config/v1" "github.com/openshift/api/helm/v1beta1" "github.com/stretchr/testify/require" - "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v4/pkg/action" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" diff --git a/pkg/helm/actions/config.go b/pkg/helm/actions/config.go index 191dcd6c00b..85deefa7a72 100644 --- a/pkg/helm/actions/config.go +++ b/pkg/helm/actions/config.go @@ -3,8 +3,8 @@ package actions import ( "net/http" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/cli" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/rest" "k8s.io/klog/v2" @@ -49,7 +49,7 @@ func GetActionConfigurations(host, ns, token string, transport *http.RoundTrippe confFlags.CAFile = &inClusterCfg.CAFile } conf := new(action.Configuration) - conf.Init(confFlags, ns, "secrets", klog.Infof) + conf.Init(confFlags, ns, "secrets") registryClient, err := GetDefaultOCIRegistry() if err != nil { klog.V(4).Infof("Failed to get default OCI registry: %v", err) diff --git a/pkg/helm/actions/get_chart.go b/pkg/helm/actions/get_chart.go index bd76446902a..162ac87c8e7 100644 --- a/pkg/helm/actions/get_chart.go +++ b/pkg/helm/actions/get_chart.go @@ -5,9 +5,9 @@ import ( "os" "github.com/openshift/api/helm/v1beta1" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v4/pkg/action" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" "k8s.io/client-go/dynamic" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" diff --git a/pkg/helm/actions/get_chart_test.go b/pkg/helm/actions/get_chart_test.go index 743905125a3..77505363642 100644 --- a/pkg/helm/actions/get_chart_test.go +++ b/pkg/helm/actions/get_chart_test.go @@ -10,11 +10,11 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "github.com/stretchr/testify/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chartutil" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/chart/common" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/dynamic" @@ -91,8 +91,7 @@ func TestGetChartWithoutTls(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -204,8 +203,7 @@ func TestGetChartWithTlsData(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -338,8 +336,7 @@ func TestGetChartBasicAuth(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { diff --git a/pkg/helm/actions/get_registry.go b/pkg/helm/actions/get_registry.go index affed8a2712..79c8750e552 100644 --- a/pkg/helm/actions/get_registry.go +++ b/pkg/helm/actions/get_registry.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v4/pkg/registry" ) // newRegistryClient is a package-level variable to allow mocking in tests diff --git a/pkg/helm/actions/get_registry_test.go b/pkg/helm/actions/get_registry_test.go index c9a0d93e17f..2f6f03ae31c 100644 --- a/pkg/helm/actions/get_registry_test.go +++ b/pkg/helm/actions/get_registry_test.go @@ -6,12 +6,12 @@ import ( "testing" "github.com/stretchr/testify/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chartutil" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/chart/common" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + "helm.sh/helm/v4/pkg/registry" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" ) func TestGetDefaultOCIRegistry_Success(t *testing.T) { @@ -20,7 +20,7 @@ func TestGetDefaultOCIRegistry_Success(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, + Capabilities: common.DefaultCapabilities, } require.Nil(t, conf.RegistryClient, "Registry Client should be nil") diff --git a/pkg/helm/actions/get_release.go b/pkg/helm/actions/get_release.go index c8ed6aa00b9..18e31df82f3 100644 --- a/pkg/helm/actions/get_release.go +++ b/pkg/helm/actions/get_release.go @@ -1,16 +1,22 @@ package actions import ( - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/release" + "fmt" + + "helm.sh/helm/v4/pkg/action" + releaseV1 "helm.sh/helm/v4/pkg/release/v1" ) -func GetRelease(releaseName string, conf *action.Configuration) (*release.Release, error) { +func GetRelease(releaseName string, conf *action.Configuration) (*releaseV1.Release, error) { cmd := action.NewGet(conf) releases, err := cmd.Run(releaseName) if err != nil { return nil, err } - return releases, nil + rel, ok := releases.(*releaseV1.Release) + if !ok { + return nil, fmt.Errorf("unexpected release type %T", releases) + } + return rel, nil } diff --git a/pkg/helm/actions/get_release_test.go b/pkg/helm/actions/get_release_test.go index 6e11ff3b05f..c7e845e721f 100644 --- a/pkg/helm/actions/get_release_test.go +++ b/pkg/helm/actions/get_release_test.go @@ -7,12 +7,12 @@ import ( "testing" "github.com/stretchr/testify/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chartutil" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/chart/common" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + rcommon "helm.sh/helm/v4/pkg/release/common" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -72,8 +72,7 @@ func TestGetRelease(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } client := K8sDynamicClientFromCRs(tt.helmCRS...) clientInterface := k8sfake.NewSimpleClientset() @@ -85,7 +84,7 @@ func TestGetRelease(t *testing.T) { rel, err := GetRelease(tt.releaseName, actionConfig) require.NoError(t, err) require.Equal(t, tt.releaseName, rel.Name) - require.Equal(t, release.StatusDeployed, rel.Info.Status) + require.Equal(t, rcommon.StatusDeployed, rel.Info.Status) require.Equal(t, tt.manifestValue, rel.Manifest) } else if tt.testName == "invalid chart path" { require.Error(t, err) @@ -157,8 +156,7 @@ func TestGetReleaseWithTlsData(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } // create a namespace if it is not same as openshift-config if tt.createNamespace && tt.namespace != configNamespace { diff --git a/pkg/helm/actions/install_chart.go b/pkg/helm/actions/install_chart.go index f206f440413..5c06ddb8d7a 100644 --- a/pkg/helm/actions/install_chart.go +++ b/pkg/helm/actions/install_chart.go @@ -11,10 +11,11 @@ import ( "github.com/openshift/api/helm/v1beta1" "github.com/openshift/console/pkg/helm/metrics" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v4/pkg/action" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" + "helm.sh/helm/v4/pkg/kube" + releaseV1 "helm.sh/helm/v4/pkg/release/v1" kv1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -95,11 +96,13 @@ func chartVersionFromURL(raw string) string { return "" } -func InstallChart(ns, name, url string, vals map[string]interface{}, conf *action.Configuration, client dynamic.Interface, coreClient corev1client.CoreV1Interface, fileCleanUp bool, indexEntry string) (*release.Release, error) { +func InstallChart(ns, name, url string, vals map[string]interface{}, conf *action.Configuration, client dynamic.Interface, coreClient corev1client.CoreV1Interface, fileCleanUp bool, indexEntry string) (*releaseV1.Release, error) { var err error var chartInfo *ChartInfo var cp, chartLocation string cmd := action.NewInstall(conf) + cmd.ServerSideApply = false + cmd.WaitStrategy = kube.LegacyStrategy // tlsFiles contain references of files to be removed once the chart // operation depending on those files is finished. tlsFiles := []*os.File{} @@ -153,11 +156,16 @@ func InstallChart(ns, name, url string, vals map[string]interface{}, conf *actio ch.Metadata.Annotations["chart_url"] = url cmd.Namespace = ns - release, err := cmd.Run(ch, vals) + result, err := cmd.Run(ch, vals) if err != nil { return nil, err } + rel, ok := result.(*releaseV1.Release) + if !ok { + return nil, fmt.Errorf("unexpected release type %T", result) + } + if ch.Metadata.Name != "" && ch.Metadata.Version != "" { metrics.HandleconsoleHelmInstallsTotal(ch.Metadata.Name, ch.Metadata.Version) } @@ -170,7 +178,7 @@ func InstallChart(ns, name, url string, vals map[string]interface{}, conf *actio os.Remove(f.Name()) } }() - return release, nil + return rel, nil } func InstallChartAsync(ns, name, url string, vals map[string]interface{}, conf *action.Configuration, client dynamic.Interface, coreClient corev1client.CoreV1Interface, fileCleanUp bool, indexEntry string) (*kv1.Secret, error) { @@ -178,6 +186,8 @@ func InstallChartAsync(ns, name, url string, vals map[string]interface{}, conf * var chartInfo *ChartInfo var cp, chartLocation string cmd := action.NewInstall(conf) + cmd.ServerSideApply = false + cmd.WaitStrategy = kube.LegacyStrategy // tlsFiles contain references of files to be removed once the chart // operation depending on those files is finished. tlsFiles := []*os.File{} @@ -302,6 +312,8 @@ func InstallChartFromURL(ns, name, url string, vals map[string]interface{}, conf } cmd := action.NewInstall(conf) + cmd.ServerSideApply = false + cmd.WaitStrategy = kube.LegacyStrategy cmd.ReleaseName = name cmd.Namespace = ns diff --git a/pkg/helm/actions/install_chart_test.go b/pkg/helm/actions/install_chart_test.go index fcceb46f5a7..30b417a9c6e 100644 --- a/pkg/helm/actions/install_chart_test.go +++ b/pkg/helm/actions/install_chart_test.go @@ -8,20 +8,21 @@ import ( "time" "github.com/stretchr/testify/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" - helmTime "helm.sh/helm/v3/pkg/time" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/chart/common" + chart "helm.sh/helm/v4/pkg/chart/v2" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + rcommon "helm.sh/helm/v4/pkg/release/common" + releaseV1 "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" k8sfake "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/rest" + helmTime "time" ) type FakeConfig struct { @@ -96,8 +97,7 @@ func TestInstallChart(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } client := K8sDynamicClientFromCRs(tt.helmCRS...) clientInterface := k8sfake.NewSimpleClientset() @@ -107,7 +107,7 @@ func TestInstallChart(t *testing.T) { require.NoError(t, err) require.Equal(t, "test", rel.Name) require.Equal(t, "test-namespace", rel.Namespace) - require.Equal(t, release.StatusDeployed, rel.Info.Status) + require.Equal(t, rcommon.StatusDeployed, rel.Info.Status) require.Equal(t, tt.chartName, rel.Chart.Metadata.Name) require.Equal(t, tt.chartVersion, rel.Chart.Metadata.Version) require.Equal(t, tt.chartPath, rel.Chart.Metadata.Annotations["chart_url"]) @@ -175,8 +175,7 @@ func TestInstallChartWithTlsData(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } // create a namespace if it is not same as openshift-config if tt.createNamespace && tt.namespace != configNamespace { @@ -272,8 +271,7 @@ func TestInstallChartBasicAuth(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } // create a namespace if it is not same as openshift-config if tt.createNamespace && tt.namespace != configNamespace { @@ -359,8 +357,7 @@ func TestInstallChartAsync(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } objs := []runtime.Object{} client := K8sDynamicClientFromCRs(tt.helmCRS...) @@ -379,10 +376,10 @@ func TestInstallChartAsync(t *testing.T) { }() if tt.requireError == false { secretsDriver := driver.NewSecrets(coreClient.Secrets(tt.namespace)) - r := release.Release{ + r := releaseV1.Release{ Name: tt.releaseName, Namespace: tt.namespace, - Info: &release.Info{ + Info: &releaseV1.Info{ FirstDeployed: helmTime.Time{}, Status: "pending-install", }, @@ -544,8 +541,7 @@ func TestInstallChartFromURL(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } registryClient, err := GetOCIRegistry(tt.skipTLSVerify, tt.plainHTTP, nil) require.NoError(t, err) @@ -589,10 +585,10 @@ func TestInstallChartFromURL(t *testing.T) { go func() { time.Sleep(2 * time.Second) secretsDriver := driver.NewSecrets(coreClient.Secrets("test-namespace")) - r := release.Release{ + r := releaseV1.Release{ Name: tt.releaseName, Namespace: "test-namespace", - Info: &release.Info{ + Info: &releaseV1.Info{ FirstDeployed: helmTime.Time{}, Status: "pending-install", }, diff --git a/pkg/helm/actions/list_releases.go b/pkg/helm/actions/list_releases.go index 4378d09b0cf..53d4ee50130 100644 --- a/pkg/helm/actions/list_releases.go +++ b/pkg/helm/actions/list_releases.go @@ -1,26 +1,37 @@ package actions import ( - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/release" + "fmt" + + "helm.sh/helm/v4/pkg/action" + chart "helm.sh/helm/v4/pkg/chart/v2" + releaseV1 "helm.sh/helm/v4/pkg/release/v1" ) -func ListReleases(conf *action.Configuration, limitInfo bool) ([]*release.Release, error) { +func ListReleases(conf *action.Configuration, limitInfo bool) ([]*releaseV1.Release, error) { cmd := action.NewList(conf) cmd.StateMask = action.ListAll - releases, err := cmd.Run() + results, err := cmd.Run() if err != nil { return nil, err } - if releases == nil { - rs := make([]*release.Release, 0) - return rs, nil + if results == nil { + return make([]*releaseV1.Release, 0), nil } - limitedReleaseInformation := make([]*release.Release, 0) - if limitInfo != false { + + releases := make([]*releaseV1.Release, 0, len(results)) + for _, r := range results { + rel, ok := r.(*releaseV1.Release) + if !ok { + return nil, fmt.Errorf("unexpected release type %T", r) + } + releases = append(releases, rel) + } + + if limitInfo { + limited := make([]*releaseV1.Release, 0, len(releases)) for _, rel := range releases { - releaseInformation := release.Release{ + info := releaseV1.Release{ Name: rel.Name, Version: rel.Version, Namespace: rel.Namespace, @@ -29,9 +40,9 @@ func ListReleases(conf *action.Configuration, limitInfo bool) ([]*release.Releas Metadata: rel.Chart.Metadata, }, } - limitedReleaseInformation = append(limitedReleaseInformation, &releaseInformation) + limited = append(limited, &info) } - return limitedReleaseInformation, nil + return limited, nil } return releases, nil } diff --git a/pkg/helm/actions/list_releases_test.go b/pkg/helm/actions/list_releases_test.go index c351baa40d3..353cbec98f9 100644 --- a/pkg/helm/actions/list_releases_test.go +++ b/pkg/helm/actions/list_releases_test.go @@ -4,27 +4,28 @@ import ( "io" "testing" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" - "helm.sh/helm/v3/pkg/time" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/chart/common" + chart "helm.sh/helm/v4/pkg/chart/v2" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + rcommon "helm.sh/helm/v4/pkg/release/common" + releaseV1 "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" + "time" ) func TestListReleases(t *testing.T) { tests := []struct { name string - release release.Release + release releaseV1.Release }{ { name: "list valid releases", - release: release.Release{ + release: releaseV1.Release{ Name: "test", Namespace: "test-namespace", - Info: &release.Info{ + Info: &releaseV1.Info{ FirstDeployed: time.Time{}, Status: "deployed", }, @@ -45,8 +46,7 @@ func TestListReleases(t *testing.T) { actionConfig := &action.Configuration{ Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } rels, err := ListReleases(actionConfig, true) if err != nil { @@ -61,7 +61,7 @@ func TestListReleases(t *testing.T) { if rels[0].Namespace != "test-namespace" { t.Error("Namespace isn't matching") } - if rels[0].Info.Status != release.StatusDeployed { + if rels[0].Info.Status != rcommon.StatusDeployed { t.Error("Chart status should be deployed") } if rels[0].Chart.Metadata.Name != "influxdb" { diff --git a/pkg/helm/actions/release_history.go b/pkg/helm/actions/release_history.go index 1962cdd3947..e71a86d8b15 100644 --- a/pkg/helm/actions/release_history.go +++ b/pkg/helm/actions/release_history.go @@ -1,20 +1,31 @@ package actions import ( - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/release" + "fmt" + + "helm.sh/helm/v4/pkg/action" + releaseV1 "helm.sh/helm/v4/pkg/release/v1" ) -func GetReleaseHistory(name string, conf *action.Configuration) ([]*release.Release, error) { +func GetReleaseHistory(name string, conf *action.Configuration) ([]*releaseV1.Release, error) { client := action.NewHistory(conf) - history, err := client.Run(name) + results, err := client.Run(name) if err != nil { return nil, err } - if history == nil { + if results == nil { return nil, ErrReleaseNotFound } - return history, nil + releases := make([]*releaseV1.Release, 0, len(results)) + for _, r := range results { + rel, ok := r.(*releaseV1.Release) + if !ok { + return nil, fmt.Errorf("unexpected release type %T", r) + } + releases = append(releases, rel) + } + + return releases, nil } diff --git a/pkg/helm/actions/release_history_test.go b/pkg/helm/actions/release_history_test.go index a77e0e17998..5775f1ad172 100644 --- a/pkg/helm/actions/release_history_test.go +++ b/pkg/helm/actions/release_history_test.go @@ -5,28 +5,29 @@ import ( "io" "testing" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chartutil" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/chart/common" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + rcommon "helm.sh/helm/v4/pkg/release/common" + releaseV1 "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" ) func TestGetReleaseHistory(t *testing.T) { tests := []struct { name string - release release.Release + release releaseV1.Release err error versions []int }{ { name: "existing release should return list of particular release history", - release: release.Release{ + release: releaseV1.Release{ Version: 1, Name: "valid-release", - Info: &release.Info{ - Status: release.StatusDeployed, + Info: &releaseV1.Info{ + Status: rcommon.StatusDeployed, }, }, err: nil, @@ -41,8 +42,7 @@ func TestGetReleaseHistory(t *testing.T) { actionConfig := &action.Configuration{ Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } err := store.Create(&tt.release) @@ -50,10 +50,10 @@ func TestGetReleaseHistory(t *testing.T) { t.Error(err) } - tt.release.Version = 2 - store.Create(&tt.release) + tt.release.Version = 2 + store.Create(&tt.release) - resp, err := GetReleaseHistory(tt.release.Name, actionConfig) + resp, err := GetReleaseHistory(tt.release.Name, actionConfig) if err != tt.err { t.Error(err) } @@ -77,13 +77,13 @@ func TestGetReleaseHistory(t *testing.T) { func TestNonExistGetReleaseHistory(t *testing.T) { tests := []struct { name string - release release.Release + release releaseV1.Release err error noOfVersions int }{ { name: "non exist release history should throw an error", - release: release.Release{ + release: releaseV1.Release{ Name: "invalid-release", }, noOfVersions: 0, @@ -97,8 +97,7 @@ func TestNonExistGetReleaseHistory(t *testing.T) { actionConfig := &action.Configuration{ Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } resp, err := GetReleaseHistory(tt.release.Name, actionConfig) diff --git a/pkg/helm/actions/rollback_release.go b/pkg/helm/actions/rollback_release.go index f6bd7d407b5..42b0863b76a 100644 --- a/pkg/helm/actions/rollback_release.go +++ b/pkg/helm/actions/rollback_release.go @@ -4,11 +4,11 @@ import ( "errors" "strings" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v4/pkg/action" + releaseV1 "helm.sh/helm/v4/pkg/release/v1" ) -func RollbackRelease(releaseName string, revision int, conf *action.Configuration) (*release.Release, error) { +func RollbackRelease(releaseName string, revision int, conf *action.Configuration) (*releaseV1.Release, error) { if revision <= 0 { return nil, errors.New("Revision no. should be more than 0") } diff --git a/pkg/helm/actions/rollback_release_test.go b/pkg/helm/actions/rollback_release_test.go index f34e9a2f66a..5483576f315 100644 --- a/pkg/helm/actions/rollback_release_test.go +++ b/pkg/helm/actions/rollback_release_test.go @@ -6,39 +6,40 @@ import ( "io" "testing" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chartutil" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/chart/common" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + rcommon "helm.sh/helm/v4/pkg/release/common" + releaseV1 "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" ) func TestRollbackRelease(t *testing.T) { tests := []struct { name string - release release.Release + release releaseV1.Release err error rollbackTo int }{ { name: "rolling back to existing previous release should rollback successfully", - release: release.Release{ + release: releaseV1.Release{ Version: 1, Name: "valid-release", - Info: &release.Info{ - Status: release.StatusDeployed, + Info: &releaseV1.Info{ + Status: rcommon.StatusDeployed, }, }, rollbackTo: 1, }, { name: "rolling back to invalid release no. should throw an error", - release: release.Release{ + release: releaseV1.Release{ Version: 1, Name: "valid-release", - Info: &release.Info{ - Status: release.StatusDeployed, + Info: &releaseV1.Info{ + Status: rcommon.StatusDeployed, }, }, err: errors.New("Revision no. should be more than 0"), @@ -53,8 +54,7 @@ func TestRollbackRelease(t *testing.T) { actionConfig := &action.Configuration{ Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } err := store.Create(&tt.release) @@ -98,8 +98,7 @@ func TestRollbackNonExistRelease(t *testing.T) { actionConfig := &action.Configuration{ Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } _, err := RollbackRelease(tt.releaseName, tt.rollbackTo, actionConfig) diff --git a/pkg/helm/actions/setup_test.go b/pkg/helm/actions/setup_test.go index 78ed335c738..d2c063565e1 100644 --- a/pkg/helm/actions/setup_test.go +++ b/pkg/helm/actions/setup_test.go @@ -11,10 +11,10 @@ import ( "testing" "time" - "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v4/pkg/cli" ) -const helmModulePath = "helm.sh/helm/v3" +const helmModulePath = "helm.sh/helm/v4" func setSettings(settings *cli.EnvSettings) { settings.RepositoryCache = os.TempDir() @@ -30,7 +30,7 @@ func helmVersionFromGoMod() string { if err != nil { continue } - // Match line like " helm.sh/helm/v3 v3.18.5" or " helm.sh/helm/v3 v3.18.5 // indirect". + // Match a require line like "helm.sh/helm/v4 v4.1.4" or "helm.sh/helm/v4 v4.1.4 // indirect". re := regexp.MustCompile(`(?m)^\s*` + regexp.QuoteMeta(helmModulePath) + `\s+(\S+)`) if m := re.FindSubmatch(data); len(m) >= 2 { return strings.TrimSpace(string(m[1])) @@ -39,7 +39,7 @@ func helmVersionFromGoMod() string { return "" } -// setHelmVersionFromBuildInfo sets HELM_VERSION from the Go module's helm.sh/helm/v3 +// setHelmVersionFromBuildInfo sets HELM_VERSION from the Go module's helm.sh/helm/v4 // dependency. Prefers debug.ReadBuildInfo(); when that is empty for test binaries (Go #33976), // falls back to parsing go.mod. func setHelmVersionFromBuildInfo() error { diff --git a/pkg/helm/actions/template.go b/pkg/helm/actions/template.go index 3837aada462..4e328d303dc 100644 --- a/pkg/helm/actions/template.go +++ b/pkg/helm/actions/template.go @@ -9,10 +9,11 @@ import ( "strings" "github.com/openshift/api/helm/v1beta1" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/releaseutil" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/chart/v2/loader" + "helm.sh/helm/v4/pkg/cli" + releaseV1 "helm.sh/helm/v4/pkg/release/v1" + releaseutil "helm.sh/helm/v4/pkg/release/v1/util" "k8s.io/client-go/dynamic" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" ) @@ -23,19 +24,19 @@ func RenderManifests(name string, url string, vals map[string]interface{}, conf var err error var chartLocation string response := make(map[string]string) - validate := false client := action.NewInstall(conf) - client.DryRun = true - includeCrds := true + client.DryRunStrategy = action.DryRunClient + client.SkipCRDs = false client.ReleaseName = "RELEASE-NAME" client.Replace = true // Skip the releaseName check - client.ClientOnly = !validate + client.DependencyUpdate = true + client.SkipSchemaValidation = true emptyResponse := "" tlsFiles := []*os.File{} if indexEntry == "" { chartInfo, err = getChartInfoFromChartUrl(url, ns, dynamicClient, coreClient) if err != nil { - return "", err + return emptyResponse, err } } else { chartInfo = getChartInfoFromIndexEntry(indexEntry, ns, url) @@ -43,19 +44,19 @@ func RenderManifests(name string, url string, vals map[string]interface{}, conf client.ChartPathOptions.Version = chartInfo.Version connectionConfig, isClusterScoped, err := getRepositoryConnectionConfig(chartInfo.RepositoryName, ns, dynamicClient) if err != nil { - return "", err + return emptyResponse, err } if isClusterScoped { clusterConnectionConfig := connectionConfig.(v1beta1.ConnectionConfig) tlsFiles, err = setUpAuthentication(&client.ChartPathOptions, &clusterConnectionConfig, coreClient) if err != nil { - return "", fmt.Errorf("error setting up authentication: %w", err) + return emptyResponse, fmt.Errorf("error setting up authentication: %w", err) } } else { namespaceConnectionConfig := connectionConfig.(v1beta1.ConnectionConfigNamespaceScoped) tlsFiles, err = setUpAuthenticationProject(&client.ChartPathOptions, &namespaceConnectionConfig, coreClient, ns) if err != nil { - return "", fmt.Errorf("error setting up authentication: %w", err) + return emptyResponse, fmt.Errorf("error setting up authentication: %w", err) } } client.ReleaseName = name @@ -74,15 +75,19 @@ func RenderManifests(name string, url string, vals map[string]interface{}, conf return emptyResponse, err } - rel, err := client.Run(ch, vals) + result, err := client.Run(ch, vals) if err != nil { return emptyResponse, err } + rel, ok := result.(*releaseV1.Release) + if !ok { + return emptyResponse, fmt.Errorf("unexpected release type %T", result) + } var manifests bytes.Buffer var output bytes.Buffer - if includeCrds { + if !client.SkipCRDs { for _, f := range rel.Chart.CRDs() { fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", f.Name, f.Data) } diff --git a/pkg/helm/actions/template_test.go b/pkg/helm/actions/template_test.go index ed1cd94847d..0468680ef5e 100644 --- a/pkg/helm/actions/template_test.go +++ b/pkg/helm/actions/template_test.go @@ -6,11 +6,11 @@ import ( "testing" "github.com/stretchr/testify/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chartutil" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/chart/common" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" k8sfake "k8s.io/client-go/kubernetes/fake" @@ -97,8 +97,7 @@ func TestRenderManifests(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } var m map[string]interface{} @@ -183,8 +182,7 @@ func TestRenderManifestsBasicAuth(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } var m map[string]interface{} diff --git a/pkg/helm/actions/uninstall_release.go b/pkg/helm/actions/uninstall_release.go index 657b67cf48e..ea168f08c8b 100644 --- a/pkg/helm/actions/uninstall_release.go +++ b/pkg/helm/actions/uninstall_release.go @@ -6,13 +6,14 @@ import ( "strings" "github.com/openshift/console/pkg/helm/metrics" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v4/pkg/action" + releasecommon "helm.sh/helm/v4/pkg/release" + releaseV1 "helm.sh/helm/v4/pkg/release/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" ) -func UninstallRelease(name string, conf *action.Configuration) (*release.UninstallReleaseResponse, error) { +func UninstallRelease(name string, conf *action.Configuration) (*releasecommon.UninstallReleaseResponse, error) { client := action.NewUninstall(conf) resp, err := client.Run(name) if err != nil { @@ -22,9 +23,11 @@ func UninstallRelease(name string, conf *action.Configuration) (*release.Uninsta return nil, err } - ch := resp.Release.Chart - if ch != nil && ch.Metadata != nil && ch.Metadata.Name != "" && ch.Metadata.Version != "" { - metrics.HandleconsoleHelmUninstallsTotal(ch.Metadata.Name, ch.Metadata.Version) + if rel, ok := resp.Release.(*releaseV1.Release); ok && rel != nil { + ch := rel.Chart + if ch != nil && ch.Metadata != nil && ch.Metadata.Name != "" && ch.Metadata.Version != "" { + metrics.HandleconsoleHelmUninstallsTotal(ch.Metadata.Name, ch.Metadata.Version) + } } return resp, nil @@ -37,10 +40,11 @@ func UninstallReleaseAsync(name string, ns string, version string, conf *action. if err != nil || resp == nil { return } - - ch := resp.Release.Chart - if ch != nil && ch.Metadata != nil && ch.Metadata.Name != "" && ch.Metadata.Version != "" { - metrics.HandleconsoleHelmUninstallsTotal(ch.Metadata.Name, ch.Metadata.Version) + if rel, ok := resp.Release.(*releaseV1.Release); ok && rel != nil { + ch := rel.Chart + if ch != nil && ch.Metadata != nil && ch.Metadata.Name != "" && ch.Metadata.Version != "" { + metrics.HandleconsoleHelmUninstallsTotal(ch.Metadata.Name, ch.Metadata.Version) + } } }() secretName := fmt.Sprintf("sh.helm.release.v1.%v.v%v", name, version) diff --git a/pkg/helm/actions/uninstall_release_test.go b/pkg/helm/actions/uninstall_release_test.go index aa1ed542e25..d4e8b12bece 100644 --- a/pkg/helm/actions/uninstall_release_test.go +++ b/pkg/helm/actions/uninstall_release_test.go @@ -8,26 +8,27 @@ import ( "time" "github.com/stretchr/testify/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chartutil" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/chart/common" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + rcommon "helm.sh/helm/v4/pkg/release/common" + releaseV1 "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" k8sfake "k8s.io/client-go/kubernetes/fake" ) func TestUninstallRelease(t *testing.T) { tests := []struct { name string - release *release.Release + release *releaseV1.Release }{ { name: "successful release uninstall should remove release installed", - release: &release.Release{ + release: &releaseV1.Release{ Name: "test-release", - Info: &release.Info{ - Status: release.StatusDeployed, + Info: &releaseV1.Info{ + Status: rcommon.StatusDeployed, }, }, }, @@ -39,8 +40,7 @@ func TestUninstallRelease(t *testing.T) { actionConfig := &action.Configuration{ Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } // create fake release err := store.Create(tt.release) @@ -48,8 +48,10 @@ func TestUninstallRelease(t *testing.T) { t.Error(err) } resp, err := UninstallRelease(tt.release.Name, actionConfig) - if resp != nil && resp.Release.Info.Status != release.StatusUninstalled { - t.Error(errors.New("Release status is not uninstalled")) + if resp != nil { + if rel, ok := resp.Release.(*releaseV1.Release); ok && rel.Info.Status != rcommon.StatusUninstalled { + t.Error(errors.New("Release status is not uninstalled")) + } } }) } @@ -58,12 +60,12 @@ func TestUninstallRelease(t *testing.T) { func TestUninstallInvalidRelease(t *testing.T) { tests := []struct { name string - release *release.Release + release *releaseV1.Release err error }{ { name: "non exist release uninstall should error out with no release found", - release: &release.Release{ + release: &releaseV1.Release{ Name: "invalid-release", }, err: ErrReleaseNotFound, @@ -76,15 +78,16 @@ func TestUninstallInvalidRelease(t *testing.T) { actionConfig := &action.Configuration{ Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } resp, err := UninstallRelease(tt.release.Name, actionConfig) if err != nil && err.Error() != tt.err.Error() { t.Error(err) } - if resp != nil && resp.Release.Info.Status != release.StatusUninstalled { - t.Error(errors.New("Release status is not uninstalled")) + if resp != nil { + if rel, ok := resp.Release.(*releaseV1.Release); ok && rel.Info.Status != rcommon.StatusUninstalled { + t.Error(errors.New("Release status is not uninstalled")) + } } }) } @@ -98,7 +101,7 @@ func TestUninstallReleaseAsync(t *testing.T) { namespace string requireError bool releaseName string - release *release.Release + release *releaseV1.Release }{ { name: "successful release uninstall should remove release installed", @@ -107,10 +110,10 @@ func TestUninstallReleaseAsync(t *testing.T) { releaseName: "test-release", namespace: "default", version: "1", - release: &release.Release{ + release: &releaseV1.Release{ Name: "test-release", - Info: &release.Info{ - Status: release.StatusDeployed, + Info: &releaseV1.Info{ + Status: rcommon.StatusDeployed, }, }, }, @@ -122,8 +125,7 @@ func TestUninstallReleaseAsync(t *testing.T) { actionConfig := &action.Configuration{ Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } // create fake release err := store.Create(tt.release) @@ -148,7 +150,7 @@ func TestUninstallReleaseAsync(t *testing.T) { func TestUninstallInvalidReleaseAsync(t *testing.T) { tests := []struct { name string - release *release.Release + release *releaseV1.Release version string namespace string releaseName string @@ -156,7 +158,7 @@ func TestUninstallInvalidReleaseAsync(t *testing.T) { }{ { name: "non exist release uninstall should error out with no release found", - release: &release.Release{ + release: &releaseV1.Release{ Name: "invalid-release", }, namespace: "default", @@ -172,8 +174,7 @@ func TestUninstallInvalidReleaseAsync(t *testing.T) { actionConfig := &action.Configuration{ Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } clientInterface := k8sfake.NewSimpleClientset() coreClient := clientInterface.CoreV1() diff --git a/pkg/helm/actions/upgrade_release.go b/pkg/helm/actions/upgrade_release.go index 1b4cb9a99de..c074fd54a01 100644 --- a/pkg/helm/actions/upgrade_release.go +++ b/pkg/helm/actions/upgrade_release.go @@ -9,10 +9,12 @@ import ( "github.com/openshift/api/helm/v1beta1" "github.com/openshift/console/pkg/helm/metrics" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v4/pkg/action" + helmchart "helm.sh/helm/v4/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" + "helm.sh/helm/v4/pkg/kube" + releaseV1 "helm.sh/helm/v4/pkg/release/v1" kv1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/dynamic" @@ -29,8 +31,9 @@ func UpgradeRelease( coreClient corev1client.CoreV1Interface, fileCleanUp bool, indexEntry string, -) (*release.Release, error) { +) (*releaseV1.Release, error) { client := action.NewUpgrade(conf) + client.ServerSideApply = "false" client.Namespace = releaseNamespace var ch *chart.Chart var cp, chartLocation string @@ -98,10 +101,8 @@ func UpgradeRelease( } } - if req := ch.Metadata.Dependencies; req != nil { - if err := action.CheckDependencies(ch, req); err != nil { - return nil, err - } + if err := checkChartDependencies(ch); err != nil { + return nil, err } // Ensure chart URL is properly set in the upgrade chart @@ -112,10 +113,14 @@ func UpgradeRelease( ch.Metadata.Annotations["chart_url"] = chartUrl } - rel, err = client.Run(releaseName, ch, vals) + result, err := client.Run(releaseName, ch, vals) if err != nil { return nil, err } + rel, ok := result.(*releaseV1.Release) + if !ok { + return nil, fmt.Errorf("unexpected release type %T", result) + } if ch.Metadata.Name != "" && ch.Metadata.Version != "" { metrics.HandleconsoleHelmUpgradesTotal(ch.Metadata.Name, ch.Metadata.Version) @@ -144,6 +149,8 @@ func UpgradeReleaseAsync( indexEntry string, ) (*kv1.Secret, error) { client := action.NewUpgrade(conf) + client.ServerSideApply = "false" + client.WaitStrategy = kube.LegacyStrategy client.Namespace = releaseNamespace var ch *chart.Chart var cp, chartLocation string @@ -211,10 +218,8 @@ func UpgradeReleaseAsync( } } - if req := ch.Metadata.Dependencies; req != nil { - if err := action.CheckDependencies(ch, req); err != nil { - return nil, err - } + if err := checkChartDependencies(ch); err != nil { + return nil, err } // Ensure chart URL is properly set in the upgrade chart @@ -251,3 +256,15 @@ func UpgradeReleaseAsync( } return &secret, nil } + +func checkChartDependencies(ch *chart.Chart) error { + deps := ch.Metadata.Dependencies + if len(deps) == 0 { + return nil + } + reqs := make([]helmchart.Dependency, len(deps)) + for i, d := range deps { + reqs[i] = d + } + return action.CheckDependencies(ch, reqs) +} diff --git a/pkg/helm/actions/upgrade_release_test.go b/pkg/helm/actions/upgrade_release_test.go index ebe73d652c5..c4edf247f35 100644 --- a/pkg/helm/actions/upgrade_release_test.go +++ b/pkg/helm/actions/upgrade_release_test.go @@ -9,19 +9,20 @@ import ( "time" "github.com/stretchr/testify/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" - helmTime "helm.sh/helm/v3/pkg/time" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/chart/common" + chart "helm.sh/helm/v4/pkg/chart/v2" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + rcommon "helm.sh/helm/v4/pkg/release/common" + releaseV1 "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" k8sfake "k8s.io/client-go/kubernetes/fake" + helmTime "time" ) func TestUpgradeReleaseWithoutDependencies(t *testing.T) { @@ -105,8 +106,7 @@ func TestUpgradeReleaseWithoutDependencies(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } if tt.createNamespace && tt.namespace != configNamespace { @@ -114,10 +114,10 @@ func TestUpgradeReleaseWithoutDependencies(t *testing.T) { objs = append(objs, nsSpec) } - r := release.Release{ + r := releaseV1.Release{ Name: "test", Namespace: tt.namespace, - Info: &release.Info{ + Info: &releaseV1.Info{ FirstDeployed: helmTime.Time{}, Status: "deployed", }, @@ -160,7 +160,7 @@ func TestUpgradeReleaseWithoutDependencies(t *testing.T) { require.NoError(t, err) require.Equal(t, r.Name, rel.Name) require.Equal(t, r.Namespace, rel.Namespace) - require.Equal(t, release.StatusDeployed, rel.Info.Status) + require.Equal(t, rcommon.StatusDeployed, rel.Info.Status) require.Equal(t, tt.chartVersion, rel.Chart.Metadata.Version) require.Equal(t, 2, rel.Version) require.Equal(t, r.Chart.Metadata.Annotations["chart_url"], rel.Chart.Metadata.Annotations["chart_url"]) @@ -212,16 +212,15 @@ func TestUpgradeReleaseWithDependencies(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } client := K8sDynamicClientFromCRs(tt.helmCRS...) clientInterface := k8sfake.NewSimpleClientset() coreClient := clientInterface.CoreV1() - r := release.Release{ + r := releaseV1.Release{ Name: "test", Namespace: "test-namespace", - Info: &release.Info{ + Info: &releaseV1.Info{ FirstDeployed: helmTime.Time{}, Status: "deployed", }, @@ -244,7 +243,7 @@ func TestUpgradeReleaseWithDependencies(t *testing.T) { require.NoError(t, err) require.Equal(t, r.Name, rel.Name) require.Equal(t, r.Namespace, rel.Namespace) - require.Equal(t, release.StatusDeployed, rel.Info.Status) + require.Equal(t, rcommon.StatusDeployed, rel.Info.Status) require.Equal(t, tt.chartVersion, rel.Chart.Metadata.Version) require.Equal(t, 2, rel.Version) require.Equal(t, r.Chart.Metadata.Annotations["chart_url"], rel.Chart.Metadata.Annotations["chart_url"]) @@ -280,8 +279,7 @@ func TestUpgradeNonExistRelease(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } client := K8sDynamicClientFromCRs() clientInterface := k8sfake.NewSimpleClientset() @@ -342,16 +340,15 @@ func TestUpgradeReleaseWithCustomValues(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } client := K8sDynamicClientFromCRs(tt.helmCRS...) clientInterface := k8sfake.NewSimpleClientset() coreClient := clientInterface.CoreV1() - r := release.Release{ + r := releaseV1.Release{ Name: "test", Namespace: "test-namespace", - Info: &release.Info{ + Info: &releaseV1.Info{ FirstDeployed: helmTime.Time{}, Status: "deployed", }, @@ -374,7 +371,7 @@ func TestUpgradeReleaseWithCustomValues(t *testing.T) { require.NoError(t, err) require.Equal(t, r.Name, rel.Name) require.Equal(t, r.Namespace, rel.Namespace) - require.Equal(t, release.StatusDeployed, rel.Info.Status) + require.Equal(t, rcommon.StatusDeployed, rel.Info.Status) require.Equal(t, tt.chartVersion, rel.Chart.Metadata.Version) require.Equal(t, 2, rel.Version) require.Equal(t, r.Chart.Metadata.Annotations["chart_url"], rel.Chart.Metadata.Annotations["chart_url"]) @@ -454,8 +451,7 @@ func TestUpgradeReleaseWithoutDependenciesAsync(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } if tt.createNamespace && tt.namespace != configNamespace { @@ -463,10 +459,10 @@ func TestUpgradeReleaseWithoutDependenciesAsync(t *testing.T) { objs = append(objs, nsSpec) } - r := release.Release{ + r := releaseV1.Release{ Name: tt.releaseName, Namespace: tt.namespace, - Info: &release.Info{ + Info: &releaseV1.Info{ FirstDeployed: helmTime.Time{}, Status: "deployed", }, @@ -573,8 +569,7 @@ func TestUpgradeReleaseWithDependenciesAsync(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } client := K8sDynamicClientFromCRs(tt.helmCRS...) clientInterface := k8sfake.NewSimpleClientset() @@ -582,10 +577,10 @@ func TestUpgradeReleaseWithDependenciesAsync(t *testing.T) { secretsDriver := driver.NewSecrets(coreClient.Secrets(tt.releaseNamespace)) var rel *v1.Secret var err error - r := release.Release{ + r := releaseV1.Release{ Name: tt.releaseName, Namespace: tt.releaseNamespace, - Info: &release.Info{ + Info: &releaseV1.Info{ FirstDeployed: helmTime.Time{}, Status: "deployed", }, @@ -665,14 +660,13 @@ func TestUpgradeReleaseWithCustomValuesAsync(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } - r := release.Release{ + r := releaseV1.Release{ Name: tt.releaseName, Namespace: tt.releaseNamespace, - Info: &release.Info{ + Info: &releaseV1.Info{ FirstDeployed: helmTime.Time{}, Status: "deployed", }, diff --git a/pkg/helm/chartproxy/proxy.go b/pkg/helm/chartproxy/proxy.go index 11b7512080e..22faf42320f 100644 --- a/pkg/helm/chartproxy/proxy.go +++ b/pkg/helm/chartproxy/proxy.go @@ -4,8 +4,8 @@ import ( "sort" "strings" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/repo" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + repo "helm.sh/helm/v4/pkg/repo/v1" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" v1 "k8s.io/client-go/kubernetes/typed/core/v1" diff --git a/pkg/helm/chartproxy/proxy_test.go b/pkg/helm/chartproxy/proxy_test.go index 869bbd523fa..6b006ca6060 100644 --- a/pkg/helm/chartproxy/proxy_test.go +++ b/pkg/helm/chartproxy/proxy_test.go @@ -6,7 +6,7 @@ import ( "testing" "golang.org/x/net/context" - "helm.sh/helm/v3/pkg/repo" + repo "helm.sh/helm/v4/pkg/repo/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/client-go/rest" diff --git a/pkg/helm/chartproxy/repos.go b/pkg/helm/chartproxy/repos.go index 0c95827129d..76e1287d9ed 100644 --- a/pkg/helm/chartproxy/repos.go +++ b/pkg/helm/chartproxy/repos.go @@ -14,7 +14,7 @@ import ( "sigs.k8s.io/yaml" - "helm.sh/helm/v3/pkg/repo" + repo "helm.sh/helm/v4/pkg/repo/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" diff --git a/pkg/helm/chartproxy/repos_test.go b/pkg/helm/chartproxy/repos_test.go index 66c38086ba5..22c61baeddc 100644 --- a/pkg/helm/chartproxy/repos_test.go +++ b/pkg/helm/chartproxy/repos_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - helmrepo "helm.sh/helm/v3/pkg/repo" + helmrepo "helm.sh/helm/v4/pkg/repo/v1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" diff --git a/pkg/helm/chartverifier/chart_verifier.go b/pkg/helm/chartverifier/chart_verifier.go index 75776cf9703..3988b2d6b44 100644 --- a/pkg/helm/chartverifier/chart_verifier.go +++ b/pkg/helm/chartverifier/chart_verifier.go @@ -5,7 +5,7 @@ import ( reportSummary "github.com/redhat-certification/chart-verifier/pkg/chartverifier/reportsummary" "github.com/redhat-certification/chart-verifier/pkg/chartverifier/verifier" - "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v4/pkg/action" ) func ChartVerifier(charturl string, values map[string]interface{}, conf *action.Configuration) (string, error) { diff --git a/pkg/helm/chartverifier/chart_verifier_test.go b/pkg/helm/chartverifier/chart_verifier_test.go index be018e751f7..0044b8fd055 100644 --- a/pkg/helm/chartverifier/chart_verifier_test.go +++ b/pkg/helm/chartverifier/chart_verifier_test.go @@ -6,11 +6,11 @@ import ( "testing" "github.com/stretchr/testify/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chartutil" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/chart/common" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" "k8s.io/client-go/rest" ) @@ -29,8 +29,7 @@ func TestVerifyApi(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } values := map[string]interface{}{ "provider": "developer-console", @@ -46,8 +45,7 @@ func TestVerifyApiChartUrlNotPresent(t *testing.T) { RESTClientGetter: FakeConfig{}, Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, - Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Capabilities: common.DefaultCapabilities, } values := map[string]interface{}{ "provider": "developer-console", diff --git a/pkg/helm/handlers/client.go b/pkg/helm/handlers/client.go index 6939fbb431c..b2bb7e27323 100644 --- a/pkg/helm/handlers/client.go +++ b/pkg/helm/handlers/client.go @@ -3,7 +3,7 @@ package handlers import ( "fmt" - "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v4/pkg/action" "k8s.io/client-go/dynamic" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" ) diff --git a/pkg/helm/handlers/handlerChartVerifier.go b/pkg/helm/handlers/handlerChartVerifier.go index fa1fc397c82..44b39293a84 100644 --- a/pkg/helm/handlers/handlerChartVerifier.go +++ b/pkg/helm/handlers/handlerChartVerifier.go @@ -11,7 +11,7 @@ import ( "github.com/openshift/console/pkg/helm/chartverifier" "github.com/openshift/console/pkg/serverutils" "github.com/openshift/console/pkg/version" - "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v4/pkg/action" "k8s.io/client-go/rest" ) diff --git a/pkg/helm/handlers/handler_chartVerifier_test.go b/pkg/helm/handlers/handler_chartVerifier_test.go index 7eed97f3786..1ef663ab416 100644 --- a/pkg/helm/handlers/handler_chartVerifier_test.go +++ b/pkg/helm/handlers/handler_chartVerifier_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/openshift/console/pkg/auth" - "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v4/pkg/action" ) var fakeReportSummary = `{"passed":"0","failed":"0","messages":null}` diff --git a/pkg/helm/handlers/handler_test.go b/pkg/helm/handlers/handler_test.go index 27b9d5e0def..06dbd177874 100644 --- a/pkg/helm/handlers/handler_test.go +++ b/pkg/helm/handlers/handler_test.go @@ -8,10 +8,12 @@ import ( "strings" "testing" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/repo" + "helm.sh/helm/v4/pkg/action" + chart "helm.sh/helm/v4/pkg/chart/v2" + releasecommon "helm.sh/helm/v4/pkg/release" + rcommon "helm.sh/helm/v4/pkg/release/common" + releaseV1 "helm.sh/helm/v4/pkg/release/v1" + repo "helm.sh/helm/v4/pkg/repo/v1" kv1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -27,13 +29,13 @@ import ( "github.com/openshift/console/pkg/helm/chartproxy" ) -var fakeReleaseList = []*release.Release{ +var fakeReleaseList = []*releaseV1.Release{ { Name: "Test", }, } -var fakeReleaseHistory = []*release.Release{ +var fakeReleaseHistory = []*releaseV1.Release{ { Name: "test", Version: 1, @@ -49,11 +51,11 @@ var fakeSecret = kv1.Secret{ Name: "Test", }, } -var fakeRelease = release.Release{ +var fakeRelease = releaseV1.Release{ Name: "Test", } -var fakeUninstallResponse = &release.UninstallReleaseResponse{ +var fakeUninstallResponse = &releasecommon.UninstallReleaseResponse{ Release: &fakeRelease, Info: "", } @@ -66,8 +68,8 @@ func fakeHelmHandler() helmHandlers { } } -func fakeInstallChart(mockedRelease *release.Release, err error) func(ns string, name string, url string, values map[string]interface{}, conf *action.Configuration, client dynamic.Interface, coreClient corev1client.CoreV1Interface, fileCleanup bool, indexEntry string) (*release.Release, error) { - return func(ns string, name string, url string, values map[string]interface{}, conf *action.Configuration, cliet dynamic.Interface, coreClient corev1client.CoreV1Interface, fileCleanup bool, indexEntry string) (r *release.Release, er error) { +func fakeInstallChart(mockedRelease *releaseV1.Release, err error) func(ns string, name string, url string, values map[string]interface{}, conf *action.Configuration, client dynamic.Interface, coreClient corev1client.CoreV1Interface, fileCleanup bool, indexEntry string) (*releaseV1.Release, error) { + return func(ns string, name string, url string, values map[string]interface{}, conf *action.Configuration, cliet dynamic.Interface, coreClient corev1client.CoreV1Interface, fileCleanup bool, indexEntry string) (r *releaseV1.Release, er error) { return mockedRelease, err } } @@ -78,8 +80,8 @@ func fakeInstallChartAsync(mockedSecret *kv1.Secret, err error) func(ns string, } } -func fakeListReleases(mockedReleases []*release.Release, err error) func(conf *action.Configuration, isTopology bool) ([]*release.Release, error) { - return func(conf *action.Configuration, isTopology bool) (releases []*release.Release, er error) { +func fakeListReleases(mockedReleases []*releaseV1.Release, err error) func(conf *action.Configuration, isTopology bool) ([]*releaseV1.Release, error) { + return func(conf *action.Configuration, isTopology bool) (releases []*releaseV1.Release, er error) { return mockedReleases, err } } @@ -90,8 +92,8 @@ func fakeGetManifest(mockedManifest string, err error) func(name string, url str } } -func fakeGetRelease(name string, t *testing.T, mockedRelease *release.Release, err error) func(releaseName string, conf *action.Configuration) (*release.Release, error) { - return func(releaseName string, conf *action.Configuration) (r *release.Release, er error) { +func fakeGetRelease(name string, t *testing.T, mockedRelease *releaseV1.Release, err error) func(releaseName string, conf *action.Configuration) (*releaseV1.Release, error) { + return func(releaseName string, conf *action.Configuration) (r *releaseV1.Release, er error) { if name != releaseName { t.Errorf("release name mismatch expected is %s, received %s", name, releaseName) } @@ -105,8 +107,8 @@ func mockedHelmGetChart(c *chart.Chart, e error) func(url string, conf *action.C } } -func fakeGetReleaseHistory(name string, fakeHistory []*release.Release, t *testing.T, err error) func(name string, conf *action.Configuration) ([]*release.Release, error) { - return func(n string, conf *action.Configuration) ([]*release.Release, error) { +func fakeGetReleaseHistory(name string, fakeHistory []*releaseV1.Release, t *testing.T, err error) func(name string, conf *action.Configuration) ([]*releaseV1.Release, error) { + return func(n string, conf *action.Configuration) ([]*releaseV1.Release, error) { if name != n { t.Errorf("release name mismatch expected is %s, received %s", n, name) } @@ -114,8 +116,8 @@ func fakeGetReleaseHistory(name string, fakeHistory []*release.Release, t *testi } } -func fakeUninstallRelease(name string, t *testing.T, fakeResp *release.UninstallReleaseResponse, err error) func(name string, conf *action.Configuration) (*release.UninstallReleaseResponse, error) { - return func(n string, conf *action.Configuration) (*release.UninstallReleaseResponse, error) { +func fakeUninstallRelease(name string, t *testing.T, fakeResp *releasecommon.UninstallReleaseResponse, err error) func(name string, conf *action.Configuration) (*releasecommon.UninstallReleaseResponse, error) { + return func(n string, conf *action.Configuration) (*releasecommon.UninstallReleaseResponse, error) { if n != name { t.Errorf("release name mismatch expected is %s, received %s", n, name) } @@ -123,8 +125,8 @@ func fakeUninstallRelease(name string, t *testing.T, fakeResp *release.Uninstall } } -func fakeUpgradeRelease(name, ns string, t *testing.T, fakeRelease *release.Release, err error) func(ns, name, url string, vals map[string]interface{}, conf *action.Configuration, client dynamic.Interface, coreClient corev1client.CoreV1Interface, fileCleanUp bool, indexEntry string) (*release.Release, error) { - return func(namespace, n, url string, vals map[string]interface{}, conf *action.Configuration, client dynamic.Interface, coreClient corev1client.CoreV1Interface, fileCleanUp bool, indexEntry string) (*release.Release, error) { +func fakeUpgradeRelease(name, ns string, t *testing.T, fakeRelease *releaseV1.Release, err error) func(ns, name, url string, vals map[string]interface{}, conf *action.Configuration, client dynamic.Interface, coreClient corev1client.CoreV1Interface, fileCleanUp bool, indexEntry string) (*releaseV1.Release, error) { + return func(namespace, n, url string, vals map[string]interface{}, conf *action.Configuration, client dynamic.Interface, coreClient corev1client.CoreV1Interface, fileCleanUp bool, indexEntry string) (*releaseV1.Release, error) { if namespace != ns { t.Errorf("Namespace mismatch expected %s received %s", ns, namespace) } @@ -147,8 +149,8 @@ func fakeUpgradeReleaseAsync(name, ns string, t *testing.T, fakeSecret *kv1.Secr } } -func fakeRollbackRelease(name string, t *testing.T, rel *release.Release, err error) func(name string, revision int, conf *action.Configuration) (*release.Release, error) { - return func(n string, revision int, conf *action.Configuration) (*release.Release, error) { +func fakeRollbackRelease(name string, t *testing.T, rel *releaseV1.Release, err error) func(name string, revision int, conf *action.Configuration) (*releaseV1.Release, error) { + return func(n string, revision int, conf *action.Configuration) (*releaseV1.Release, error) { if name != n { t.Errorf("Release name mismatch expected is %s and received %s", name, n) } @@ -211,7 +213,7 @@ func TestHelmHandlers_HandleHelmList(t *testing.T) { tests := []struct { name string expectedResponse string - releaseList []*release.Release + releaseList []*releaseV1.Release error httpStatusCode int }{ @@ -255,7 +257,7 @@ func TestHelmHandlers_HandleHelmInstall(t *testing.T) { tests := []struct { name string expectedResponse string - installedRelease release.Release + installedRelease releaseV1.Release error httpStatusCode int }{ @@ -346,7 +348,7 @@ func TestHelmHandlers_HandleGetRelease(t *testing.T) { tests := []struct { name string expectedResponse string - release *release.Release + release *releaseV1.Release releaseName string error httpStatusCode int @@ -442,7 +444,7 @@ func TestHelmHandlers_HandleGetReleaseHistory(t *testing.T) { tests := []struct { name string expectedResponse string - history []*release.Release + history []*releaseV1.Release expectedContentType string error httpStatusCode int @@ -500,7 +502,7 @@ func TestHelmHandlers_HandleHelmUninstallRelease(t *testing.T) { tests := []struct { name string expectedResponse string - uninstallResponse *release.UninstallReleaseResponse + uninstallResponse *releasecommon.UninstallReleaseResponse expectedContentType string releaseName string releaseNamespace string @@ -562,7 +564,7 @@ func TestHelmHandlers_HandleHelmRollbackRelease(t *testing.T) { tests := []struct { name string expectedResponse string - release *release.Release + release *releaseV1.Release expectedContentType string body string releaseName string @@ -584,10 +586,10 @@ func TestHelmHandlers_HandleHelmRollbackRelease(t *testing.T) { name: "Valid chart rollback release test", expectedResponse: `{"name":"test-release","info":{"first_deployed":"","last_deployed":"","deleted":"","status":"deployed"},"version":1}`, body: `{"name": "test", "namespace":"test", "version":1}`, - release: &release.Release{ + release: &releaseV1.Release{ Name: "test-release", - Info: &release.Info{ - Status: release.StatusDeployed, + Info: &releaseV1.Info{ + Status: rcommon.StatusDeployed, }, Version: 1, }, @@ -648,7 +650,7 @@ func TestHelmHandlers_HandleHelmUpgradeRelease(t *testing.T) { name string expectedResponse string expectedContentType string - release *release.Release + release *releaseV1.Release error httpStatusCode int requestBody string diff --git a/pkg/helm/handlers/handlers.go b/pkg/helm/handlers/handlers.go index 1fe109b735e..eed9784cffd 100644 --- a/pkg/helm/handlers/handlers.go +++ b/pkg/helm/handlers/handlers.go @@ -6,9 +6,10 @@ import ( "net/http" "strconv" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v4/pkg/action" + chart "helm.sh/helm/v4/pkg/chart/v2" + releasecommon "helm.sh/helm/v4/pkg/release" + releaseV1 "helm.sh/helm/v4/pkg/release/v1" kv1 "k8s.io/api/core/v1" "k8s.io/client-go/dynamic" "k8s.io/client-go/rest" @@ -63,18 +64,18 @@ type helmHandlers struct { // helm actions renderManifests func(string, string, map[string]interface{}, *action.Configuration, dynamic.Interface, corev1client.CoreV1Interface, string, string, bool) (string, error) installChartAsync func(string, string, string, map[string]interface{}, *action.Configuration, dynamic.Interface, corev1client.CoreV1Interface, bool, string) (*kv1.Secret, error) - installChart func(string, string, string, map[string]interface{}, *action.Configuration, dynamic.Interface, corev1client.CoreV1Interface, bool, string) (*release.Release, error) + installChart func(string, string, string, map[string]interface{}, *action.Configuration, dynamic.Interface, corev1client.CoreV1Interface, bool, string) (*releaseV1.Release, error) installChartFromURL func(string, string, string, map[string]interface{}, *action.Configuration, corev1client.CoreV1Interface, string, string) (*kv1.Secret, error) - listReleases func(*action.Configuration, bool) ([]*release.Release, error) + listReleases func(*action.Configuration, bool) ([]*releaseV1.Release, error) upgradeReleaseAsync func(string, string, string, map[string]interface{}, *action.Configuration, dynamic.Interface, corev1client.CoreV1Interface, bool, string) (*kv1.Secret, error) - upgradeRelease func(string, string, string, map[string]interface{}, *action.Configuration, dynamic.Interface, corev1client.CoreV1Interface, bool, string) (*release.Release, error) - uninstallRelease func(string, *action.Configuration) (*release.UninstallReleaseResponse, error) + upgradeRelease func(string, string, string, map[string]interface{}, *action.Configuration, dynamic.Interface, corev1client.CoreV1Interface, bool, string) (*releaseV1.Release, error) + uninstallRelease func(string, *action.Configuration) (*releasecommon.UninstallReleaseResponse, error) uninstallReleaseAsync func(string, string, string, *action.Configuration, corev1client.CoreV1Interface) error - rollbackRelease func(string, int, *action.Configuration) (*release.Release, error) - getRelease func(string, *action.Configuration) (*release.Release, error) + rollbackRelease func(string, int, *action.Configuration) (*releaseV1.Release, error) + getRelease func(string, *action.Configuration) (*releaseV1.Release, error) getChart func(chartUrl string, conf *action.Configuration, namespace string, client dynamic.Interface, coreClient corev1client.CoreV1Interface, filesCleanup bool, indexEntry string) (*chart.Chart, error) getChartFromURL func(url string, conf *action.Configuration, namespace string, client dynamic.Interface, coreClient corev1client.CoreV1Interface, filesCleanup bool, basicAuthSecretName string) (*chart.Chart, error) - getReleaseHistory func(releaseName string, conf *action.Configuration) ([]*release.Release, error) + getReleaseHistory func(releaseName string, conf *action.Configuration) ([]*releaseV1.Release, error) newProxy func(bearerToken string) (chartproxy.Proxy, error) } diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/clearsign/clearsign.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/clearsign/clearsign.go new file mode 100644 index 00000000000..4c80db75693 --- /dev/null +++ b/vendor/github.com/ProtonMail/go-crypto/openpgp/clearsign/clearsign.go @@ -0,0 +1,561 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package clearsign generates and processes OpenPGP, clear-signed data. See +// RFC 4880, section 7. +// +// Clearsigned messages are cryptographically signed, but the contents of the +// message are kept in plaintext so that it can be read without special tools. +package clearsign // import "github.com/ProtonMail/go-crypto/openpgp/clearsign" + +import ( + "bufio" + "bytes" + "crypto" + "fmt" + "hash" + "io" + "net/textproto" + "strconv" + "strings" + + "github.com/ProtonMail/go-crypto/openpgp" + "github.com/ProtonMail/go-crypto/openpgp/armor" + "github.com/ProtonMail/go-crypto/openpgp/errors" + "github.com/ProtonMail/go-crypto/openpgp/packet" +) + +// A Block represents a clearsigned message. A signature on a Block can +// be checked by calling Block.VerifySignature. +type Block struct { + Headers textproto.MIMEHeader // Optional unverified Hash headers + Plaintext []byte // The original message text + Bytes []byte // The signed message + ArmoredSignature *armor.Block // The signature block +} + +// start is the marker which denotes the beginning of a clearsigned message. +var start = []byte("\n-----BEGIN PGP SIGNED MESSAGE-----") + +// dashEscape is prefixed to any lines that begin with a hyphen so that they +// can't be confused with endText. +var dashEscape = []byte("- ") + +// endText is a marker which denotes the end of the message and the start of +// an armored signature. +var endText = []byte("-----BEGIN PGP SIGNATURE-----") + +// end is a marker which denotes the end of the armored signature. +var end = []byte("\n-----END PGP SIGNATURE-----") + +var crlf = []byte("\r\n") +var lf = byte('\n') + +const hashHeader string = "Hash" + +// getLine returns the first \r\n or \n delineated line from the given byte +// array. The line does not include the \r\n or \n. The remainder of the byte +// array (also not including the new line bytes) is also returned and this will +// always be smaller than the original argument. +func getLine(data []byte) (line, rest []byte) { + i := bytes.Index(data, []byte{'\n'}) + var j int + if i < 0 { + i = len(data) + j = i + } else { + j = i + 1 + if i > 0 && data[i-1] == '\r' { + i-- + } + } + return data[0:i], data[j:] +} + +// Decode finds the first clearsigned message in data and returns it, as well as +// the suffix of data which remains after the message. Any prefix data is +// discarded. +// +// If no message is found, or if the message is invalid, Decode returns nil and +// the whole data slice. The only allowed header type is Hash, and it is not +// verified against the signature hash. +func Decode(data []byte) (b *Block, rest []byte) { + // start begins with a newline. However, at the very beginning of + // the byte array, we'll accept the start string without it. + rest = data + if bytes.HasPrefix(data, start[1:]) { + rest = rest[len(start)-1:] + } else if i := bytes.Index(data, start); i >= 0 { + rest = rest[i+len(start):] + } else { + return nil, data + } + + // Consume the start line and check it does not have a suffix. + suffix, rest := getLine(rest) + if len(suffix) != 0 { + return nil, data + } + + var line []byte + b = &Block{ + Headers: make(textproto.MIMEHeader), + } + + // Next come a series of header lines. + for { + // This loop terminates because getLine's second result is + // always smaller than its argument. + if len(rest) == 0 { + return nil, data + } + // An empty line marks the end of the headers. + if line, rest = getLine(rest); len(strings.TrimSpace(string(line))) == 0 { + break + } + + // Reject headers with control or Unicode characters. + if i := bytes.IndexFunc(line, func(r rune) bool { + return r < 0x20 || r > 0x7e + }); i != -1 { + return nil, data + } + + i := bytes.Index(line, []byte{':'}) + if i == -1 { + return nil, data + } + + key, val := string(line[0:i]), string(line[i+1:]) + key = strings.TrimSpace(key) + if key == hashHeader { + for _, val := range strings.Split(val, ",") { + val = strings.TrimSpace(val) + b.Headers.Add(key, val) + } + } else { + // Only "Hash" headers are allowed. + return nil, data + } + } + + firstLine := true + for { + start := rest + + line, rest = getLine(rest) + if len(line) == 0 && len(rest) == 0 { + // No armored data was found, so this isn't a complete message. + return nil, data + } + if bytes.Equal(line, endText) { + // Back up to the start of the line because armor expects to see the + // header line. + rest = start + break + } + + // The final CRLF isn't included in the hash so we don't write it until + // we've seen the next line. + if firstLine { + firstLine = false + } else { + b.Bytes = append(b.Bytes, crlf...) + } + + if bytes.HasPrefix(line, dashEscape) { + line = line[2:] + } + line = bytes.TrimRight(line, " \t") + b.Bytes = append(b.Bytes, line...) + + b.Plaintext = append(b.Plaintext, line...) + b.Plaintext = append(b.Plaintext, lf) + } + b.Plaintext = b.Plaintext[:len(b.Plaintext)-1] + + // We want to find the extent of the armored data (including any newlines at + // the end). + i := bytes.Index(rest, end) + if i == -1 { + return nil, data + } + i += len(end) + for i < len(rest) && (rest[i] == '\r' || rest[i] == '\n') { + i++ + } + armored := rest[:i] + rest = rest[i:] + + var err error + b.ArmoredSignature, err = armor.Decode(bytes.NewBuffer(armored)) + if err != nil { + return nil, data + } + + return b, rest +} + +// A dashEscaper is an io.WriteCloser which processes the body of a clear-signed +// message. The clear-signed message is written to buffered and a hash, suitable +// for signing, is maintained in h. +// +// When closed, an armored signature is created and written to complete the +// message. +type dashEscaper struct { + buffered *bufio.Writer + hashers []hash.Hash // one per key in privateKeys + hashTypes []crypto.Hash + toHash io.Writer // writes to all the hashes in hashers + salts [][]byte // salts for the signatures if v6 + armorHeader map[string]string // Armor headers + + atBeginningOfLine bool + isFirstLine bool + + whitespace []byte + byteBuf []byte // a one byte buffer to save allocations + + privateKeys []*packet.PrivateKey + config *packet.Config +} + +func (d *dashEscaper) Write(data []byte) (n int, err error) { + for _, b := range data { + d.byteBuf[0] = b + + if d.atBeginningOfLine { + // The final CRLF isn't included in the hash so we have to wait + // until this point (the start of the next line) before writing it. + if !d.isFirstLine { + if _, err = d.toHash.Write(crlf); err != nil { + return + } + } + d.isFirstLine = false + } + + // Any whitespace at the end of the line has to be removed so we + // buffer it until we find out whether there's more on this line. + if b == ' ' || b == '\t' || b == '\r' { + d.whitespace = append(d.whitespace, b) + d.atBeginningOfLine = false + continue + } + + if d.atBeginningOfLine { + // At the beginning of a line, hyphens have to be escaped. + if b == '-' { + // The signature isn't calculated over the dash-escaped text so + // the escape is only written to buffered. + if _, err = d.buffered.Write(dashEscape); err != nil { + return + } + if _, err = d.toHash.Write(d.byteBuf); err != nil { + return + } + d.atBeginningOfLine = false + } else if b == '\n' { + // Nothing to do because we delay writing CRLF to the hash. + } else { + if _, err = d.toHash.Write(d.byteBuf); err != nil { + return + } + d.atBeginningOfLine = false + } + if err = d.buffered.WriteByte(b); err != nil { + return + } + } else { + if b == '\n' { + // We got a raw \n. Drop any trailing whitespace and write a + // CRLF. + d.whitespace = d.whitespace[:0] + // We delay writing CRLF to the hash until the start of the + // next line. + if err = d.buffered.WriteByte(b); err != nil { + return + } + d.atBeginningOfLine = true + } else { + // Any buffered whitespace wasn't at the end of the line so + // we need to write it out. + if len(d.whitespace) > 0 { + if _, err = d.toHash.Write(d.whitespace); err != nil { + return + } + if _, err = d.buffered.Write(d.whitespace); err != nil { + return + } + d.whitespace = d.whitespace[:0] + } + if _, err = d.toHash.Write(d.byteBuf); err != nil { + return + } + if err = d.buffered.WriteByte(b); err != nil { + return + } + } + } + } + + n = len(data) + return +} + +func (d *dashEscaper) Close() (err error) { + if d.atBeginningOfLine { + if !d.isFirstLine { + if _, err := d.toHash.Write(crlf); err != nil { + return err + } + } + } + if err = d.buffered.WriteByte(lf); err != nil { + return + } + + out, err := armor.EncodeWithChecksumOption(d.buffered, "PGP SIGNATURE", d.armorHeader, false) + if err != nil { + return + } + + t := d.config.Now() + indexSalt := 0 + for i, k := range d.privateKeys { + sig := new(packet.Signature) + sig.Version = k.Version + sig.SigType = packet.SigTypeText + sig.PubKeyAlgo = k.PubKeyAlgo + sig.Hash = d.hashTypes[i] + sig.CreationTime = t + sig.IssuerKeyId = &k.KeyId + sig.IssuerFingerprint = k.Fingerprint + sig.Notations = d.config.Notations() + sigLifetimeSecs := d.config.SigLifetime() + sig.SigLifetimeSecs = &sigLifetimeSecs + if k.Version == 6 { + if err = sig.SetSalt(d.salts[indexSalt]); err != nil { + return + } + indexSalt++ + } + if err = sig.Sign(d.hashers[i], k, d.config); err != nil { + return + } + if err = sig.Serialize(out); err != nil { + return + } + } + + if err = out.Close(); err != nil { + return + } + if err = d.buffered.Flush(); err != nil { + return + } + return +} + +// Encode returns a WriteCloser which will clear-sign a message with privateKey +// and write it to w. If config is nil, sensible defaults are used. +func Encode(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) { + return EncodeMulti(w, []*packet.PrivateKey{privateKey}, config) +} + +// EncodeWithHeader returns a WriteCloser which will clear-sign a message with privateKey +// and write it to w. If config is nil, sensible defaults are used. +// Additionally provides a headers argument for custom headers. +func EncodeWithHeader(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config, headers map[string]string) (plaintext io.WriteCloser, err error) { + return EncodeMultiWithHeader(w, []*packet.PrivateKey{privateKey}, config, headers) +} + +// EncodeMulti returns a WriteCloser which will clear-sign a message with all the +// private keys indicated and write it to w. If config is nil, sensible defaults +// are used. +func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) { + return EncodeMultiWithHeader(w, privateKeys, config, nil) +} + +// EncodeMultiWithHeader returns a WriteCloser which will clear-sign a message with all the +// private keys indicated and write it to w. If config is nil, sensible defaults +// are used. +// Additionally provides a headers argument for custom headers. +func EncodeMultiWithHeader(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.Config, headers map[string]string) (plaintext io.WriteCloser, err error) { + for _, k := range privateKeys { + if k.Encrypted { + return nil, errors.InvalidArgumentError(fmt.Sprintf("signing key %s is encrypted", k.KeyIdString())) + } + } + + hashType := config.Hash() + + var hashers []hash.Hash + var hashTypes []crypto.Hash + var ws []io.Writer + var salts [][]byte + for _, sk := range privateKeys { + acceptedHashes := acceptableHashesToWrite(&sk.PublicKey) + // acceptedHashes contains at least one hash + selectedHashType := acceptedHashes[0] + for _, acceptedHash := range acceptedHashes { + if hashType == acceptedHash { + selectedHashType = hashType + break + } + } + h := selectedHashType.New() + if sk.Version == 6 { + // generate salt + var salt []byte + salt, err = packet.SignatureSaltForHash(hashType, config.Random()) + if err != nil { + return + } + if _, err = h.Write(salt); err != nil { + return + } + salts = append(salts, salt) + } + hashers = append(hashers, h) + hashTypes = append(hashTypes, selectedHashType) + ws = append(ws, h) + } + toHash := io.MultiWriter(ws...) + + buffered := bufio.NewWriter(w) + // start has a \n at the beginning that we don't want here. + if _, err = buffered.Write(start[1:]); err != nil { + return + } + if err = buffered.WriteByte(lf); err != nil { + return + } + // write headers + nonV6 := len(salts) < len(hashers) + // Crypto refresh: Headers SHOULD NOT be emitted + if nonV6 { // Emit header if non v6 signatures are present for compatibility + if err := writeHashHeader(buffered, hashTypes); err != nil { + return nil, err + } + } + if err = buffered.WriteByte(lf); err != nil { + return + } + + plaintext = &dashEscaper{ + buffered: buffered, + hashers: hashers, + hashTypes: hashTypes, + toHash: toHash, + salts: salts, + armorHeader: headers, + + atBeginningOfLine: true, + isFirstLine: true, + + byteBuf: make([]byte, 1), + + privateKeys: privateKeys, + config: config, + } + + return +} + +// VerifySignature checks a clearsigned message signature, and checks that the +// hash algorithm in the header matches the hash algorithm in the signature. +func (b *Block) VerifySignature(keyring openpgp.KeyRing, config *packet.Config) (signer *openpgp.Entity, err error) { + _, signer, err = openpgp.VerifyDetachedSignature(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body, config) + return +} + +// writeHashHeader writes the legacy cleartext hash header to buffered. +func writeHashHeader(buffered *bufio.Writer, hashTypes []crypto.Hash) error { + seen := make(map[string]bool, len(hashTypes)) + if _, err := buffered.WriteString(fmt.Sprintf("%s: ", hashHeader)); err != nil { + return err + } + + for index, sigHashType := range hashTypes { + first := index == 0 + name := nameOfHash(sigHashType) + if len(name) == 0 { + return errors.UnsupportedError("unknown hash type: " + strconv.Itoa(int(sigHashType))) + } + + switch { + case !seen[name] && first: + if _, err := buffered.WriteString(name); err != nil { + return err + } + case !seen[name]: + if _, err := buffered.WriteString(fmt.Sprintf(",%s", name)); err != nil { + return err + } + } + seen[name] = true + } + + if err := buffered.WriteByte(lf); err != nil { + return err + } + + return nil +} + +// nameOfHash returns the OpenPGP name for the given hash, or the empty string +// if the name isn't known. See RFC 4880, section 9.4. +func nameOfHash(h crypto.Hash) string { + switch h { + case crypto.SHA224: + return "SHA224" + case crypto.SHA256: + return "SHA256" + case crypto.SHA384: + return "SHA384" + case crypto.SHA512: + return "SHA512" + case crypto.SHA3_256: + return "SHA3-256" + case crypto.SHA3_512: + return "SHA3-512" + } + return "" +} + +func acceptableHashesToWrite(singingKey *packet.PublicKey) []crypto.Hash { + switch singingKey.PubKeyAlgo { + case packet.PubKeyAlgoEd448: + return []crypto.Hash{ + crypto.SHA512, + crypto.SHA3_512, + } + case packet.PubKeyAlgoECDSA, packet.PubKeyAlgoEdDSA: + if curve, err := singingKey.Curve(); err == nil { + if curve == packet.Curve448 || + curve == packet.CurveNistP521 || + curve == packet.CurveBrainpoolP512 { + return []crypto.Hash{ + crypto.SHA512, + crypto.SHA3_512, + } + } else if curve == packet.CurveBrainpoolP384 || + curve == packet.CurveNistP384 { + return []crypto.Hash{ + crypto.SHA384, + crypto.SHA512, + crypto.SHA3_512, + } + } + } + } + return []crypto.Hash{ + crypto.SHA256, + crypto.SHA384, + crypto.SHA512, + crypto.SHA3_256, + crypto.SHA3_512, + } +} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/errors/errors.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/errors/errors.go index e44b45734d4..2e341507a73 100644 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/errors/errors.go +++ b/vendor/github.com/ProtonMail/go-crypto/openpgp/errors/errors.go @@ -180,6 +180,16 @@ func (dke ErrMalformedMessage) Error() string { return "openpgp: malformed message " + string(dke) } +type messageTooLargeError int + +func (e messageTooLargeError) Error() string { + return "openpgp: decompressed message size exceeds provided limit" +} + +// ErrMessageTooLarge is returned if the read data from +// a compressed packet exceeds the provided limit. +var ErrMessageTooLarge error = messageTooLargeError(0) + // ErrEncryptionKeySelection is returned if encryption key selection fails (v2 API). type ErrEncryptionKeySelection struct { PrimaryKeyId string diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/aead_config.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/aead_config.go index fec41a0e73f..ef100d372b7 100644 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/aead_config.go +++ b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/aead_config.go @@ -37,7 +37,7 @@ func (conf *AEADConfig) Mode() AEADMode { // ChunkSizeByte returns the byte indicating the chunk size. The effective // chunk size is computed with the formula uint64(1) << (chunkSizeByte + 6) -// limit to 16 = 4 MiB +// limit chunkSizeByte to 16 which equals to 2^22 = 4 MiB // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2 func (conf *AEADConfig) ChunkSizeByte() byte { if conf == nil || conf.ChunkSize == 0 { @@ -49,8 +49,8 @@ func (conf *AEADConfig) ChunkSizeByte() byte { switch { case exponent < 6: exponent = 6 - case exponent > 16: - exponent = 16 + case exponent > 22: + exponent = 22 } return byte(exponent - 6) diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/compressed.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/compressed.go index 0bcb38cacac..931f55a4e80 100644 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/compressed.go +++ b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/compressed.go @@ -98,6 +98,16 @@ func (c *Compressed) parse(r io.Reader) error { return err } +// LimitedBodyReader wraps the provided body reader with a limiter that restricts +// the number of bytes read to the specified limit. +// If limit is nil, the reader is unbounded. +func (c *Compressed) LimitedBodyReader(limit *int64) io.Reader { + if limit == nil { + return c.Body + } + return &LimitReader{R: c.Body, N: *limit} +} + // compressedWriterCloser represents the serialized compression stream // header and the compressor. Its Close() method ensures that both the // compressor and serialized stream header are closed. Its Write() @@ -159,3 +169,24 @@ func SerializeCompressed(w io.WriteCloser, algo CompressionAlgo, cc *Compression return } + +// LimitReader is an io.Reader that fails with MessageToLarge if read bytes exceed N. +type LimitReader struct { + R io.Reader // underlying reader + N int64 // max bytes allowed +} + +func (l *LimitReader) Read(p []byte) (int, error) { + if l.N <= 0 { + return 0, errors.ErrMessageTooLarge + } + + n, err := l.R.Read(p) + l.N -= int64(n) + + if err == nil && l.N <= 0 { + err = errors.ErrMessageTooLarge + } + + return n, err +} diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/config.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/config.go index 257398d9dde..30167ed9deb 100644 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/config.go +++ b/vendor/github.com/ProtonMail/go-crypto/openpgp/packet/config.go @@ -178,6 +178,11 @@ type Config struct { // When set to true, a key without flags is treated as if all flags are enabled. // This behavior is consistent with GPG. InsecureAllowAllKeyFlagsWhenMissing bool + + // MaxDecompressedMessageSize specifies the maximum number of bytes that can be + // read from a compressed packet. This serves as an upper limit to prevent + // excessively large decompressed messages. + MaxDecompressedMessageSize *int64 } func (c *Config) Random() io.Reader { @@ -415,6 +420,13 @@ func (c *Config) AllowAllKeyFlagsWhenMissing() bool { return c.InsecureAllowAllKeyFlagsWhenMissing } +func (c *Config) DecompressedMessageSizeLimit() *int64 { + if c == nil { + return nil + } + return c.MaxDecompressedMessageSize +} + // BoolPointer is a helper function to set a boolean pointer in the Config. // e.g., config.CheckPacketSequence = BoolPointer(true) func BoolPointer(value bool) *bool { diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/read.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/read.go index e6dd9b5fd30..5578797edf0 100644 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/read.go +++ b/vendor/github.com/ProtonMail/go-crypto/openpgp/read.go @@ -259,7 +259,7 @@ FindLiteralData: } switch p := p.(type) { case *packet.Compressed: - if err := packets.Push(p.Body); err != nil { + if err := packets.Push(p.LimitedBodyReader(config.DecompressedMessageSizeLimit())); err != nil { return nil, err } case *packet.OnePassSignature: diff --git a/vendor/github.com/ProtonMail/go-crypto/openpgp/write.go b/vendor/github.com/ProtonMail/go-crypto/openpgp/write.go index b0f6ef7b092..84bc27d83e1 100644 --- a/vendor/github.com/ProtonMail/go-crypto/openpgp/write.go +++ b/vendor/github.com/ProtonMail/go-crypto/openpgp/write.go @@ -253,34 +253,12 @@ func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entit } var hash crypto.Hash - for _, hashId := range candidateHashes { - if h, ok := algorithm.HashIdToHash(hashId); ok && h.Available() { - hash = h - break - } - } - - // If the hash specified by config is a candidate, we'll use that. - if configuredHash := config.Hash(); configuredHash.Available() { - for _, hashId := range candidateHashes { - if h, ok := algorithm.HashIdToHash(hashId); ok && h == configuredHash { - hash = h - break - } - } - } - - if hash == 0 { - hashId := candidateHashes[0] - name, ok := algorithm.HashIdToString(hashId) - if !ok { - name = "#" + strconv.Itoa(int(hashId)) - } - return nil, errors.InvalidArgumentError("cannot encrypt because no candidate hash functions are compiled in. (Wanted " + name + " in this case.)") - } - var salt []byte if signer != nil { + if hash, err = selectHash(candidateHashes, config.Hash(), signer); err != nil { + return nil, err + } + var opsVersion = 3 if signer.Version == 6 { opsVersion = signer.Version @@ -558,13 +536,34 @@ func (s signatureWriter) Close() error { return s.encryptedData.Close() } +func selectHashForSigningKey(config *packet.Config, signer *packet.PublicKey) crypto.Hash { + acceptableHashes := acceptableHashesToWrite(signer) + hash, ok := algorithm.HashToHashId(config.Hash()) + if !ok { + return config.Hash() + } + for _, acceptableHashes := range acceptableHashes { + if acceptableHashes == hash { + return config.Hash() + } + } + if len(acceptableHashes) > 0 { + defaultAcceptedHash, ok := algorithm.HashIdToHash(acceptableHashes[0]) + if ok { + return defaultAcceptedHash + } + } + return config.Hash() +} + func createSignaturePacket(signer *packet.PublicKey, sigType packet.SignatureType, config *packet.Config) *packet.Signature { sigLifetimeSecs := config.SigLifetime() + hash := selectHashForSigningKey(config, signer) return &packet.Signature{ Version: signer.Version, SigType: sigType, PubKeyAlgo: signer.PubKeyAlgo, - Hash: config.Hash(), + Hash: hash, CreationTime: config.Now(), IssuerKeyId: &signer.KeyId, IssuerFingerprint: signer.Fingerprint, @@ -618,3 +617,74 @@ func handleCompression(compressed io.WriteCloser, candidateCompression []uint8, } return data, nil } + +// selectHash selects the preferred hash given the candidateHashes and the configuredHash +func selectHash(candidateHashes []byte, configuredHash crypto.Hash, signer *packet.PrivateKey) (hash crypto.Hash, err error) { + acceptableHashes := acceptableHashesToWrite(&signer.PublicKey) + candidateHashes = intersectPreferences(acceptableHashes, candidateHashes) + + for _, hashId := range candidateHashes { + if h, ok := algorithm.HashIdToHash(hashId); ok && h.Available() { + hash = h + break + } + } + + // If the hash specified by config is a candidate, we'll use that. + if configuredHash.Available() { + for _, hashId := range candidateHashes { + if h, ok := algorithm.HashIdToHash(hashId); ok && h == configuredHash { + hash = h + break + } + } + } + + if hash == 0 { + if len(acceptableHashes) > 0 { + if h, ok := algorithm.HashIdToHash(acceptableHashes[0]); ok { + hash = h + } else { + return 0, errors.UnsupportedError("no candidate hash functions are compiled in.") + } + } else { + return 0, errors.UnsupportedError("no candidate hash functions are compiled in.") + } + } + return +} + +func acceptableHashesToWrite(singingKey *packet.PublicKey) []uint8 { + switch singingKey.PubKeyAlgo { + case packet.PubKeyAlgoEd448: + return []uint8{ + hashToHashId(crypto.SHA512), + hashToHashId(crypto.SHA3_512), + } + case packet.PubKeyAlgoECDSA, packet.PubKeyAlgoEdDSA: + if curve, err := singingKey.Curve(); err == nil { + if curve == packet.Curve448 || + curve == packet.CurveNistP521 || + curve == packet.CurveBrainpoolP512 { + return []uint8{ + hashToHashId(crypto.SHA512), + hashToHashId(crypto.SHA3_512), + } + } else if curve == packet.CurveBrainpoolP384 || + curve == packet.CurveNistP384 { + return []uint8{ + hashToHashId(crypto.SHA384), + hashToHashId(crypto.SHA512), + hashToHashId(crypto.SHA3_512), + } + } + } + } + return []uint8{ + hashToHashId(crypto.SHA256), + hashToHashId(crypto.SHA384), + hashToHashId(crypto.SHA512), + hashToHashId(crypto.SHA3_256), + hashToHashId(crypto.SHA3_512), + } +} diff --git a/vendor/github.com/cloudflare/circl/internal/sha3/xor_unaligned.go b/vendor/github.com/cloudflare/circl/internal/sha3/xor_unaligned.go index 052fc8d32d2..0910613465a 100644 --- a/vendor/github.com/cloudflare/circl/internal/sha3/xor_unaligned.go +++ b/vendor/github.com/cloudflare/circl/internal/sha3/xor_unaligned.go @@ -14,14 +14,14 @@ import "unsafe" type storageBuf [maxRate / 8]uint64 func (b *storageBuf) asBytes() *[maxRate]byte { - return (*[maxRate]byte)(unsafe.Pointer(b)) + return (*[maxRate]byte)(unsafe.Pointer(b)) //nolint:gosec } // xorInuses unaligned reads and writes to update d.a to contain d.a // XOR buf. func xorIn(d *State, buf []byte) { n := len(buf) - bw := (*[maxRate / 8]uint64)(unsafe.Pointer(&buf[0]))[: n/8 : n/8] + bw := (*[maxRate / 8]uint64)(unsafe.Pointer(&buf[0]))[: n/8 : n/8] //nolint:gosec if n >= 72 { d.a[0] ^= bw[0] d.a[1] ^= bw[1] @@ -56,6 +56,6 @@ func xorIn(d *State, buf []byte) { } func copyOut(d *State, buf []byte) { - ab := (*[maxRate]uint8)(unsafe.Pointer(&d.a[0])) + ab := (*[maxRate]uint8)(unsafe.Pointer(&d.a[0])) //nolint:gosec copy(buf, ab[:]) } diff --git a/vendor/github.com/cloudflare/circl/sign/sign.go b/vendor/github.com/cloudflare/circl/sign/sign.go index 557d6f09605..1247f1b626e 100644 --- a/vendor/github.com/cloudflare/circl/sign/sign.go +++ b/vendor/github.com/cloudflare/circl/sign/sign.go @@ -38,6 +38,12 @@ type PrivateKey interface { encoding.BinaryMarshaler } +// A private key that retains the seed with which it was generated. +type Seeded interface { + // returns the seed if retained, otherwise nil + Seed() []byte +} + // A Scheme represents a specific instance of a signature scheme. type Scheme interface { // Name of the scheme. diff --git a/vendor/github.com/dylibso/observe-sdk/go/.gitignore b/vendor/github.com/dylibso/observe-sdk/go/.gitignore new file mode 100644 index 00000000000..796603f3490 --- /dev/null +++ b/vendor/github.com/dylibso/observe-sdk/go/.gitignore @@ -0,0 +1,2 @@ +target +.env \ No newline at end of file diff --git a/vendor/github.com/dylibso/observe-sdk/go/LICENSE b/vendor/github.com/dylibso/observe-sdk/go/LICENSE new file mode 100644 index 00000000000..390f385eff9 --- /dev/null +++ b/vendor/github.com/dylibso/observe-sdk/go/LICENSE @@ -0,0 +1,200 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023-present Dylibso, Inc. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/dylibso/observe-sdk/go/adapter.go b/vendor/github.com/dylibso/observe-sdk/go/adapter.go new file mode 100644 index 00000000000..06faac7aec4 --- /dev/null +++ b/vendor/github.com/dylibso/observe-sdk/go/adapter.go @@ -0,0 +1,146 @@ +package observe + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "github.com/tetratelabs/wazero" + trace "go.opentelemetry.io/proto/otlp/trace/v1" +) + +// The primary interface that every Adapter needs to follow +// Start() and Stop() can just call the implementations on AdapterBase +// or provide some custom logic. HandleTraceEvent is called after +// an invocation of a wasm module is done and all events are collected. +type Adapter interface { + Start(context.Context) + Stop(wait bool) + HandleTraceEvent(e TraceEvent) +} + +// The payload that contains all the Events +// from a single wasm module invocation +type TraceEvent struct { + Events []Event + TelemetryId TelemetryId + AdapterMeta interface{} +} + +// Shared implementation for all Adapters +type AdapterBase struct { + TraceEvents chan TraceEvent + + stop chan bool + eventBucket *EventBucket + flusher Flusher +} + +func (a *AdapterBase) NewTraceCtx(ctx context.Context, r wazero.Runtime, wasm []byte, opts *Options) (*TraceCtx, error) { + if opts == nil { + opts = NewDefaultOptions() + } + return newTraceCtx(ctx, a.TraceEvents, r, wasm, opts) +} + +func NewAdapterBase(batchSize int, flushPeriod time.Duration) AdapterBase { + bucket := NewEventBucket(batchSize, flushPeriod) + return AdapterBase{ + TraceEvents: make(chan TraceEvent, 100), + eventBucket: bucket, + } +} + +func (b *AdapterBase) SetFlusher(f Flusher) { + b.flusher = f +} + +func (b *AdapterBase) HandleTraceEvent(te TraceEvent) { + b.eventBucket.addEvent(te, b.flusher) +} + +func (b *AdapterBase) Start(ctx context.Context, a Adapter) { + b.stop = make(chan bool) + + go func() { + for { + select { + case <-ctx.Done(): + log.Println("context cancelled") + return + case event := <-b.TraceEvents: + a.HandleTraceEvent(event) + case <-b.stop: + return + } + } + }() +} + +// Stops the adapter and waits for all flushes to complete. +// Set wait parameter to false if you don't want to wait +func (b *AdapterBase) Stop(wait bool) { + b.stop <- true + if wait { + b.eventBucket.Wait() + } +} + +// MakeOtelCallSpans recursively constructs call spans in open telemetry format +func (b *AdapterBase) MakeOtelCallSpans(event CallEvent, parentId []byte, traceId string) []*trace.Span { + name := event.FunctionName() + span := NewOtelSpan(traceId, parentId, name, event.Time, event.Time.Add(event.Duration)) + span.Attributes = append(span.Attributes, NewOtelKeyValueString("function-name", fmt.Sprintf("function-call-%s", name))) + + spans := []*trace.Span{span} + for _, ev := range event.Within() { + if call, ok := ev.(CallEvent); ok { + spans = append(spans, b.MakeOtelCallSpans(call, span.SpanId, traceId)...) + } + if alloc, ok := ev.(MemoryGrowEvent); ok { + kv := NewOtelKeyValueInt64("allocation", int64(alloc.MemoryGrowAmount())) + i, existing := GetOtelAttrFromSpan("allocation", span) + if existing != nil { + span.Attributes[i] = AddOtelKeyValueInt64(kv, existing) + } else { + span.Attributes = append(span.Attributes, kv) + } + } + if tags, ok := ev.(SpanTagsEvent); ok { + for _, tag := range tags.Tags { + parts := strings.Split(tag, ":") + if len(parts) != 2 { + log.Printf("Invalid tag: %s\n", tag) + continue + } + + kv := NewOtelKeyValueString(parts[0], parts[1]) + span.Attributes = append(span.Attributes, kv) + } + } + } + return spans +} + +// Definition of how to filter our Spans to reduce noise +type SpanFilter struct { + MinDuration time.Duration +} + +// Specify options to change what or how the adapter receives ObserveEvents +type Options struct { + SpanFilter *SpanFilter + ChannelBufferSize int +} + +// Create a default configuration +func NewDefaultOptions() *Options { + return &Options{ + ChannelBufferSize: 1024, + SpanFilter: &SpanFilter{ + MinDuration: time.Microsecond * 20, + }, + } +} diff --git a/vendor/github.com/dylibso/observe-sdk/go/bucket.go b/vendor/github.com/dylibso/observe-sdk/go/bucket.go new file mode 100644 index 00000000000..418c50ed352 --- /dev/null +++ b/vendor/github.com/dylibso/observe-sdk/go/bucket.go @@ -0,0 +1,86 @@ +package observe + +import ( + "log" + "sync" + "time" +) + +type Flusher interface { + Flush(events []TraceEvent) error +} + +// EventBucket is a bucket for outgoing TraceEvents. +// It only schedules flushes when the bucket goes from empty to 1 item. +// At most the latency to flush the bucket will be flushPeriod. +// It will also flush the TraceEvents in batches according to batch size +type EventBucket struct { + mu sync.Mutex + wg sync.WaitGroup + bucket []TraceEvent + flushPeriod time.Duration + batchSize int +} + +// NewEventBucket creates an EventBucket +func NewEventBucket(batchSize int, flushPeriod time.Duration) *EventBucket { + return &EventBucket{ + flushPeriod: flushPeriod, + batchSize: batchSize, + } +} + +// addEvent adds a TraceEvent and schedules to flush to Flusher if needed +func (b *EventBucket) addEvent(e TraceEvent, f Flusher) { + b.mu.Lock() + wasEmpty := len(b.bucket) == 0 + b.bucket = append(b.bucket, e) + b.mu.Unlock() + // if this is the first event in the bucket, + // we schedule a flush + if wasEmpty { + b.scheduleFlush(f) + } +} + +// Wait will block until all pending flushes are done +func (b *EventBucket) Wait() { + b.wg.Wait() +} + +// scheduleFlush schedules a goroutine to flush +// the bucket at some time in the future depending on flushPeriod. +// Events will continue to build up until the flush comes due +func (b *EventBucket) scheduleFlush(f Flusher) { + // we start this routine and immediately wait, we are effectively + // scheduling the flush to run flushPeriod sections later. In the meantime, + // events may still be coming into the eventBucket + go func() { + // register this flush with the wait group + defer b.wg.Done() + b.wg.Add(1) + + // wait for flushPeriod + time.Sleep(b.flushPeriod) + + // move the events out of the EventBucket to a slice + // and add 1 to the waitgroup + b.mu.Lock() + bucket := b.bucket + b.bucket = nil + b.mu.Unlock() + + // flush the bucket in chunks of batchSize + for i := 0; i < len(bucket); i += b.batchSize { + j := i + b.batchSize + if j > len(bucket) { + j = len(bucket) + } + // TODO retry logic? + err := f.Flush(bucket[i:j]) + if err != nil { + log.Println(err) + } + } + }() +} diff --git a/vendor/github.com/dylibso/observe-sdk/go/event.go b/vendor/github.com/dylibso/observe-sdk/go/event.go new file mode 100644 index 00000000000..a755ac6a7f9 --- /dev/null +++ b/vendor/github.com/dylibso/observe-sdk/go/event.go @@ -0,0 +1,167 @@ +package observe + +import ( + "time" + + "github.com/ianlancetaylor/demangle" + "github.com/tetratelabs/wazero/experimental" +) + +type RawEventKind int + +const ( + RawEnter RawEventKind = iota + RawExit + RawMemoryGrow + RawMetric + RawSpanTags + RawLog + RawUnknownEvent +) + +type EventKind int + +const ( + Call EventKind = iota + MemoryGrow + Custom + Metric + SpanTags + Log +) + +type MetricFormat uint + +const ( + StatsdFormat MetricFormat = 1 +) + +// Represents the raw event in our Observe form. +// Events are transformed into vendor specific formats +// in the Adapters. +type RawEvent struct { + Kind RawEventKind + Stack []experimental.InternalFunction + FunctionIndex uint32 + FunctionName string + MemoryGrowAmount uint32 + Time time.Time + Duration time.Duration +} + +type Event interface { + RawEvents() []RawEvent +} + +type CallEvent struct { + Raw []RawEvent + Time time.Time + Duration time.Duration + within []Event +} + +func (e *CallEvent) Stop(at time.Time) { + e.Duration = at.Sub(e.Time) +} + +func (e CallEvent) RawEvents() []RawEvent { + return e.Raw +} + +func (e CallEvent) Within() []Event { + return e.within +} + +type CustomEvent struct { + Time time.Time + Name string + Metadata map[string]interface{} +} + +func NewCustomEvent(name string) CustomEvent { + return CustomEvent{ + Time: time.Now(), + Name: name, + Metadata: map[string]interface{}{}, + } +} + +func (e CustomEvent) RawEvents() []RawEvent { + return []RawEvent{} +} + +type MetricEvent struct { + Time time.Time + Format MetricFormat + Message string +} + +type SpanTagsEvent struct { + Raw RawEvent + Time time.Time + Tags []string +} + +type MemoryGrowEvent struct { + Raw RawEvent + Time time.Time +} + +type LogLevel uint + +const ( + Error LogLevel = 1 + Warn = 2 + Info = 3 + Debug = 4 +) + +type LogEvent struct { + Time time.Time + Message string + Level LogLevel +} + +func (e MemoryGrowEvent) RawEvents() []RawEvent { + return []RawEvent{e.Raw} +} + +func (e MemoryGrowEvent) FunctionName() string { + s, err := demangle.ToString(e.Raw.FunctionName) + if err != nil { + return e.Raw.FunctionName + } + return s +} + +func (e MemoryGrowEvent) FunctionIndex() uint32 { + return e.Raw.FunctionIndex +} + +func (e CallEvent) FunctionName() string { + s, err := demangle.ToString(e.Raw[0].FunctionName) + if err != nil { + return e.Raw[0].FunctionName + } + return s +} + +func (e CallEvent) FunctionIndex() uint32 { + return e.Raw[0].FunctionIndex +} + +func (e MemoryGrowEvent) MemoryGrowAmount() uint32 { + return e.Raw.MemoryGrowAmount +} + +func (e MetricEvent) RawEvents() []RawEvent { + return []RawEvent{} +} + +func (e SpanTagsEvent) RawEvents() []RawEvent { + return []RawEvent{e.Raw} +} + +func (e LogEvent) RawEvents() []RawEvent { + return []RawEvent{} +} diff --git a/vendor/github.com/dylibso/observe-sdk/go/listener.go b/vendor/github.com/dylibso/observe-sdk/go/listener.go new file mode 100644 index 00000000000..22c3b1dae1a --- /dev/null +++ b/vendor/github.com/dylibso/observe-sdk/go/listener.go @@ -0,0 +1,79 @@ +package observe + +import ( + "context" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental" +) + +// Implements the NewListener() method to satisfy the FunctionListener interface +func (t *TraceCtx) NewListener(def api.FunctionDefinition) experimental.FunctionListener { + if def.GoFunction() == nil { + return nil + } + return t +} + +// Implements the NewFunctionListener() method to satisfy the FunctionListener interface +func (t *TraceCtx) NewFunctionListener(_ api.FunctionDefinition) experimental.FunctionListener { + return t +} + +// Implements the Before() method to satisfy the FunctionListener interface. +// This takes events from the wazero runtime and sends them to the `raw` channel on the TraceCtx. +func (t *TraceCtx) Before(ctx context.Context, _ api.Module, def api.FunctionDefinition, inputs []uint64, stack experimental.StackIterator) { + var event RawEvent + name := def.Name() + + switch name { + case "enter": + fallthrough + case "instrument_enter": + event.Kind = RawEnter + event.FunctionIndex = uint32(inputs[0]) + event.FunctionName = t.names[event.FunctionIndex] + case "exit": + fallthrough + case "instrument_exit": + event.Kind = RawExit + event.FunctionIndex = uint32(inputs[0]) + event.FunctionName = t.names[event.FunctionIndex] + case "memory-grow": + fallthrough + case "instrument_memory_grow": + event.Kind = RawMemoryGrow + event.MemoryGrowAmount = uint32(inputs[0]) + + // manual events + case "span-enter": + fallthrough + case "span_enter": + event.Kind = RawEnter + case "span-exit": + fallthrough + case "span_exit": + event.Kind = RawExit + case "span-tags": + fallthrough + case "span_tags": + event.Kind = RawSpanTags + case "metric": + return + case "log": + return + default: + event.Kind = RawUnknownEvent + } + for stack.Next() { + f := stack.Function() + event.Stack = append(event.Stack, f) + } + t.raw <- event +} + +// Null implementation of the After() method to satisfy the FunctionListener interface. +func (t *TraceCtx) After(context.Context, api.Module, api.FunctionDefinition, []uint64) {} + +// Null implementation of the Abort() method to satisfy the FunctionListener interface. +func (t *TraceCtx) Abort(context.Context, api.Module, api.FunctionDefinition, error) {} diff --git a/vendor/github.com/dylibso/observe-sdk/go/otel_formatter.go b/vendor/github.com/dylibso/observe-sdk/go/otel_formatter.go new file mode 100644 index 00000000000..87e3391fe2f --- /dev/null +++ b/vendor/github.com/dylibso/observe-sdk/go/otel_formatter.go @@ -0,0 +1,132 @@ +package observe + +import ( + "encoding/binary" + "encoding/hex" + "time" + + common "go.opentelemetry.io/proto/otlp/common/v1" + resource "go.opentelemetry.io/proto/otlp/resource/v1" + trace "go.opentelemetry.io/proto/otlp/trace/v1" +) + +type OtelTrace struct { + TraceId string + TracesData *trace.TracesData +} + +func NewOtelTrace(traceId string, serviceName string, spans []*trace.Span) *OtelTrace { + return &OtelTrace{ + TraceId: traceId, + TracesData: &trace.TracesData{ + ResourceSpans: []*trace.ResourceSpans{ + { + Resource: &resource.Resource{ + Attributes: []*common.KeyValue{ + NewOtelKeyValueString("service.name", serviceName), + }, + }, + ScopeSpans: []*trace.ScopeSpans{ + { + Spans: spans, + }, + }, + }, + }, + }, + } +} + +func (t *OtelTrace) SetMetadata(te *TraceEvent, meta map[string]string) { + for _, rs := range t.TracesData.ResourceSpans { + for _, ss := range rs.ScopeSpans { + for _, span := range ss.Spans { + for key, value := range meta { + span.Attributes = append(span.Attributes, NewOtelKeyValueString(key, value)) + } + } + } + } +} + +func NewOtelSpan(traceId string, parentId []byte, name string, start, end time.Time) *trace.Span { + if parentId == nil { + parentId = []byte{} + } + + traceIdB, err := hex.DecodeString(traceId) + if err != nil { + panic(err) + } + + spanId := NewSpanId().Msb() + spanIdB := make([]byte, 8) + binary.LittleEndian.PutUint64(spanIdB, spanId) + + return &trace.Span{ + TraceId: traceIdB, + SpanId: spanIdB, + ParentSpanId: parentId, + Name: name, + Kind: 1, + StartTimeUnixNano: uint64(start.UnixNano()), + EndTimeUnixNano: uint64(end.UnixNano()), + // uses empty defaults for remaining fields... + } +} + +func NewOtelKeyValueString(key string, value string) *common.KeyValue { + strVal := &common.AnyValue_StringValue{ + StringValue: value, + } + return &common.KeyValue{ + Key: key, + Value: &common.AnyValue{ + Value: strVal, + }, + } +} + +func NewOtelKeyValueInt64(key string, value int64) *common.KeyValue { + intVal := &common.AnyValue_IntValue{ + IntValue: value, + } + return &common.KeyValue{ + Key: key, + Value: &common.AnyValue{ + Value: intVal, + }, + } +} + +func GetOtelAttrFromSpan(attr string, span *trace.Span) (int, *common.KeyValue) { + for i, attr := range span.Attributes { + if attr.Key == "allocation" { + return i, attr + } + } + return -1, nil +} + +func AddOtelKeyValueInt64(kvs ...*common.KeyValue) *common.KeyValue { + if len(kvs) > 0 { + retKv := &common.KeyValue{ + Key: kvs[0].Key, + Value: kvs[0].Value, + } + for i := 1; i < len(kvs); i++ { + v, ok := retKv.Value.Value.(*common.AnyValue_IntValue) + if ok { + curr, ok := kvs[i].Value.Value.(*common.AnyValue_IntValue) + if ok { + intVal := &common.AnyValue_IntValue{ + IntValue: v.IntValue + curr.IntValue, + } + retKv.Value.Value = intVal + } + } + } + return retKv + } + return nil +} diff --git a/vendor/github.com/dylibso/observe-sdk/go/telemetry.go b/vendor/github.com/dylibso/observe-sdk/go/telemetry.go new file mode 100644 index 00000000000..96becea467c --- /dev/null +++ b/vendor/github.com/dylibso/observe-sdk/go/telemetry.go @@ -0,0 +1,84 @@ +package observe + +import ( + "encoding/binary" + "encoding/hex" + "errors" + "fmt" + "math/rand" + "time" +) + +// This is a shared type for a span or trace id. +// It's represented by 2 uint64s and can be transformed +// to different string or int representations where needed. +type TelemetryId struct { + lsb uint64 + msb uint64 +} + +var rng rand.Source + +func init() { + rng = rand.NewSource(time.Now().UnixNano()) +} + +// Create a new trace id +func NewTraceId() TelemetryId { + return TelemetryId{ + msb: uint64(rng.Int63()), + lsb: uint64(rng.Int63()), + } +} + +// Create a new span id +func NewSpanId() TelemetryId { + return TelemetryId{ + msb: uint64(rng.Int63()), + lsb: uint64(rng.Int63()), + } +} + +func (id TelemetryId) Msb() uint64 { + return id.msb +} + +func (id TelemetryId) Lsb() uint64 { + return id.lsb +} + +// Encode this id into an 8 byte hex (16 chars) +// Just uses the least significant of the 16 bytes +func (t TelemetryId) ToHex8() string { + return fmt.Sprintf("%016x", t.lsb) +} + +// Encode this id into a 16 byte hex (32 chars) +// Uses both 16 byte uint64 values +func (t TelemetryId) ToHex16() string { + return fmt.Sprintf("%016x%016x", t.msb, t.lsb) +} + +// Some adapters may need a raw representation +func (t TelemetryId) ToUint64() uint64 { + return t.lsb +} + +func (t *TelemetryId) FromBytes(id []byte) error { + if len(id) != 16 { + return errors.New("TraceID must be 16 bytes") + } + + t.msb = binary.BigEndian.Uint64(id) + t.lsb = binary.BigEndian.Uint64(id[8:]) + + return nil +} + +func (t *TelemetryId) FromString(id string) error { + b, err := hex.DecodeString(id) + if err != nil { + return err + } + return t.FromBytes(b) +} diff --git a/vendor/github.com/dylibso/observe-sdk/go/trace_ctx.go b/vendor/github.com/dylibso/observe-sdk/go/trace_ctx.go new file mode 100644 index 00000000000..f7541282b1a --- /dev/null +++ b/vendor/github.com/dylibso/observe-sdk/go/trace_ctx.go @@ -0,0 +1,380 @@ +package observe + +import ( + "context" + "log" + "strings" + "time" + + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental" +) + +// TraceCtx holds the context for a trace, or wasm module invocation. +// It collects holds a channel to the Adapter and from the wazero Listener +// It will collect events throughout the invocation of the function. Calling +// Finish() will then submit those events to the Adapter to be processed and sent +type TraceCtx struct { + adapter chan TraceEvent + raw chan RawEvent + events []Event + stack []CallEvent + Options *Options + names map[uint32]string + telemetryId TelemetryId + adapterMeta interface{} +} + +// Creates a new TraceCtx. Used internally by the Adapter. The user should create the trace context from the Adapter. +func newTraceCtx(ctx context.Context, eventsChan chan TraceEvent, r wazero.Runtime, data []byte, opts *Options) (*TraceCtx, error) { + names, err := parseNames(data) + if err != nil { + return nil, err + } + + if opts.ChannelBufferSize == 0 { + opts.ChannelBufferSize = 64 // set a reasonable minimum here so unset option doesn't block execution on an unbuffered channel + } + + traceCtx := &TraceCtx{ + adapter: eventsChan, + raw: make(chan RawEvent, opts.ChannelBufferSize), + names: names, + telemetryId: NewTraceId(), + Options: opts, + } + + err = traceCtx.init(ctx, r) + if err != nil { + return nil, err + } + + return traceCtx, nil +} + +func (t *TraceCtx) SetTraceId(id string) error { + return t.telemetryId.FromString(id) +} + +func (t *TraceCtx) Metadata(metadata interface{}) { + t.adapterMeta = metadata +} + +// Finish() will stop the trace and send the +// TraceEvent payload to the adapter +func (t *TraceCtx) Finish() { + traceEvent := TraceEvent{ + Events: t.events, + TelemetryId: t.telemetryId, + AdapterMeta: t.adapterMeta, + } + t.adapter <- traceEvent + // clear the trace context + t.events = nil + t.telemetryId = NewTraceId() +} + +// Attaches the wazero FunctionListener to the context +func (t *TraceCtx) withListener(ctx context.Context) context.Context { + return experimental.WithFunctionListenerFactory(ctx, t) +} + +// Initializes the TraceCtx. This connects up the channels with events from the FunctionListener. +// Should only be called once. +func (t *TraceCtx) init(ctx context.Context, r wazero.Runtime) error { + ctx = t.withListener(ctx) + + if r.Module("dylibso_observe") != nil || r.Module("dylibso:observe/instrument") != nil || + r.Module("dylibso:observe/api") != nil { + return nil + } + + enterFunc := func(ctx context.Context, m api.Module, i uint32) { + start := time.Now() + ev := <-t.raw + + t.enter(ev, start) + } + + spanEnterFunc := func(ctx context.Context, m api.Module, ptr uint32, len uint32) { + start := time.Now() + ev := <-t.raw + + functionName, ok := m.Memory().Read(ptr, len) + if !ok { + log.Printf("span_enter: failed to read memory at offset %v with length %v\n", ptr, len) + } + + ev.FunctionName = string(functionName) + + t.enter(ev, start) + } + + oldSpanEnterFunc := func(ctx context.Context, m api.Module, ptr uint64, len uint32) { + spanEnterFunc(ctx, m, uint32(ptr), len) + } + + exitFunc := func(ctx context.Context, i uint32) { + end := time.Now() + ev := <-t.raw + + t.exit(ev, end) + } + + spanExitFunc := func(ctx context.Context, m api.Module) { + end := time.Now() + ev := <-t.raw + + t.exit(ev, end) + } + + memoryGrowFunc := func(ctx context.Context, amt uint32) { + ev := <-t.raw + if ev.Kind != RawMemoryGrow { + log.Println("Expected event", MemoryGrow, "but got", ev.Kind) + return + } + + if len(t.stack) > 0 { + f := t.stack[len(t.stack)-1] + ev.FunctionIndex = f.FunctionIndex() + ev.FunctionName = f.FunctionName() + } + + event := MemoryGrowEvent{ + Raw: ev, + Time: time.Now(), + } + + fn, ok := t.popFunction() + if !ok { + t.events = append(t.events, event) + return + } + fn.within = append(fn.within, event) + t.pushFunction(fn) + } + + metricFunc := func(ctx context.Context, m api.Module, f uint32, ptr uint32, l uint32) { + format := MetricFormat(f) + buffer, ok := m.Memory().Read(ptr, l) + if !ok { + log.Printf("metric: failed to read memory at offset %v with length %v\n", ptr, l) + } + + event := MetricEvent{ + Time: time.Now(), + Format: format, + Message: string(buffer), + } + + fn, ok := t.popFunction() + if !ok { + t.events = append(t.events, event) + return + } + fn.within = append(fn.within, event) + t.pushFunction(fn) + } + + oldMetricFunc := func(ctx context.Context, m api.Module, f uint32, ptr uint64, len uint32) { + metricFunc(ctx, m, f, uint32(ptr), len) + } + + spanTagsFunc := func(ctx context.Context, m api.Module, ptr uint32, len uint32) { + buffer, ok := m.Memory().Read(ptr, len) + if !ok { + log.Printf("span-tags: failed to read memory at offset %v with length %v\n", ptr, len) + } + + ev := <-t.raw + if ev.Kind != RawSpanTags { + log.Println("Expected event", SpanTags, "but got", ev.Kind) + return + } + + event := SpanTagsEvent{ + Time: time.Now(), + Raw: ev, + Tags: strings.Split(string(buffer), ","), + } + + fn, ok := t.popFunction() + if !ok { + t.events = append(t.events, event) + return + } + fn.within = append(fn.within, event) + t.pushFunction(fn) + } + + oldSpanTagsFunc := func(ctx context.Context, m api.Module, ptr uint64, len uint32) { + spanTagsFunc(ctx, m, uint32(ptr), len) + } + + logFunc := func(ctx context.Context, m api.Module, l uint32, ptr uint32, len uint32) { + if l < uint32(Error) || l > uint32(Debug) { + log.Printf("log: invalid log level %v\n", l) + } + + level := LogLevel(l) + + buffer, ok := m.Memory().Read(ptr, len) + if !ok { + log.Printf("log: failed to read memory at offset %v with length %v\n", ptr, len) + } + + event := LogEvent{ + Time: time.Now(), + Level: level, + Message: string(buffer), + } + + fn, ok := t.popFunction() + if !ok { + t.events = append(t.events, event) + return + } + fn.within = append(fn.within, event) + t.pushFunction(fn) + } + + oldLogFunc := func(ctx context.Context, m api.Module, l uint32, ptr uint64, len uint32) { + logFunc(ctx, m, l, uint32(ptr), len) + } + + // instrument api + { + instrument := r.NewHostModuleBuilder("dylibso:observe/instrument") + instrFunctions := instrument.NewFunctionBuilder() + instrFunctions.WithFunc(enterFunc).Export("enter") + instrFunctions.WithFunc(exitFunc).Export("exit") + instrFunctions.WithFunc(memoryGrowFunc).Export("memory-grow") + _, err := instrument.Instantiate(ctx) + if err != nil { + return err + } + } + + // manual api + { + api := r.NewHostModuleBuilder("dylibso:observe/api") + apiFunctions := api.NewFunctionBuilder() + apiFunctions.WithFunc(spanEnterFunc).Export("span-enter") + apiFunctions.WithFunc(spanExitFunc).Export("span-exit") + apiFunctions.WithFunc(spanTagsFunc).Export("span-tags") + apiFunctions.WithFunc(metricFunc).Export("metric") + apiFunctions.WithFunc(logFunc).Export("log") + _, err := api.Instantiate(ctx) + if err != nil { + return err + } + } + + //old api (combined instrument and manual api) + { + observe := r.NewHostModuleBuilder("dylibso_observe") + observeFunctions := observe.NewFunctionBuilder() + observeFunctions.WithFunc(enterFunc).Export("instrument_enter") + observeFunctions.WithFunc(exitFunc).Export("instrument_exit") + observeFunctions.WithFunc(memoryGrowFunc).Export("instrument_memory_grow") + observeFunctions.WithFunc(oldSpanEnterFunc).Export("span_enter") + observeFunctions.WithFunc(spanExitFunc).Export("span_exit") + observeFunctions.WithFunc(oldSpanTagsFunc).Export("span_tags") + observeFunctions.WithFunc(oldMetricFunc).Export("metric") + observeFunctions.WithFunc(oldLogFunc).Export("log") + _, err := observe.Instantiate(ctx) + if err != nil { + return err + } + } + + return nil +} + +func (t *TraceCtx) enter(ev RawEvent, start time.Time) { + if ev.Kind != RawEnter { + log.Println("Expected event", RawEnter, "but got", ev.Kind) + } + t.pushFunction(CallEvent{Raw: []RawEvent{ev}, Time: start}) +} + +func (t *TraceCtx) exit(ev RawEvent, end time.Time) { + + if ev.Kind != RawExit { + log.Println("Expected event", RawExit, "but got", ev.Kind) + return + } + fn, ok := t.peekFunction() + if !ok { + log.Println("Expected values on started function stack, but none were found") + return + } + if ev.FunctionIndex != fn.FunctionIndex() { + log.Println("Expected call to", ev.FunctionIndex, "but found call to", fn.FunctionIndex()) + return + } + + fn, _ = t.popFunction() + fn.Stop(end) + fn.Raw = append(fn.Raw, ev) + + // if there is no function left to pop, we are exiting the root function of the trace + f, ok := t.peekFunction() + if !ok { + t.events = append(t.events, fn) + return + } + + // if the function duration is less than minimum duration, disregard + funcDuration := fn.Duration.Microseconds() + minSpanDuration := t.Options.SpanFilter.MinDuration.Microseconds() + if funcDuration < minSpanDuration { + // check for memory allocations and attribute them to the parent span + f, ok = t.popFunction() + if ok { + for _, ev := range fn.within { + switch e := ev.(type) { + case MemoryGrowEvent: + f.within = append(f.within, e) + } + } + t.pushFunction(f) + } + return + } + + // the function is within another function + f, ok = t.popFunction() + if ok { + f.within = append(f.within, fn) + t.pushFunction(f) + } +} + +// Pushes a function onto the stack +func (t *TraceCtx) pushFunction(ev CallEvent) { + t.stack = append(t.stack, ev) +} + +// Pops a function off the stack +func (t *TraceCtx) popFunction() (CallEvent, bool) { + if len(t.stack) == 0 { + return CallEvent{}, false + } + + event := t.stack[len(t.stack)-1] + t.stack = t.stack[:len(t.stack)-1] + + return event, true +} + +// Peek at the function on top of the stack without modifying +func (t *TraceCtx) peekFunction() (CallEvent, bool) { + if len(t.stack) == 0 { + return CallEvent{}, false + } + + return t.stack[len(t.stack)-1], true +} diff --git a/vendor/github.com/dylibso/observe-sdk/go/wasm.go b/vendor/github.com/dylibso/observe-sdk/go/wasm.go new file mode 100644 index 00000000000..7f0768c8dca --- /dev/null +++ b/vendor/github.com/dylibso/observe-sdk/go/wasm.go @@ -0,0 +1,42 @@ +package observe + +import ( + "errors" + "log" + + "github.com/tetratelabs/wabin/binary" + "github.com/tetratelabs/wabin/wasm" +) + +// Parse the names of the functions out of the +// names custom section in the wasm binary. +func parseNames(data []byte) (map[uint32]string, error) { + features := wasm.CoreFeaturesV2 + m, err := binary.DecodeModule(data, features) + if err != nil { + return nil, err + } + + if m.NameSection == nil { + return nil, errors.New("Name section not found") + } + + names := make(map[uint32]string, len(m.NameSection.FunctionNames)) + + for _, v := range m.NameSection.FunctionNames { + names[v.Index] = v.Name + } + + warnOnDylibsoObserve := true + for _, item := range m.ImportSection { + if item.Module == "dylibso_observe" { + if warnOnDylibsoObserve { + warnOnDylibsoObserve = false + log.Println("Module uses deprecated namespace \"dylibso_observe\"!\n" + + "Please consider reinstrumenting with newer wasm-instr or Observe API!") + } + } + } + + return names, nil +} diff --git a/vendor/github.com/extism/go-sdk/.gitignore b/vendor/github.com/extism/go-sdk/.gitignore new file mode 100644 index 00000000000..3b735ec4a8c --- /dev/null +++ b/vendor/github.com/extism/go-sdk/.gitignore @@ -0,0 +1,21 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work diff --git a/vendor/github.com/extism/go-sdk/LICENSE b/vendor/github.com/extism/go-sdk/LICENSE new file mode 100644 index 00000000000..9168a9e41e3 --- /dev/null +++ b/vendor/github.com/extism/go-sdk/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2023, Extism + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/extism/go-sdk/README.md b/vendor/github.com/extism/go-sdk/README.md new file mode 100644 index 00000000000..2b93fc97ebf --- /dev/null +++ b/vendor/github.com/extism/go-sdk/README.md @@ -0,0 +1,365 @@ +# Extism Go SDK + +This repo houses the Go SDK for integrating with the [Extism](https://extism.org/) runtime. Install this library into your host Go applications to run Extism plugins. + +Join the [Extism Discord](https://extism.org/discord) and chat with us! + +## Installation + +Install via `go get`: + +``` +go get github.com/extism/go-sdk +``` + +## Reference Docs + +You can find the reference docs at [https://pkg.go.dev/github.com/extism/go-sdk](https://pkg.go.dev/github.com/extism/go-sdk). + +## Getting Started + +This guide should walk you through some of the concepts in Extism and this Go library. + +### Creating A Plug-in + +The primary concept in Extism is the [plug-in](https://extism.org/docs/concepts/plug-in). You can think of a plug-in as a code module stored in a `.wasm` file. + +Plug-in code can come from a file on disk, object storage or any number of places. Since you may not have one handy let's load a demo plug-in from the web. Let's +start by creating a main func and loading an Extism Plug-in: + +```go +package main + +import ( + "context" + "fmt" + "github.com/extism/go-sdk" + "os" +) + +func main() { + manifest := extism.Manifest{ + Wasm: []extism.Wasm{ + extism.WasmUrl{ + Url: "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm", + }, + }, + } + + ctx := context.Background() + config := extism.PluginConfig{} + plugin, err := extism.NewPlugin(ctx, manifest, config, []extism.HostFunction{}) + + if err != nil { + fmt.Printf("Failed to initialize plugin: %v\n", err) + os.Exit(1) + } +} +``` +> **Note**: See [the Manifest docs](https://pkg.go.dev/github.com/extism/go-sdk#Manifest) as it has a rich schema and a lot of options. + +### Calling A Plug-in's Exports + +This plug-in was written in Rust and it does one thing, it counts vowels in a string. As such, it exposes one "export" function: `count_vowels`. We can call exports using [extism.Plugin.Call](https://pkg.go.dev/github.com/extism/go-sdk#Plugin.Call). +Let's add that code to our main func: + +```go +func main() { + // ... + + data := []byte("Hello, World!") + exit, out, err := plugin.Call("count_vowels", data) + if err != nil { + fmt.Println(err) + os.Exit(int(exit)) + } + + response := string(out) + fmt.Println(response) + // => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"} +} +``` + +Running this should print out the JSON vowel count report: + +```bash +$ go run main.go +# => {"count":3,"total":3,"vowels":"aeiouAEIOU"} +``` + +All exports have a simple interface of optional bytes in, and optional bytes out. This plug-in happens to take a string and return a JSON encoded string with a report of results. + +> **Note**: If you want to pass a custom `context.Context` when calling a plugin function, you can use the [extism.Plugin.CallWithContext](https://pkg.go.dev/github.com/extism/go-sdk#Plugin.CallWithContext) method instead. + + +### Plug-in State + +Plug-ins may be stateful or stateless. Plug-ins can maintain state between calls by the use of variables. Our count vowels plug-in remembers the total number of vowels it's ever counted in the "total" key in the result. You can see this by making subsequent calls to the export: + +```go +func main () { + // ... + + exit, out, err := plugin.Call("count_vowels", []byte("Hello, World!")) + if err != nil { + fmt.Println(err) + os.Exit(int(exit)) + } + fmt.Println(string(out)) + // => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"} + + exit, out, err = plugin.Call("count_vowels", []byte("Hello, World!")) + if err != nil { + fmt.Println(err) + os.Exit(int(exit)) + } + fmt.Println(string(out)) + // => {"count": 3, "total": 9, "vowels": "aeiouAEIOU"} +} + +``` + +These variables will persist until this plug-in is freed or you initialize a new one. + +### Configuration + +Plug-ins may optionally take a configuration object. This is a static way to configure the plug-in. Our count-vowels plugin takes an optional configuration to change out which characters are considered vowels. Example: + +```go +func main() { + manifest := extism.Manifest{ + Wasm: []extism.Wasm{ + extism.WasmUrl{ + Url: "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm", + }, + }, + Config: map[string]string{ + "vowels": "aeiouyAEIOUY", + }, + } + + ctx := context.Background() + config := extism.PluginConfig{} + + plugin, err := extism.NewPlugin(ctx, manifest, config, []extism.HostFunction{}) + + if err != nil { + fmt.Printf("Failed to initialize plugin: %v\n", err) + os.Exit(1) + } + + exit, out, err := plugin.Call("count_vowels", []byte("Yellow, World!")) + if err != nil { + fmt.Println(err) + os.Exit(int(exit)) + } + + fmt.Println(string(out)) + // => {"count": 4, "total": 4, "vowels": "aeiouAEIOUY"} +} +``` + +### Host Functions + +Let's extend our count-vowels example a little bit: Instead of storing the `total` in an ephemeral plug-in var, let's store it in a persistent key-value store! + +Wasm can't use our KV store on it's own. This is where [Host Functions](https://extism.org/docs/concepts/host-functions) come in. + +[Host functions](https://extism.org/docs/concepts/host-functions) allow us to grant new capabilities to our plug-ins from our application. They are simply some Go functions you write which can be passed down and invoked from any language inside the plug-in. + +Let's load the manifest like usual but load up this `count_vowels_kvstore` plug-in: + +```go +manifest := extism.Manifest{ + Wasm: []extism.Wasm{ + extism.WasmUrl{ + Url: "https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm", + }, + }, +} +``` + +> *Note*: The source code for this is [here](https://github.com/extism/plugins/blob/main/count_vowels_kvstore/src/lib.rs) and is written in rust, but it could be written in any of our PDK languages. + +Unlike our previous plug-in, this plug-in expects you to provide host functions that satisfy our its import interface for a KV store. + +We want to expose two functions to our plugin, `kv_write(key string, value []bytes)` which writes a bytes value to a key and `kv_read(key string) []byte` which reads the bytes at the given `key`. +```go +// pretend this is Redis or something :) +kvStore := make(map[string][]byte) + +kvRead := extism.NewHostFunctionWithStack( + "kv_read", + func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { + key, err := p.ReadString(stack[0]) + if err != nil { + panic(err) + } + + value, success := kvStore[key] + if !success { + value = []byte{0, 0, 0, 0} + } + + stack[0], err = p.WriteBytes(value) + }, + []ValueType{ValueTypePTR}, + []ValueType{ValueTypePTR}, +) + +kvWrite := extism.NewHostFunctionWithStack( + "kv_write", + func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { + key, err := p.ReadString(stack[0]) + if err != nil { + panic(err) + } + + value, err := p.ReadBytes(stack[1]) + if err != nil { + panic(err) + } + + kvStore[key] = value + }, + []ValueType{ValueTypePTR, ValueTypePTR}, + []ValueType{}, +) +``` + +> *Note*: In order to write host functions you should get familiar with the methods on the [extism.CurrentPlugin](https://pkg.go.dev/github.com/extism/go-sdk#CurrentPlugin) type. The `p` parameter is an instance of this type. + +We need to pass these imports to the plug-in to create them. All imports of a plug-in must be satisfied for it to be initialized: + +```go +plugin, err := extism.NewPlugin(ctx, manifest, config, []extism.HostFunction{kvRead, kvWrite}); +``` + +Now we can invoke the event: + +```go +exit, out, err := plugin.Call("count_vowels", []byte("Hello, World!")) +// => Read from key=count-vowels" +// => Writing value=3 from key=count-vowels" +// => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"} + +exit, out, err = plugin.Call("count_vowels", []byte("Hello, World!")) +// => Read from key=count-vowels" +// => Writing value=6 from key=count-vowels" +// => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"} +``` + +### Enabling Compilation Cache +While Wazero (the underlying Wasm runtime) is very fast in initializing modules, you can make subsequent initializations even faster by enabling the compilation cache: + +```go +ctx := context.Background() +cache := wazero.NewCompilationCache() +defer cache.Close(ctx) + +manifest := Manifest{Wasm: []Wasm{WasmFile{Path: "wasm/noop.wasm"}}} + +config := PluginConfig{ + EnableWasi: true, + ModuleConfig: wazero.NewModuleConfig(), + RuntimeConfig: wazero.NewRuntimeConfig().WithCompilationCache(cache), +} + +_, err := NewPlugin(ctx, manifest, config, []HostFunction{}) +``` + +### Integrate with Dylibso Observe SDK +Dylibso provides [observability SDKs](https://github.com/dylibso/observe-sdk) for WebAssembly (Wasm), enabling continuous monitoring of WebAssembly code as it executes within a runtime. It provides developers with the tools necessary to capture and emit telemetry data from Wasm code, including function execution and memory allocation traces, logs, and metrics. + +While Observe SDK has adapters for many popular observability platforms, it also ships with an stdout adapter: + +``` +ctx := context.Background() + +adapter := stdout.NewStdoutAdapter() +adapter.Start(ctx) + +manifest := manifest("nested.c.instr.wasm") + +config := PluginConfig{ + ModuleConfig: wazero.NewModuleConfig().WithSysWalltime(), + EnableWasi: true, + ObserveAdapter: adapter.AdapterBase, +} + +plugin, err := NewPlugin(ctx, manifest, config, []HostFunction{}) +if err != nil { + panic(err) +} + +meta := map[string]string{ + "http.url": "https://example.com/my-endpoint", + "http.status_code": "200", + "http.client_ip": "192.168.1.0", +} + +plugin.TraceCtx.Metadata(meta) + +_, _, _ = plugin.Call("_start", []byte("hello world")) +plugin.Close() +``` + +### Enable filesystem access + +WASM plugins can read/write files outside the runtime. To do this we add `AllowedPaths` mapping of "HOST:PLUGIN" to the `extism.Manifest` of our plugin. + +```go +package main + +import ( + "context" + "fmt" + "os" + + extism "github.com/extism/go-sdk" +) + +func main() { + manifest := extism.Manifest{ + AllowedPaths: map[string]string{ + // Here we specifify a host directory data to be linked + // to the /mnt directory inside the wasm runtime + "data": "/mnt", + }, + Wasm: []extism.Wasm{ + extism.WasmFile{ + Path: "fs_plugin.wasm", + }, + }, + } + + ctx := context.Background() + config := extism.PluginConfig{ + EnableWasi: true, + } + plugin, err := extism.NewPlugin(ctx, manifest, config, []extism.HostFunction{}) + + if err != nil { + fmt.Printf("Failed to initialize plugin: %v\n", err) + os.Exit(1) + } + + data := []byte("Hello world, this is written from within our wasm plugin.") + exit, _, err := plugin.Call("write_file", data) + if err != nil { + fmt.Println(err) + os.Exit(int(exit)) + } +} +``` + +> *Note*: In order for filesystem APIs to work the plugin needs to be compiled with WASI target. Source code for the plugin can be found [here](https://github.com/extism/go-pdk/blob/main/example/fs/main.go) and is written in Go, but it could be written in any of our PDK languages. + +## Build example plugins + +Since our [example plugins](./plugins/) are also written in Go, for compiling them we use [TinyGo](https://tinygo.org/): + +```sh +cd plugins/config +tinygo build -target wasi -o ../wasm/config.wasm main.go +``` diff --git a/vendor/github.com/extism/go-sdk/extism-runtime.wasm b/vendor/github.com/extism/go-sdk/extism-runtime.wasm new file mode 100644 index 00000000000..618a34e66ea Binary files /dev/null and b/vendor/github.com/extism/go-sdk/extism-runtime.wasm differ diff --git a/vendor/github.com/extism/go-sdk/extism-runtime.wasm.version b/vendor/github.com/extism/go-sdk/extism-runtime.wasm.version new file mode 100644 index 00000000000..a20e2d82d27 --- /dev/null +++ b/vendor/github.com/extism/go-sdk/extism-runtime.wasm.version @@ -0,0 +1 @@ +v1.7.0 diff --git a/vendor/github.com/extism/go-sdk/extism.go b/vendor/github.com/extism/go-sdk/extism.go new file mode 100644 index 00000000000..cf0383fa037 --- /dev/null +++ b/vendor/github.com/extism/go-sdk/extism.go @@ -0,0 +1,551 @@ +package extism + +import ( + "context" + "crypto/sha256" + _ "embed" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "math" + "net/http" + "os" + "sync/atomic" + "time" + + observe "github.com/dylibso/observe-sdk/go" + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/sys" +) + +type PluginCtxKey string +type InputOffsetKey string + +//go:embed extism-runtime.wasm +var extismRuntimeWasm []byte + +//go:embed extism-runtime.wasm.version +var extismRuntimeWasmVersion string + +func RuntimeVersion() string { + return extismRuntimeWasmVersion +} + +// Runtime represents the Extism plugin's runtime environment, including the underlying Wazero runtime and modules. +type Runtime struct { + Wazero wazero.Runtime + Extism api.Module + Env api.Module +} + +// PluginInstanceConfig contains configuration options for the Extism plugin. +type PluginInstanceConfig struct { + // ModuleConfig allows the user to specify custom module configuration. + // + // NOTE: Module name and start functions are ignored as they are overridden by Extism, also if Manifest contains + // non-empty AllowedPaths, then FS is also ignored. If EXTISM_ENABLE_WASI_OUTPUT is set, then stdout and stderr are + // set to os.Stdout and os.Stderr respectively (ignoring user defined module config). + ModuleConfig wazero.ModuleConfig +} + +// HttpRequest represents an HTTP request to be made by the plugin. +type HttpRequest struct { + Url string + Headers map[string]string + Method string +} + +// LogLevel defines different log levels. +type LogLevel int32 + +const ( + logLevelUnset LogLevel = iota // unexporting this intentionally so its only ever the default + LogLevelTrace + LogLevelDebug + LogLevelInfo + LogLevelWarn + LogLevelError + + LogLevelOff LogLevel = math.MaxInt32 +) + +func (l LogLevel) ExtismCompat() int32 { + switch l { + case LogLevelTrace: + return 0 + case LogLevelDebug: + return 1 + case LogLevelInfo: + return 2 + case LogLevelWarn: + return 3 + case LogLevelError: + return 4 + default: + return int32(LogLevelOff) + } +} + +func (l LogLevel) String() string { + s := "" + switch l { + case LogLevelTrace: + s = "TRACE" + case LogLevelDebug: + s = "DEBUG" + case LogLevelInfo: + s = "INFO" + case LogLevelWarn: + s = "WARN" + case LogLevelError: + s = "ERROR" + default: + s = "OFF" + } + return s +} + +// Plugin is used to call WASM functions +type Plugin struct { + close []func(ctx context.Context) error + extism api.Module + mainModule api.Module + modules map[string]api.Module + Timeout time.Duration + Config map[string]string + Var map[string][]byte + AllowedHosts []string + AllowedPaths map[string]string + LastStatusCode int + LastResponseHeaders map[string]string + MaxHttpResponseBytes int64 + MaxVarBytes int64 + log func(LogLevel, string) + hasWasi bool + guestRuntime guestRuntime + Adapter *observe.AdapterBase + traceCtx *observe.TraceCtx +} + +func logStd(level LogLevel, message string) { + log.Print(message) +} + +func (p *Plugin) Module() *Module { + return &Module{inner: p.mainModule} +} + +// SetLogger sets a custom logging callback +func (p *Plugin) SetLogger(logger func(LogLevel, string)) { + p.log = logger +} + +func (p *Plugin) Log(level LogLevel, message string) { + minimumLevel := LogLevel(pluginLogLevel.Load()) + + // If the global log level hasn't been set, use LogLevelOff as default + if minimumLevel == logLevelUnset { + minimumLevel = LogLevelOff + } + + if level >= minimumLevel { + p.log(level, message) + } +} + +func (p *Plugin) Logf(level LogLevel, format string, args ...any) { + message := fmt.Sprintf(format, args...) + p.Log(level, message) +} + +// Wasm is an interface that represents different ways of providing WebAssembly data. +type Wasm interface { + ToWasmData(ctx context.Context) (WasmData, error) +} + +// WasmData represents in-memory WebAssembly data, including its content, hash, and name. +type WasmData struct { + Data []byte `json:"data"` + Hash string `json:"hash,omitempty"` + Name string `json:"name,omitempty"` +} + +// WasmFile represents WebAssembly data that needs to be loaded from a file. +type WasmFile struct { + Path string `json:"path"` + Hash string `json:"hash,omitempty"` + Name string `json:"name,omitempty"` +} + +// WasmUrl represents WebAssembly data that needs to be fetched from a URL. +type WasmUrl struct { + Url string `json:"url"` + Hash string `json:"hash,omitempty"` + Headers map[string]string `json:"headers,omitempty"` + Name string `json:"name,omitempty"` + Method string `json:"method,omitempty"` +} + +type concreteWasm struct { + Data []byte `json:"data,omitempty"` + Path string `json:"path,omitempty"` + Url string `json:"url,omitempty"` + Headers map[string]string `json:"headers,omitempty"` + Method string `json:"method,omitempty"` + Hash string `json:"hash,omitempty"` + Name string `json:"name,omitempty"` +} + +func (d WasmData) ToWasmData(ctx context.Context) (WasmData, error) { + return d, nil +} + +func (f WasmFile) ToWasmData(ctx context.Context) (WasmData, error) { + select { + case <-ctx.Done(): + return WasmData{}, ctx.Err() + default: + data, err := os.ReadFile(f.Path) + if err != nil { + return WasmData{}, err + } + + return WasmData{ + Data: data, + Hash: f.Hash, + Name: f.Name, + }, nil + } +} + +func (u WasmUrl) ToWasmData(ctx context.Context) (WasmData, error) { + client := http.DefaultClient + + req, err := http.NewRequestWithContext(ctx, u.Method, u.Url, nil) + if err != nil { + return WasmData{}, err + } + + for key, value := range u.Headers { + req.Header.Set(key, value) + } + + resp, err := client.Do(req) + if err != nil { + return WasmData{}, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return WasmData{}, errors.New("failed to fetch Wasm data from URL") + } + + data, err := io.ReadAll(resp.Body) + if err != nil { + return WasmData{}, err + } + + return WasmData{ + Data: data, + Hash: u.Hash, + Name: u.Name, + }, nil +} + +type ManifestMemory struct { + MaxPages uint32 `json:"max_pages,omitempty"` + MaxHttpResponseBytes int64 `json:"max_http_response_bytes,omitempty"` + MaxVarBytes int64 `json:"max_var_bytes,omitempty"` +} + +// Manifest represents the plugin's manifest, including Wasm modules and configuration. +// See https://extism.org/docs/concepts/manifest for schema. +type Manifest struct { + Wasm []Wasm `json:"wasm"` + Memory *ManifestMemory `json:"memory,omitempty"` + Config map[string]string `json:"config,omitempty"` + AllowedHosts []string `json:"allowed_hosts,omitempty"` + AllowedPaths map[string]string `json:"allowed_paths,omitempty"` + Timeout uint64 `json:"timeout_ms,omitempty"` +} + +type concreteManifest struct { + Wasm []concreteWasm `json:"wasm"` + Memory *struct { + MaxPages uint32 `json:"max_pages,omitempty"` + MaxHttpResponseBytes *int64 `json:"max_http_response_bytes,omitempty"` + MaxVarBytes *int64 `json:"max_var_bytes,omitempty"` + } `json:"memory,omitempty"` + Config map[string]string `json:"config,omitempty"` + AllowedHosts []string `json:"allowed_hosts,omitempty"` + AllowedPaths map[string]string `json:"allowed_paths,omitempty"` + Timeout uint64 `json:"timeout_ms,omitempty"` +} + +func (m *Manifest) UnmarshalJSON(data []byte) error { + tmp := concreteManifest{} + err := json.Unmarshal(data, &tmp) + if err != nil { + return err + } + + m.Memory = &ManifestMemory{} + if tmp.Memory != nil { + m.Memory.MaxPages = tmp.Memory.MaxPages + if tmp.Memory.MaxHttpResponseBytes != nil { + m.Memory.MaxHttpResponseBytes = *tmp.Memory.MaxHttpResponseBytes + } else { + m.Memory.MaxHttpResponseBytes = -1 + } + + if tmp.Memory.MaxVarBytes != nil { + m.Memory.MaxVarBytes = *tmp.Memory.MaxVarBytes + } else { + m.Memory.MaxVarBytes = -1 + } + } else { + m.Memory.MaxPages = 0 + m.Memory.MaxHttpResponseBytes = -1 + m.Memory.MaxVarBytes = -1 + } + m.Config = tmp.Config + m.AllowedHosts = tmp.AllowedHosts + m.AllowedPaths = tmp.AllowedPaths + m.Timeout = tmp.Timeout + if m.Wasm == nil { + m.Wasm = []Wasm{} + } + for _, w := range tmp.Wasm { + if len(w.Data) > 0 { + m.Wasm = append(m.Wasm, WasmData{Data: w.Data, Hash: w.Hash, Name: w.Name}) + } else if len(w.Path) > 0 { + m.Wasm = append(m.Wasm, WasmFile{Path: w.Path, Hash: w.Hash, Name: w.Name}) + } else if len(w.Url) > 0 { + m.Wasm = append(m.Wasm, WasmUrl{ + Url: w.Url, + Headers: w.Headers, + Method: w.Method, + Hash: w.Hash, + Name: w.Name, + }) + } else { + return errors.New("invalid Wasm entry") + } + } + return nil +} + +// Close closes the plugin by freeing the underlying resources. +func (p *Plugin) Close(ctx context.Context) error { + return p.CloseWithContext(ctx) +} + +// CloseWithContext closes the plugin by freeing the underlying resources. +func (p *Plugin) CloseWithContext(ctx context.Context) error { + for _, fn := range p.close { + if err := fn(ctx); err != nil { + return err + } + } + return nil +} + +// add an atomic global to store the plugin runtime-wide log level +var pluginLogLevel = atomic.Int32{} + +// SetPluginLogLevel sets the log level for the plugin +func SetLogLevel(level LogLevel) { + pluginLogLevel.Store(int32(level)) +} + +// SetInput sets the input data for the plugin to be used in the next WebAssembly function call. +func (p *Plugin) SetInput(data []byte) (uint64, error) { + return p.SetInputWithContext(context.Background(), data) +} + +// SetInputWithContext sets the input data for the plugin to be used in the next WebAssembly function call. +func (p *Plugin) SetInputWithContext(ctx context.Context, data []byte) (uint64, error) { + _, err := p.extism.ExportedFunction("reset").Call(ctx) + if err != nil { + fmt.Println(err) + return 0, errors.New("reset") + } + + ptr, err := p.extism.ExportedFunction("alloc").Call(ctx, uint64(len(data))) + if err != nil { + return 0, err + } + p.Memory().Write(uint32(ptr[0]), data) + p.extism.ExportedFunction("input_set").Call(ctx, ptr[0], uint64(len(data))) + return ptr[0], nil +} + +// GetOutput retrieves the output data from the last WebAssembly function call. +func (p *Plugin) GetOutput() ([]byte, error) { + return p.GetOutputWithContext(context.Background()) +} + +// GetOutputWithContext retrieves the output data from the last WebAssembly function call. +func (p *Plugin) GetOutputWithContext(ctx context.Context) ([]byte, error) { + outputOffs, err := p.extism.ExportedFunction("output_offset").Call(ctx) + if err != nil { + return []byte{}, err + } + + outputLen, err := p.extism.ExportedFunction("output_length").Call(ctx) + if err != nil { + return []byte{}, err + } + mem, _ := p.Memory().Read(uint32(outputOffs[0]), uint32(outputLen[0])) + + // Make sure output is copied, because `Read` returns a write-through view + buffer := make([]byte, len(mem)) + copy(buffer, mem) + + return buffer, nil +} + +// Memory returns the plugin's WebAssembly memory interface. +func (p *Plugin) Memory() api.Memory { + return p.extism.ExportedMemory("memory") +} + +// GetError retrieves the error message from the last WebAssembly function call, if any. +func (p *Plugin) GetError() string { + return p.GetErrorWithContext(context.Background()) +} + +// GetErrorWithContext retrieves the error message from the last WebAssembly function call. +func (p *Plugin) GetErrorWithContext(ctx context.Context) string { + errOffs, err := p.extism.ExportedFunction("error_get").Call(ctx) + if err != nil { + return "" + } + + if errOffs[0] == 0 { + return "" + } + + errLen, err := p.extism.ExportedFunction("length").Call(ctx, errOffs[0]) + if err != nil { + return "" + } + + mem, _ := p.Memory().Read(uint32(errOffs[0]), uint32(errLen[0])) + return string(mem) +} + +// FunctionExists returns true when the named function is present in the plugin's main Module +func (p *Plugin) FunctionExists(name string) bool { + return p.mainModule.ExportedFunction(name) != nil +} + +// Call a function by name with the given input, returning the output +func (p *Plugin) Call(name string, data []byte) (uint32, []byte, error) { + return p.CallWithContext(context.Background(), name, data) +} + +// Call a function by name with the given input and context, returning the output +func (p *Plugin) CallWithContext(ctx context.Context, name string, data []byte) (uint32, []byte, error) { + if p.mainModule.IsClosed() { + return 0, nil, fmt.Errorf("module is closed") + } + + ctx = context.WithValue(ctx, PluginCtxKey("extism"), p.extism) + if p.Timeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, p.Timeout) + defer cancel() + } + + ctx = context.WithValue(ctx, PluginCtxKey("plugin"), p) + + intputOffset, err := p.SetInput(data) + if err != nil { + return 1, []byte{}, err + } + + ctx = context.WithValue(ctx, InputOffsetKey("inputOffset"), intputOffset) + + var f = p.mainModule.ExportedFunction(name) + + if f == nil { + return 1, []byte{}, fmt.Errorf("unknown function: %s", name) + } else if n := len(f.Definition().ResultTypes()); n > 1 { + return 1, []byte{}, fmt.Errorf("function %s has %v results, expected 0 or 1", name, n) + } + + var isStart = name == "_start" || name == "_initialize" + if p.guestRuntime.init != nil && !isStart && !p.guestRuntime.initialized { + err := p.guestRuntime.init(ctx) + if err != nil { + return 1, []byte{}, fmt.Errorf("failed to initialize runtime: %v", err) + } + p.guestRuntime.initialized = true + } + + p.Logf(LogLevelDebug, "Calling function : %v", name) + + res, err := f.Call(ctx) + + if p.traceCtx != nil { + defer p.traceCtx.Finish() + } + + // Try to extact WASI exit code + if exitErr, ok := err.(*sys.ExitError); ok { + exitCode := exitErr.ExitCode() + + if exitCode == 0 { + err = nil + } + + if len(res) == 0 { + res = []uint64{api.EncodeU32(exitCode)} + } + } + + var rc uint32 + if len(res) == 0 { + // As long as there is no error, we assume the call has succeeded + if err == nil { + rc = 0 + } else { + rc = 1 + } + } else { + rc = api.DecodeU32(res[0]) + } + + if err != nil { + return rc, []byte{}, err + } + + var returnErr error = nil + errMsg := p.GetErrorWithContext(ctx) + if errMsg != "" { + returnErr = errors.New(errMsg) + } + + output, err := p.GetOutputWithContext(ctx) + if err != nil { + e := fmt.Errorf("failed to get output: %v", err) + if returnErr != nil { + return rc, []byte{}, errors.Join(returnErr, e) + } else { + return rc, []byte{}, e + } + } + + return rc, output, returnErr +} + +func calculateHash(data []byte) string { + hasher := sha256.New() + hasher.Write(data) + return hex.EncodeToString(hasher.Sum(nil)) +} diff --git a/vendor/github.com/extism/go-sdk/host.go b/vendor/github.com/extism/go-sdk/host.go new file mode 100644 index 00000000000..c22e245bd7d --- /dev/null +++ b/vendor/github.com/extism/go-sdk/host.go @@ -0,0 +1,694 @@ +package extism + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "unsafe" + + // TODO: is there a better package for this? + "github.com/gobwas/glob" + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" +) + +type ValueType = byte + +const ( + // ValueTypeI32 is a 32-bit integer. + ValueTypeI32 = api.ValueTypeI32 + // ValueTypeI64 is a 64-bit integer. + ValueTypeI64 = api.ValueTypeI64 + // ValueTypeF32 is a 32-bit floating point number. + ValueTypeF32 = api.ValueTypeF32 + // ValueTypeF64 is a 64-bit floating point number. + ValueTypeF64 = api.ValueTypeF64 + // ValueTypePTR represents a pointer to an Extism memory block. Alias for ValueTypeI64 + ValueTypePTR = ValueTypeI64 +) + +// HostFunctionStackCallback is a Function implemented in Go instead of a wasm binary. +// The plugin parameter is the calling plugin, used to access memory or +// exported functions and logging. +// +// The stack is includes any parameters encoded according to their ValueType. +// Its length is the max of parameter or result length. When there are results, +// write them in order beginning at index zero. Do not use the stack after the +// function returns. +// +// Here's a typical way to read three parameters and write back one. +// +// // read parameters in index order +// argv, argvBuf := DecodeU32(inputs[0]), DecodeU32(inputs[1]) +// +// // write results back to the stack in index order +// stack[0] = EncodeU32(ErrnoSuccess) +// +// This function can be non-deterministic or cause side effects. It also +// has special properties not defined in the WebAssembly Core specification. +// Notably, this uses the caller's memory (via Module.Memory). See +// https://www.w3.org/TR/wasm-core-1/#host-functions%E2%91%A0 +// +// To safely decode/encode values from/to the uint64 inputs/ouputs, users are encouraged to use +// Extism's EncodeXXX or DecodeXXX functions. +type HostFunctionStackCallback func(ctx context.Context, p *CurrentPlugin, stack []uint64) + +// HostFunction represents a custom function defined by the host. +type HostFunction struct { + stackCallback HostFunctionStackCallback + Name string + Namespace string + Params []api.ValueType + Returns []api.ValueType +} + +func (f *HostFunction) SetNamespace(namespace string) { + f.Namespace = namespace +} + +// NewHostFunctionWithStack creates a new instance of a HostFunction, which is designed +// to provide custom functionality in a given host environment. +// Here's an example multiplication function that loads operands from memory: +// +// mult := NewHostFunctionWithStack( +// "mult", +// func(ctx context.Context, plugin *CurrentPlugin, stack []uint64) { +// a := DecodeI32(stack[0]) +// b := DecodeI32(stack[1]) +// +// stack[0] = EncodeI32(a * b) +// }, +// []ValueType{ValueTypeI64, ValueTypeI64}, +// ValueTypeI64 +// ) +func NewHostFunctionWithStack( + name string, + callback HostFunctionStackCallback, + params []ValueType, + returnTypes []ValueType) HostFunction { + + return HostFunction{ + stackCallback: callback, + Name: name, + Namespace: "extism:host/user", + Params: params, + Returns: returnTypes, + } +} + +type CurrentPlugin struct { + plugin *Plugin +} + +func (p *Plugin) currentPlugin() *CurrentPlugin { + return &CurrentPlugin{p} +} + +func (p *CurrentPlugin) Log(level LogLevel, message string) { + p.plugin.Log(level, message) +} + +func (p *CurrentPlugin) Logf(level LogLevel, format string, args ...any) { + p.plugin.Logf(level, format, args...) +} + +// Memory returns the plugin's WebAssembly memory interface. +func (p *CurrentPlugin) Memory() api.Memory { + return p.plugin.Memory() +} + +// Alloc a new memory block of the given length, returning its offset +func (p *CurrentPlugin) Alloc(n uint64) (uint64, error) { + return p.AllocWithContext(context.Background(), n) +} + +// Alloc a new memory block of the given length, returning its offset +func (p *CurrentPlugin) AllocWithContext(ctx context.Context, n uint64) (uint64, error) { + out, err := p.plugin.extism.ExportedFunction("alloc").Call(ctx, uint64(n)) + if err != nil { + return 0, err + } else if len(out) != 1 { + return 0, fmt.Errorf("expected 1 return, go %v", len(out)) + } + + return uint64(out[0]), nil +} + +// Free the memory block specified by the given offset +func (p *CurrentPlugin) Free(offset uint64) error { + return p.FreeWithContext(context.Background(), offset) +} + +// Free the memory block specified by the given offset +func (p *CurrentPlugin) FreeWithContext(ctx context.Context, offset uint64) error { + _, err := p.plugin.extism.ExportedFunction("free").Call(ctx, uint64(offset)) + if err != nil { + return err + } + + return nil +} + +// Length returns the number of bytes allocated at the specified offset +func (p *CurrentPlugin) Length(offs uint64) (uint64, error) { + return p.LengthWithContext(context.Background(), offs) +} + +// Length returns the number of bytes allocated at the specified offset +func (p *CurrentPlugin) LengthWithContext(ctx context.Context, offs uint64) (uint64, error) { + out, err := p.plugin.extism.ExportedFunction("length").Call(ctx, uint64(offs)) + if err != nil { + return 0, err + } else if len(out) != 1 { + return 0, fmt.Errorf("expected 1 return, go %v", len(out)) + } + + return uint64(out[0]), nil +} + +// Write a string to wasm memory and return the offset +func (p *CurrentPlugin) WriteString(s string) (uint64, error) { + return p.WriteBytes([]byte(s)) +} + +// WriteBytes writes a string to wasm memory and return the offset +func (p *CurrentPlugin) WriteBytes(b []byte) (uint64, error) { + ptr, err := p.Alloc(uint64(len(b))) + if err != nil { + return 0, err + } + + ok := p.Memory().Write(uint32(ptr), b) + if !ok { + return 0, fmt.Errorf("failed to write to memory") + } + + return ptr, nil +} + +// ReadString reads a string from wasm memory +func (p *CurrentPlugin) ReadString(offset uint64) (string, error) { + buffer, err := p.ReadBytes(offset) + if err != nil { + return "", err + } + + return string(buffer), nil +} + +// ReadBytes reads a byte array from memory +func (p *CurrentPlugin) ReadBytes(offset uint64) ([]byte, error) { + length, err := p.Length(offset) + if err != nil { + return []byte{}, err + } + + buffer, ok := p.Memory().Read(uint32(offset), uint32(length)) + if !ok { + return []byte{}, fmt.Errorf("invalid memory block") + } + + cpy := make([]byte, len(buffer)) + copy(cpy, buffer) + + return cpy, nil +} + +func buildHostModule(ctx context.Context, rt wazero.Runtime, name string, funcs []HostFunction) (api.Module, error) { + builder := rt.NewHostModuleBuilder(name) + + defineCustomHostFunctions(builder, funcs) + + return builder.Instantiate(ctx) +} + +func defineCustomHostFunctions(builder wazero.HostModuleBuilder, funcs []HostFunction) { + for _, f := range funcs { + + // Go closures capture variables by reference, not by value. + // This means that if you directly use f inside the closure without creating + // a separate variable (closure) and assigning the value of f to it, you might run into unexpected behavior. + // All the closures created in the loop would end up referencing the same f, which could lead to incorrect or unintended results. + // See: https://github.com/extism/go-sdk/issues/5#issuecomment-1666774486 + closure := f.stackCallback + + builder.NewFunctionBuilder().WithGoFunction(api.GoFunc(func(ctx context.Context, stack []uint64) { + if plugin, ok := ctx.Value(PluginCtxKey("plugin")).(*Plugin); ok { + closure(ctx, &CurrentPlugin{plugin}, stack) + return + } + + panic("Invalid context, `plugin` key not found") + }), f.Params, f.Returns).Export(f.Name) + } +} + +func instantiateEnvModule(ctx context.Context, rt wazero.Runtime) (api.Module, error) { + builder := rt.NewHostModuleBuilder("extism:host/env") + + // A wrapper that creates allows calls from guest -> go host -> extism kernel wasm + // See https://github.com/extism/proposals/blob/main/EIP-007-extism-runtime-kernel.md. + extismFunc := func(name string, params []ValueType, results []ValueType) { + builder. + NewFunctionBuilder(). + WithGoModuleFunction(api.GoModuleFunc(func(ctx context.Context, m api.Module, stack []uint64) { + extism, ok := ctx.Value(PluginCtxKey("extism")).(api.Module) + if !ok { + panic("Invalid context, `extism` key not found") + } + + f := extism.ExportedFunction(name) + if f == nil { + panic(fmt.Errorf("function %q not found in extism:host", name)) + } + + err := f.CallWithStack(ctx, stack) + if err != nil { + panic(err) + } + }), params, results). + Export(name) + } + + extismFunc("alloc", []ValueType{ValueTypeI64}, []ValueType{ValueTypeI64}) + extismFunc("free", []ValueType{ValueTypeI64}, []ValueType{}) + extismFunc("load_u8", []ValueType{ValueTypeI64}, []ValueType{ValueTypeI32}) + extismFunc("input_load_u8", []ValueType{ValueTypeI64}, []ValueType{ValueTypeI32}) + extismFunc("store_u64", []ValueType{ValueTypeI64, ValueTypeI64}, []ValueType{}) + extismFunc("store_u8", []ValueType{ValueTypeI64, ValueTypeI32}, []ValueType{}) + extismFunc("input_set", []ValueType{ValueTypeI64, ValueTypeI64}, []ValueType{}) + extismFunc("output_set", []ValueType{ValueTypeI64, ValueTypeI64}, []ValueType{}) + extismFunc("input_length", []ValueType{}, []ValueType{ValueTypeI64}) + extismFunc("input_offset", []ValueType{}, []ValueType{ValueTypeI64}) + extismFunc("output_length", []ValueType{}, []ValueType{ValueTypeI64}) + extismFunc("output_offset", []ValueType{}, []ValueType{ValueTypeI64}) + extismFunc("length", []ValueType{ValueTypeI64}, []ValueType{ValueTypeI64}) + extismFunc("length_unsafe", []ValueType{ValueTypeI64}, []ValueType{ValueTypeI64}) + extismFunc("reset", []ValueType{}, []ValueType{}) + extismFunc("error_set", []ValueType{ValueTypeI64}, []ValueType{}) + extismFunc("error_get", []ValueType{}, []ValueType{ValueTypeI64}) + extismFunc("memory_bytes", []ValueType{}, []ValueType{ValueTypeI64}) + + builder.NewFunctionBuilder(). + WithGoModuleFunction(api.GoModuleFunc(api.GoModuleFunc(inputLoad_u64)), []ValueType{ValueTypeI64}, []ValueType{ValueTypeI64}). + Export("input_load_u64") + + builder.NewFunctionBuilder(). + WithGoModuleFunction(api.GoModuleFunc(load_u64), []ValueType{ValueTypeI64}, []ValueType{ValueTypeI64}). + Export("load_u64") + + builder.NewFunctionBuilder(). + WithGoModuleFunction(api.GoModuleFunc(store_u64), []ValueType{ValueTypeI64, ValueTypeI64}, []ValueType{}). + Export("store_u64") + + hostFunc := func(name string, f interface{}) { + builder.NewFunctionBuilder().WithFunc(f).Export(name) + } + + hostFunc("config_get", configGet) + hostFunc("var_get", varGet) + hostFunc("var_set", varSet) + hostFunc("http_request", httpRequest) + hostFunc("http_status_code", httpStatusCode) + hostFunc("http_headers", httpHeaders) + hostFunc("get_log_level", getLogLevel) + + logFunc := func(name string, level LogLevel) { + hostFunc(name, func(ctx context.Context, m api.Module, offset uint64) { + if plugin, ok := ctx.Value(PluginCtxKey("plugin")).(*Plugin); ok { + if LogLevel(pluginLogLevel.Load()) > level { + plugin.currentPlugin().Free(offset) + return + } + + message, err := plugin.currentPlugin().ReadString(offset) + if err != nil { + panic(fmt.Errorf("failed to read log message from memory: %v", err)) + } + + plugin.Log(level, message) + + plugin.currentPlugin().Free(offset) + + return + } + + panic("Invalid context, `plugin` key not found") + }) + } + + logFunc("log_trace", LogLevelTrace) + logFunc("log_debug", LogLevelDebug) + logFunc("log_info", LogLevelInfo) + logFunc("log_warn", LogLevelWarn) + logFunc("log_error", LogLevelError) + + return builder.Instantiate(ctx) +} + +func store_u64(ctx context.Context, mod api.Module, stack []uint64) { + p, ok := ctx.Value(PluginCtxKey("plugin")).(*Plugin) + if !ok { + panic("Invalid context") + } + + offset := stack[0] + value := stack[1] + ok = p.Memory().WriteUint64Le(uint32(offset), value) + if !ok { + panic(fmt.Sprintf("could not write value '%v' at offset: %v", value, offset)) + } +} + +func load_u64(ctx context.Context, mod api.Module, stack []uint64) { + p, ok := ctx.Value(PluginCtxKey("plugin")).(*Plugin) + if !ok { + panic("Invalid context") + } + + stack[0], ok = p.Memory().ReadUint64Le(uint32(stack[0])) + if !ok { + panic(fmt.Sprintf("could not read value at offset: %v", stack[0])) + } +} + +func inputLoad_u64(ctx context.Context, mod api.Module, stack []uint64) { + p, ok := ctx.Value(PluginCtxKey("plugin")).(*Plugin) + if !ok { + panic("Invalid context") + } + + offset, ok := ctx.Value(InputOffsetKey("inputOffset")).(uint64) + if !ok { + panic("Invalid context") + } + + stack[0], ok = p.Memory().ReadUint64Le(uint32(stack[0] + offset)) + if !ok { + panic(fmt.Sprintf("could not read value at offset: %v", stack[0])) + } +} + +func configGet(ctx context.Context, m api.Module, offset uint64) uint64 { + if plugin, ok := ctx.Value(PluginCtxKey("plugin")).(*Plugin); ok { + cp := plugin.currentPlugin() + + name, err := cp.ReadString(offset) + if err != nil { + panic(fmt.Errorf("failed to read config name from memory: %v", err)) + } + + value, ok := plugin.Config[name] + if !ok { + // Return 0 without an error if key is not found + return 0 + } + + offset, err = cp.WriteString(value) + if err != nil { + panic(fmt.Errorf("failed to write config value to memory: %v", err)) + } + + return offset + } + + panic("Invalid context, `plugin` key not found") +} + +func varGet(ctx context.Context, m api.Module, offset uint64) uint64 { + if plugin, ok := ctx.Value(PluginCtxKey("plugin")).(*Plugin); ok { + cp := plugin.currentPlugin() + + name, err := cp.ReadString(offset) + if err != nil { + panic(fmt.Errorf("failed to read var name from memory: %v", err)) + } + + cp.Free(offset) + + value, ok := plugin.Var[name] + if !ok { + // Return 0 without an error if key is not found + return 0 + } + + offset, err = cp.WriteBytes(value) + if err != nil { + panic(fmt.Errorf("failed to write var value to memory: %v", err)) + } + + return offset + } + + panic("Invalid context, `plugin` key not found") +} + +func varSet(ctx context.Context, m api.Module, nameOffset uint64, valueOffset uint64) { + plugin, ok := ctx.Value(PluginCtxKey("plugin")).(*Plugin) + if !ok { + panic("Invalid context, `plugin` key not found") + } + + if plugin.MaxVarBytes == 0 { + panic("Vars are disabled by this host") + } + + cp := plugin.currentPlugin() + + name, err := cp.ReadString(nameOffset) + if err != nil { + panic(fmt.Errorf("failed to read var name from memory: %v", err)) + } + + cp.Free(nameOffset) + + // Remove if the value offset is 0 + if valueOffset == 0 { + delete(plugin.Var, name) + return + } + + value, err := cp.ReadBytes(valueOffset) + if err != nil { + panic(fmt.Errorf("failed to read var value from memory: %v", err)) + } + + cp.Free(valueOffset) + + // Calculate size including current key/value + size := int(unsafe.Sizeof([]byte{})+unsafe.Sizeof("")) + len(name) + len(value) + for k, v := range plugin.Var { + size += len(k) + size += len(v) + size += int(unsafe.Sizeof([]byte{}) + unsafe.Sizeof("")) + } + + if size >= int(plugin.MaxVarBytes) && valueOffset != 0 { + panic("Variable store is full") + } + + plugin.Var[name] = value +} + +func httpRequest(ctx context.Context, m api.Module, requestOffset uint64, bodyOffset uint64) uint64 { + if plugin, ok := ctx.Value(PluginCtxKey("plugin")).(*Plugin); ok { + cp := plugin.currentPlugin() + + if plugin.LastResponseHeaders != nil { + for k := range plugin.LastResponseHeaders { + delete(plugin.LastResponseHeaders, k) + } + } + plugin.LastStatusCode = 0 + + requestJson, err := cp.ReadBytes(requestOffset) + if err != nil { + panic(fmt.Errorf("failed to read http request from memory: %v", err)) + } + + var request HttpRequest + err = json.Unmarshal(requestJson, &request) + cp.Free(requestOffset) + if err != nil { + panic(fmt.Errorf("invalid http request: %v", err)) + } + + // default method to GET and force to be upper + if request.Method == "" { + request.Method = "GET" + } + request.Method = strings.ToUpper(request.Method) + + url, err := url.Parse(request.Url) + if err != nil { + panic(fmt.Errorf("invalid url: %v", err)) + } + + // deny all requests by default + hostMatches := false + for _, allowedHost := range plugin.AllowedHosts { + if allowedHost == url.Hostname() { + hostMatches = true + break + } + + pattern := glob.MustCompile(allowedHost) + if pattern.Match(url.Hostname()) { + hostMatches = true + break + } + } + + if !hostMatches { + panic(fmt.Errorf("HTTP request to '%v' is not allowed", request.Url)) + } + + var bodyReader io.Reader = nil + if bodyOffset != 0 { + body, err := cp.ReadBytes(bodyOffset) + if err != nil { + panic("failed to read response body from memory") + } + + cp.Free(bodyOffset) + + bodyReader = bytes.NewReader(body) + } + + req, err := http.NewRequestWithContext(ctx, request.Method, request.Url, bodyReader) + if err != nil { + panic(err) + } + + for key, value := range request.Headers { + req.Header.Set(key, value) + } + + client := http.DefaultClient + resp, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() + + if plugin.LastResponseHeaders != nil { + for k, v := range resp.Header { + plugin.LastResponseHeaders[strings.ToLower(k)] = strings.Join(v, ",") + } + } + + plugin.LastStatusCode = resp.StatusCode + + limiter := http.MaxBytesReader(nil, resp.Body, int64(plugin.MaxHttpResponseBytes)) + body, err := io.ReadAll(limiter) + if err != nil { + panic(err) + } + + if len(body) == 0 { + return 0 + } else { + offset, err := cp.WriteBytes(body) + if err != nil { + panic("Failed to write resposne body to memory") + } + + return offset + } + } + + panic("Invalid context, `plugin` key not found") +} + +func httpStatusCode(ctx context.Context, m api.Module) int32 { + if plugin, ok := ctx.Value(PluginCtxKey("plugin")).(*Plugin); ok { + return int32(plugin.LastStatusCode) + } + + panic("Invalid context, `plugin` key not found") +} + +func httpHeaders(ctx context.Context, _ api.Module) uint64 { + if plugin, ok := ctx.Value(PluginCtxKey("plugin")).(*Plugin); ok { + if plugin.LastResponseHeaders == nil { + return 0 + } + + data, err := json.Marshal(plugin.LastResponseHeaders) + if err != nil { + panic(err) + } + mem, err := plugin.currentPlugin().WriteBytes(data) + if err != nil { + panic(err) + } + return mem + } + + panic("Invalid context, `plugin` key not found") +} + +func getLogLevel(ctx context.Context, m api.Module) int32 { + // if _, ok := callCtx.Value(PluginCtxKey("plugin")).(*Plugin); ok { + // panic("Invalid context, `plugin` key not found") + // } + return LogLevel(pluginLogLevel.Load()).ExtismCompat() +} + +// EncodeI32 encodes the input as a ValueTypeI32. +func EncodeI32(input int32) uint64 { + return api.EncodeI32(input) +} + +// DecodeI32 decodes the input as a ValueTypeI32. +func DecodeI32(input uint64) int32 { + return api.DecodeI32(input) +} + +// EncodeU32 encodes the input as a ValueTypeI32. +func EncodeU32(input uint32) uint64 { + return api.EncodeU32(input) +} + +// DecodeU32 decodes the input as a ValueTypeI32. +func DecodeU32(input uint64) uint32 { + return api.DecodeU32(input) +} + +// EncodeI64 encodes the input as a ValueTypeI64. +func EncodeI64(input int64) uint64 { + return api.EncodeI64(input) +} + +// EncodeF32 encodes the input as a ValueTypeF32. +// +// See DecodeF32 +func EncodeF32(input float32) uint64 { + return api.EncodeF32(input) +} + +// DecodeF32 decodes the input as a ValueTypeF32. +// +// See EncodeF32 +func DecodeF32(input uint64) float32 { + return api.DecodeF32(input) +} + +// EncodeF64 encodes the input as a ValueTypeF64. +// +// See EncodeF32 +func EncodeF64(input float64) uint64 { + return api.EncodeF64(input) +} + +// DecodeF64 decodes the input as a ValueTypeF64. +// +// See EncodeF64 +func DecodeF64(input uint64) float64 { + return api.DecodeF64(input) +} diff --git a/vendor/github.com/extism/go-sdk/module.go b/vendor/github.com/extism/go-sdk/module.go new file mode 100644 index 00000000000..c5d928dc101 --- /dev/null +++ b/vendor/github.com/extism/go-sdk/module.go @@ -0,0 +1,30 @@ +package extism + +import "github.com/tetratelabs/wazero/api" + +// Module is a wrapper around a wazero module. It allows us to provide +// our own API and stability guarantees despite any changes that wazero +// may choose to make. +type Module struct { + inner api.Module +} + +// ExportedFunctions returns a map of functions exported from the module +// keyed by the function name. +func (m *Module) ExportedFunctions() map[string]FunctionDefinition { + v := make(map[string]FunctionDefinition) + for name, def := range m.inner.ExportedFunctionDefinitions() { + v[name] = FunctionDefinition{inner: def} + } + return v +} + +// FunctionDefinition represents a function defined in a module. It provides +// a wrapper around the underlying wazero function definition. +type FunctionDefinition struct { + inner api.FunctionDefinition +} + +func (f *FunctionDefinition) Name() string { + return f.inner.Name() +} diff --git a/vendor/github.com/extism/go-sdk/plugin.go b/vendor/github.com/extism/go-sdk/plugin.go new file mode 100644 index 00000000000..605c8453839 --- /dev/null +++ b/vendor/github.com/extism/go-sdk/plugin.go @@ -0,0 +1,421 @@ +// new +package extism + +import ( + "context" + "errors" + "fmt" + "os" + "strings" + "sync/atomic" + "time" + + observe "github.com/dylibso/observe-sdk/go" + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" +) + +type CompiledPlugin struct { + runtime wazero.Runtime + main wazero.CompiledModule + extism wazero.CompiledModule + env api.Module + modules map[string]wazero.CompiledModule + + // when a module (main) is instantiated, it may have a module name that's added + // to the data section of the wasm. If this is the case, we won't be able to + // instantiate that module more than once. This counter acts as the module name + // incrementing each time we instantiate the module. + instanceCount atomic.Uint64 + + // this is the raw wasm bytes of the provided module, it is required when using a tracing observeAdapter. + // If an adapter is not provided, this field will be nil. + wasmBytes []byte + hasWasi bool + manifest Manifest + observeAdapter *observe.AdapterBase + observeOptions *observe.Options + + maxHttp int64 + maxVar int64 + enableHttpResponseHeaders bool +} + +type PluginConfig struct { + RuntimeConfig wazero.RuntimeConfig + EnableWasi bool + ObserveAdapter *observe.AdapterBase + ObserveOptions *observe.Options + EnableHttpResponseHeaders bool + + // ModuleConfig is only used when a plugins are built using the NewPlugin + // function. In this function, the plugin is both compiled, and an instance + // of the plugin is instantiated, and the ModuleConfig is passed to the + // instance. + // + // When plugins are built using NewCompiledPlugin, the ModuleConfig has no + // effect because the instance is not created. Instead, the ModuleConfig is + // passed directly in calls to the CompiledPlugin.Instance method. + // + // NOTE: Module name and start functions are ignored as they are overridden by Extism, also if Manifest contains + // non-empty AllowedPaths, then FS is also ignored. If EXTISM_ENABLE_WASI_OUTPUT is set, then stdout and stderr are + // set to os.Stdout and os.Stderr respectively (ignoring user defined module config). + ModuleConfig wazero.ModuleConfig +} + +// NewPlugin creates compiles and instantiates a plugin that is ready +// to be used. Plugins are not thread-safe. If you need to use a plugin +// across multiple goroutines, use NewCompiledPlugin and create instances +// of the plugin using the CompiledPlugin.Instance method. +func NewPlugin( + ctx context.Context, + manifest Manifest, + config PluginConfig, + functions []HostFunction, +) (*Plugin, error) { + c, err := NewCompiledPlugin(ctx, manifest, config, functions) + if err != nil { + return nil, err + } + p, err := c.Instance(ctx, PluginInstanceConfig{ + ModuleConfig: config.ModuleConfig, + }) + if err != nil { + return nil, err + } + p.close = append(p.close, c.Close) + return p, nil +} + +func calculateMaxHttp(manifest Manifest) int64 { + // Default is 50MB + maxHttp := int64(1024 * 1024 * 50) + if manifest.Memory != nil && manifest.Memory.MaxHttpResponseBytes >= 0 { + maxHttp = manifest.Memory.MaxHttpResponseBytes + } + return maxHttp +} + +func calculateMaxVar(manifest Manifest) int64 { + // Default is 1MB + maxVar := int64(1024 * 1024) + if manifest.Memory != nil && manifest.Memory.MaxVarBytes >= 0 { + maxVar = manifest.Memory.MaxVarBytes + } + return maxVar +} + +// NewCompiledPlugin creates a compiled plugin that is ready to be instantiated. +// You can instantiate the plugin multiple times using the CompiledPlugin.Instance +// method and run those instances concurrently. +func NewCompiledPlugin( + ctx context.Context, + manifest Manifest, + config PluginConfig, + funcs []HostFunction, +) (*CompiledPlugin, error) { + count := len(manifest.Wasm) + if count == 0 { + return nil, fmt.Errorf("manifest can't be empty") + } + + runtimeConfig := config.RuntimeConfig + if runtimeConfig == nil { + runtimeConfig = wazero.NewRuntimeConfig() + } + + // Make sure function calls are cancelled if the context is cancelled + if manifest.Timeout > 0 { + runtimeConfig = runtimeConfig.WithCloseOnContextDone(true) + } + + if manifest.Memory != nil { + if manifest.Memory.MaxPages > 0 { + runtimeConfig = runtimeConfig.WithMemoryLimitPages(manifest.Memory.MaxPages) + } + } + + p := CompiledPlugin{ + manifest: manifest, + runtime: wazero.NewRuntimeWithConfig(ctx, runtimeConfig), + observeAdapter: config.ObserveAdapter, + observeOptions: config.ObserveOptions, + enableHttpResponseHeaders: config.EnableHttpResponseHeaders, + modules: make(map[string]wazero.CompiledModule), + maxHttp: calculateMaxHttp(manifest), + maxVar: calculateMaxVar(manifest), + } + + if config.EnableWasi { + wasi_snapshot_preview1.MustInstantiate(ctx, p.runtime) + p.hasWasi = true + } + + // Build host modules + hostModules := make(map[string][]HostFunction) + for _, f := range funcs { + hostModules[f.Namespace] = append(hostModules[f.Namespace], f) + } + for name, funcs := range hostModules { + _, err := buildHostModule(ctx, p.runtime, name, funcs) + if err != nil { + return nil, fmt.Errorf("building host module: %w", err) + } + } + + // Compile the extism module + var err error + p.extism, err = p.runtime.CompileModule(ctx, extismRuntimeWasm) + if err != nil { + return nil, fmt.Errorf("instantiating extism module: %w", err) + } + + // Build and instantiate extism:host/env module + p.env, err = instantiateEnvModule(ctx, p.runtime) + if err != nil { + return nil, err + } + + // Try to find the main module: + // - There is always one main module + // - If a Wasm value has the Name field set to "main" then use that module + // - If there is only one module in the manifest then that is the main module by default + // - Otherwise the last module listed is the main module + + foundMain := false + for i, wasm := range manifest.Wasm { + data, err := wasm.ToWasmData(ctx) + if err != nil { + return nil, err + } + + if (data.Name == "" || i == len(manifest.Wasm)-1) && !foundMain { + data.Name = "main" + } + + _, okm := p.modules[data.Name] + + if data.Name == "extism:host/env" || okm { + return nil, fmt.Errorf("module name collision: '%s'", data.Name) + } + + if data.Hash != "" { + calculatedHash := calculateHash(data.Data) + if data.Hash != calculatedHash { + return nil, fmt.Errorf("hash mismatch for module '%s'", data.Name) + } + } + + if p.observeAdapter != nil { + p.wasmBytes = data.Data + } + + compiledModule, err := p.runtime.CompileModule(ctx, data.Data) + if err != nil { + return nil, err + } + + if data.Name == "main" { + if foundMain { + return nil, errors.New("can't have more than one main module") + } + p.main = compiledModule + foundMain = true + } else { + // Store compiled module for instantiation + p.modules[data.Name] = compiledModule + // Create wrapper with original name that will forward calls to the actual module instance. See createModuleWrapper for more details. + _, err = createModuleWrapper(ctx, p.runtime, data.Name, compiledModule) + if err != nil { + return nil, fmt.Errorf("failed to create wrapper for %s: %w", data.Name, err) + } + } + } + + if p.main == nil { + return nil, errors.New("no main module found") + } + + // We no longer need the wasm in the manifest so nil it + // to make the slice eligible for garbage collection. + p.manifest.Wasm = nil + + return &p, nil +} + +// createModuleWrapper creates a host module that acts as a proxy for module instances. +// In Wazero, modules with the same name cannot be instantiated multiple times in the same runtime. +// However, we need each Plugin instance to have its own copy of each module for isolation. To solve this, we: +// 1. Create a host module wrapper that keeps the original module name (needed for imports to work) +// 2. Instantiate actual module copies with unique names for each Plugin +// 3. The wrapper forwards function calls to the correct module instance for each Plugin +func createModuleWrapper(ctx context.Context, rt wazero.Runtime, name string, compiled wazero.CompiledModule) (api.Module, error) { + builder := rt.NewHostModuleBuilder(name) + + // Create proxy functions for each exported function from the original module. + // These proxies will forward calls to the appropriate module instance. + for _, export := range compiled.ExportedFunctions() { + exportName := export.Name() + + // Skip wrapping the _start function since it's automatically called by wazero during instantiation. + // The wrapper functions require a Plugin instance in the context to work, but during wrapper + // instantiation there is no Plugin instance yet. + if exportName == "_start" { + continue + } + + // Create a proxy function that: + // 1. Gets the calling Plugin instance from context + // 2. Looks up that Plugin's copy of this module + // 3. Forwards the call to the actual function + wrapper := func(callCtx context.Context, mod api.Module, stack []uint64) { + // Get the Plugin instance that's making this call + plugin, ok := callCtx.Value(PluginCtxKey("plugin")).(*Plugin) + if !ok { + panic("Invalid context, `plugin` key not found") + } + + // Get this Plugin's instance of the module + actualModule, ok := plugin.modules[name] + if !ok { + panic(fmt.Sprintf("module %s not found in plugin", name)) + } + + // Forward the call to the actual module instance + fn := actualModule.ExportedFunction(exportName) + if fn == nil { + panic(fmt.Sprintf("function %s not found in module %s", exportName, name)) + } + + err := fn.CallWithStack(callCtx, stack) + if err != nil { + panic(err) + } + } + + // Export the proxy function with the same name and signature as the original + builder.NewFunctionBuilder(). + WithGoModuleFunction(api.GoModuleFunc(wrapper), export.ParamTypes(), export.ResultTypes()). + Export(exportName) + } + + return builder.Instantiate(ctx) +} + +func (p *CompiledPlugin) Close(ctx context.Context) error { + return p.runtime.Close(ctx) +} + +func (p *CompiledPlugin) Instance(ctx context.Context, config PluginInstanceConfig) (*Plugin, error) { + instanceNum := p.instanceCount.Add(1) + + var closers []func(ctx context.Context) error + + moduleConfig := config.ModuleConfig + if moduleConfig == nil { + moduleConfig = wazero.NewModuleConfig() + } + + // NOTE: we don't want wazero to call the start function, we will initialize + // the guest runtime manually. + // See: https://github.com/extism/go-sdk/pull/1#issuecomment-1650527495 + moduleConfig = moduleConfig.WithStartFunctions() + + if len(p.manifest.AllowedPaths) > 0 { + // NOTE: this is only necessary for guest modules because + // host modules have the same access privileges as the host itself + fs := wazero.NewFSConfig() + for host, guest := range p.manifest.AllowedPaths { + if strings.HasPrefix(host, "ro:") { + trimmed := strings.TrimPrefix(host, "ro:") + fs = fs.WithReadOnlyDirMount(trimmed, guest) + } else { + fs = fs.WithDirMount(host, guest) + } + } + moduleConfig = moduleConfig.WithFSConfig(fs) + } + + _, wasiOutput := os.LookupEnv("EXTISM_ENABLE_WASI_OUTPUT") + if p.hasWasi && wasiOutput { + moduleConfig = moduleConfig.WithStderr(os.Stderr).WithStdout(os.Stdout) + } + + var trace *observe.TraceCtx + var err error + if p.observeAdapter != nil { + trace, err = p.observeAdapter.NewTraceCtx(ctx, p.runtime, p.wasmBytes, p.observeOptions) + if err != nil { + return nil, fmt.Errorf("failed to initialize Observe Adapter: %v", err) + } + } + + // Compile and instantiate the extism runtime. This runtime is stateful and needs to be + // instantiated on a per-instance basis. We don't provide a name because the module needs + // to be anonymous -- you cannot instantiate multiple modules with the same name into the + // same runtime. It is okay that this is anonymous, because this module is only called + // from Go host functions and not from the Wasm module itself. + extism, err := p.runtime.InstantiateModule(ctx, p.extism, wazero.NewModuleConfig()) + if err != nil { + return nil, fmt.Errorf("instantiating extism module: %w", err) + } + + closers = append(closers, extism.Close) + + // Instantiate all non-main modules first + instancedModules := make(map[string]api.Module) + for name, compiledModule := range p.modules { + uniqueName := fmt.Sprintf("%s_%d", name, instanceNum) + instance, err := p.runtime.InstantiateModule(ctx, compiledModule, moduleConfig.WithName(uniqueName)) + if err != nil { + for _, closer := range closers { + closer(ctx) + } + return nil, fmt.Errorf("instantiating module %s: %w", name, err) + } + instancedModules[name] = instance + closers = append(closers, instance.Close) + } + + mainModuleName := fmt.Sprintf("main_%d", instanceNum) + main, err := p.runtime.InstantiateModule(ctx, p.main, moduleConfig.WithName(mainModuleName)) + if err != nil { + for _, closer := range closers { + closer(ctx) + } + + return nil, fmt.Errorf("instantiating module: %w", err) + } + + closers = append(closers, main.Close) + + var headers map[string]string = nil + if p.enableHttpResponseHeaders { + headers = map[string]string{} + } + + instance := &Plugin{ + close: closers, + extism: extism, + hasWasi: p.hasWasi, + mainModule: main, + modules: instancedModules, + Timeout: time.Duration(p.manifest.Timeout) * time.Millisecond, + Config: p.manifest.Config, + Var: make(map[string][]byte), + AllowedHosts: p.manifest.AllowedHosts, + AllowedPaths: p.manifest.AllowedPaths, + LastStatusCode: 0, + LastResponseHeaders: headers, + MaxHttpResponseBytes: p.maxHttp, + MaxVarBytes: p.maxVar, + guestRuntime: guestRuntime{}, + Adapter: p.observeAdapter, + log: logStd, + traceCtx: trace, + } + instance.guestRuntime = detectGuestRuntime(instance) + + return instance, nil +} diff --git a/vendor/github.com/extism/go-sdk/runtime.go b/vendor/github.com/extism/go-sdk/runtime.go new file mode 100644 index 00000000000..5aa4230d8d9 --- /dev/null +++ b/vendor/github.com/extism/go-sdk/runtime.go @@ -0,0 +1,195 @@ +package extism + +import ( + "context" + + "github.com/tetratelabs/wazero/api" +) + +// TODO: test runtime initialization for WASI and Haskell + +type runtimeType uint8 + +const ( + None runtimeType = iota + Haskell + Wasi +) + +type guestRuntime struct { + mainRuntime moduleRuntime + runtimes map[string]moduleRuntime + init func(ctx context.Context) error + initialized bool +} + +type moduleRuntime struct { + runtimeType runtimeType + init func(ctx context.Context) error + initialized bool +} + +// detectGuestRuntime detects the runtime of the main module and all other modules +// it returns a guest runtime with an initialization function specific that invokes +// the initialization function of all the modules, with the main module last. +func detectGuestRuntime(p *Plugin) guestRuntime { + r := guestRuntime{runtimes: make(map[string]moduleRuntime)} + + r.mainRuntime = detectModuleRuntime(p, p.mainModule) + for k, m := range p.modules { + r.runtimes[k] = detectModuleRuntime(p, m) + } + + r.init = func(ctx context.Context) error { + + for k, v := range r.runtimes { + p.Logf(LogLevelDebug, "Initializing runtime for module %v", k) + err := v.init(ctx) + if err != nil { + return err + } + v.initialized = true + } + + m := r.mainRuntime + p.Logf(LogLevelDebug, "Initializing runtime for main module") + err := m.init(ctx) + if err != nil { + return err + } + m.initialized = true + + return nil + } + + return r +} + +// detectModuleRuntime detects the specific runtime of a given module +// it returns a module runtime with an initialization function specific to that module +func detectModuleRuntime(p *Plugin, m api.Module) moduleRuntime { + runtime, ok := haskellRuntime(p, m) + if ok { + return runtime + } + + runtime, ok = wasiRuntime(p, m) + if ok { + return runtime + } + + p.Log(LogLevelTrace, "No runtime detected") + return moduleRuntime{runtimeType: None, init: func(_ context.Context) error { return nil }, initialized: true} +} + +// Check for Haskell runtime initialization functions +// Initialize Haskell runtime if `hs_init` and `hs_exit` are present, +// by calling the `hs_init` export +func haskellRuntime(p *Plugin, m api.Module) (moduleRuntime, bool) { + initFunc := m.ExportedFunction("hs_init") + if initFunc == nil { + return moduleRuntime{}, false + } + + params := initFunc.Definition().ParamTypes() + + if len(params) != 2 || params[0] != api.ValueTypeI32 || params[1] != api.ValueTypeI32 { + p.Logf(LogLevelTrace, "hs_init function found with type %v", params) + } + + reactorInit := m.ExportedFunction("_initialize") + + init := func(ctx context.Context) error { + if reactorInit != nil { + _, err := reactorInit.Call(ctx) + if err != nil { + p.Logf(LogLevelError, "Error running reactor _initialize: %s", err.Error()) + } + } + _, err := initFunc.Call(ctx, 0, 0) + if err == nil { + p.Log(LogLevelDebug, "Initialized Haskell language runtime.") + } + + return err + } + + p.Log(LogLevelTrace, "Haskell runtime detected") + return moduleRuntime{runtimeType: Haskell, init: init}, true +} + +// Check for initialization functions defined by the WASI standard +func wasiRuntime(p *Plugin, m api.Module) (moduleRuntime, bool) { + if !p.hasWasi { + return moduleRuntime{}, false + } + + // WASI supports two modules: Reactors and Commands + // we prioritize Reactors over Commands + // see: https://github.com/WebAssembly/WASI/blob/main/legacy/application-abi.md + if r, ok := reactorModule(m, p); ok { + return r, ok + } + + return commandModule(m, p) +} + +// Check for `_initialize` this is used by WASI to initialize certain interfaces. +func reactorModule(m api.Module, p *Plugin) (moduleRuntime, bool) { + init := findFunc(m, p, "_initialize") + if init == nil { + return moduleRuntime{}, false + } + + p.Logf(LogLevelTrace, "WASI runtime detected") + p.Logf(LogLevelTrace, "Reactor module detected") + + return moduleRuntime{runtimeType: Wasi, init: init}, true +} + +// Check for `__wasm__call_ctors`, this is used by WASI to +// initialize certain interfaces. +func commandModule(m api.Module, p *Plugin) (moduleRuntime, bool) { + init := findFunc(m, p, "__wasm_call_ctors") + if init == nil { + return moduleRuntime{}, false + } + + p.Logf(LogLevelTrace, "WASI runtime detected") + p.Logf(LogLevelTrace, "Command module detected") + + return moduleRuntime{runtimeType: Wasi, init: init}, true +} + +func findFunc(m api.Module, p *Plugin, name string) func(context.Context) error { + initFunc := m.ExportedFunction(name) + if initFunc == nil { + return nil + } + + params := initFunc.Definition().ParamTypes() + if len(params) != 0 { + p.Logf(LogLevelTrace, "%v function found with type %v", name, params) + return nil + } + + return func(ctx context.Context) error { + p.Logf(LogLevelDebug, "Calling %v", name) + _, err := initFunc.Call(ctx) + return err + } +} + +func equal(actual []byte, expected []byte) bool { + if len(actual) != len(expected) { + return false + } + + for i, k := range actual { + if expected[i] != k { + return false + } + } + + return true +} diff --git a/vendor/github.com/fluxcd/cli-utils/LICENSE b/vendor/github.com/fluxcd/cli-utils/LICENSE new file mode 100644 index 00000000000..8dada3edaf5 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/fluxcd/cli-utils/LICENSE_TEMPLATE b/vendor/github.com/fluxcd/cli-utils/LICENSE_TEMPLATE new file mode 100644 index 00000000000..0c2b3b6556a --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/LICENSE_TEMPLATE @@ -0,0 +1,2 @@ +Copyright {{.Year}} {{.Holder}} +SPDX-License-Identifier: Apache-2.0 diff --git a/vendor/github.com/fluxcd/cli-utils/LICENSE_TEMPLATE_GO b/vendor/github.com/fluxcd/cli-utils/LICENSE_TEMPLATE_GO new file mode 100644 index 00000000000..ef790b6616e --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/LICENSE_TEMPLATE_GO @@ -0,0 +1,2 @@ +// Copyright YEAR The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/aggregator/aggregator.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/aggregator/aggregator.go new file mode 100644 index 00000000000..a28ab0bfe9e --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/aggregator/aggregator.go @@ -0,0 +1,47 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package aggregator + +import ( + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/kstatus/status" +) + +// AggregateStatus computes the aggregate status for all the resources. +// The rules are the following: +// - If any of the resources has the FailedStatus, the aggregate status is also +// FailedStatus +// - If none of the resources have the FailedStatus and at least one is +// UnknownStatus, the aggregate status is UnknownStatus +// - If all the resources have the desired status, the aggregate status is the +// desired status. +// - If none of the first three rules apply, the aggregate status is +// InProgressStatus +func AggregateStatus(rss []*event.ResourceStatus, desired status.Status) status.Status { + if len(rss) == 0 { + return desired + } + + allDesired := true + anyUnknown := false + for _, rs := range rss { + s := rs.Status + if s == status.FailedStatus { + return status.FailedStatus + } + if s == status.UnknownStatus { + anyUnknown = true + } + if s != desired { + allDesired = false + } + } + if anyUnknown { + return status.UnknownStatus + } + if allDesired { + return desired + } + return status.InProgressStatus +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/clusterreader/caching_reader.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/clusterreader/caching_reader.go new file mode 100644 index 00000000000..019212d7ef9 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/clusterreader/caching_reader.go @@ -0,0 +1,338 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package clusterreader + +import ( + "context" + "errors" + "fmt" + "sync" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/cli-utils/pkg/object" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/tools/pager" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// This map is hard-coded knowledge that a Deployment contains and +// ReplicaSet, and that a ReplicaSet in turn contains Pods, etc., and the +// approach to finding status being used here requires hardcoding that +// knowledge in the status client library. +// TODO: These should probably be defined in the statusreaders rather than here. +var genGroupKinds = map[schema.GroupKind][]schema.GroupKind{ + schema.GroupKind{Group: "apps", Kind: "Deployment"}: { //nolint:gofmt + { + Group: "apps", + Kind: "ReplicaSet", + }, + }, + schema.GroupKind{Group: "apps", Kind: "ReplicaSet"}: { //nolint:gofmt + { + Group: "", + Kind: "Pod", + }, + }, + schema.GroupKind{Group: "apps", Kind: "StatefulSet"}: { //nolint:gofmt + { + Group: "", + Kind: "Pod", + }, + }, +} + +// NewCachingClusterReader returns a new instance of the ClusterReader. The +// ClusterReader needs will use the clusterreader to fetch resources from the cluster, +// while the mapper is used to resolve the version for GroupKinds. The set of +// identifiers is needed so the ClusterReader can figure out which GroupKind +// and namespace combinations it needs to cache when the Sync function is called. +// We only want to fetch the resources that are actually needed. +func NewCachingClusterReader(reader client.Reader, mapper meta.RESTMapper, identifiers object.ObjMetadataSet) (engine.ClusterReader, error) { + gvkNamespaceSet := newGnSet() + for _, id := range identifiers { + // For every identifier, add the GroupVersionKind and namespace combination to the gvkNamespaceSet and + // check the genGroupKinds map for any generated resources that also should be included. + err := buildGvkNamespaceSet([]schema.GroupKind{id.GroupKind}, id.Namespace, gvkNamespaceSet) + if err != nil { + return nil, err + } + } + + return &CachingClusterReader{ + reader: reader, + mapper: mapper, + gns: gvkNamespaceSet.gvkNamespaces, + }, nil +} + +func buildGvkNamespaceSet(gks []schema.GroupKind, namespace string, gvkNamespaceSet *gvkNamespaceSet) error { + for _, gk := range gks { + gvkNamespaceSet.add(gkNamespace{ + GroupKind: gk, + Namespace: namespace, + }) + genGKs, found := genGroupKinds[gk] + if found { + err := buildGvkNamespaceSet(genGKs, namespace, gvkNamespaceSet) + if err != nil { + return err + } + } + } + return nil +} + +type gvkNamespaceSet struct { + gvkNamespaces []gkNamespace + seen map[gkNamespace]struct{} +} + +func newGnSet() *gvkNamespaceSet { + return &gvkNamespaceSet{ + seen: make(map[gkNamespace]struct{}), + } +} + +func (g *gvkNamespaceSet) add(gn gkNamespace) { + if _, found := g.seen[gn]; !found { + g.gvkNamespaces = append(g.gvkNamespaces, gn) + g.seen[gn] = struct{}{} + } +} + +// CachingClusterReader is an implementation of the ObserverReader interface that will +// pre-fetch all resources needed before every sync loop. The resources needed are decided by +// finding all combinations of GroupVersionKind and namespace referenced by the provided +// identifiers. This list is then expanded to include any known generated resource types. +type CachingClusterReader struct { + mx sync.RWMutex + + // clusterreader provides functions to read and list resources from the + // cluster. + reader client.Reader + + // mapper is the client-side representation of the server-side scheme. It is used + // to resolve GroupVersionKind from GroupKind. + mapper meta.RESTMapper + + // gns contains the slice of all the GVK and namespace combinations that + // should be included in the cache. This is computed based the resource identifiers + // passed in when the CachingClusterReader is created and augmented with other + // resource types needed to compute status (see genGroupKinds). + gns []gkNamespace + + // cache contains the resources found in the cluster for the given combination + // of GVK and namespace. Before each polling cycle, the framework will call the + // Sync function, which is responsible for repopulating the cache. + cache map[gkNamespace]cacheEntry +} + +type cacheEntry struct { + resources unstructured.UnstructuredList + err error +} + +// gkNamespace contains information about a GroupVersionKind and a namespace. +type gkNamespace struct { + GroupKind schema.GroupKind + Namespace string +} + +// Get looks up the resource identified by the key and the object GVK in the cache. If the needed combination +// of GVK and namespace is not part of the cache, that is considered an error. +func (c *CachingClusterReader) Get(_ context.Context, key client.ObjectKey, obj *unstructured.Unstructured) error { + c.mx.RLock() + defer c.mx.RUnlock() + gvk := obj.GetObjectKind().GroupVersionKind() + mapping, err := c.mapper.RESTMapping(gvk.GroupKind()) + if err != nil { + return err + } + gn := gkNamespace{ + GroupKind: gvk.GroupKind(), + Namespace: key.Namespace, + } + cacheEntry, found := c.cache[gn] + if !found { + return fmt.Errorf("GVK %s and Namespace %s not found in cache", gvk.String(), gn.Namespace) + } + + if cacheEntry.err != nil { + return cacheEntry.err + } + for _, u := range cacheEntry.resources.Items { + if u.GetName() == key.Name { + obj.Object = u.Object + return nil + } + } + return apierrors.NewNotFound(mapping.Resource.GroupResource(), key.Name) +} + +// ListNamespaceScoped lists all resource identifier by the GVK of the list, the namespace and the selector +// from the cache. If the needed combination of GVK and namespace is not part of the cache, that is considered an error. +func (c *CachingClusterReader) ListNamespaceScoped(_ context.Context, list *unstructured.UnstructuredList, namespace string, selector labels.Selector) error { + c.mx.RLock() + defer c.mx.RUnlock() + gvk := list.GroupVersionKind() + gn := gkNamespace{ + GroupKind: gvk.GroupKind(), + Namespace: namespace, + } + + cacheEntry, found := c.cache[gn] + if !found { + return fmt.Errorf("GVK %s and Namespace %s not found in cache", gvk.String(), gn.Namespace) + } + + if cacheEntry.err != nil { + return cacheEntry.err + } + + var items []unstructured.Unstructured + for _, u := range cacheEntry.resources.Items { + if selector.Matches(labels.Set(u.GetLabels())) { + items = append(items, u) + } + } + list.Items = items + return nil +} + +// ListClusterScoped lists all resource identifier by the GVK of the list and selector +// from the cache. If the needed combination of GVK and namespace (which for clusterscoped resources +// will always be the empty string) is not part of the cache, that is considered an error. +func (c *CachingClusterReader) ListClusterScoped(ctx context.Context, list *unstructured.UnstructuredList, selector labels.Selector) error { + return c.ListNamespaceScoped(ctx, list, "", selector) +} + +// Sync loops over the list of gkNamespace we know of, and uses list calls to fetch the resources. +// This information populates the cache. +func (c *CachingClusterReader) Sync(ctx context.Context) error { + c.mx.Lock() + defer c.mx.Unlock() + cache := make(map[gkNamespace]cacheEntry) + for _, gn := range c.gns { + mapping, err := c.mapper.RESTMapping(gn.GroupKind) + if err != nil { + if meta.IsNoMatchError(err) { + // If we get a NoMatchError, it means we are checking for + // a type that doesn't exist. Presumably the CRD is being + // applied, so it will be added. Reset the RESTMapper to + // make sure we pick up any new resource types on the + // APIServer. + cache[gn] = cacheEntry{ + err: err, + } + continue + } + return err + } + ns := "" + if mapping.Scope == meta.RESTScopeNamespace { + ns = gn.Namespace + } + list, err := c.listUnstructured(ctx, mapping.GroupVersionKind, ns) + if err != nil { + // If the context was cancelled, we just stop the work and return + // the error. + if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { + return err + } + // For other errors, we just keep it the error. Whenever any pollers + // request a resource covered by this gns, we just return the + // error. + cache[gn] = cacheEntry{ + err: err, + } + continue + } + cache[gn] = cacheEntry{ + resources: *list, + } + } + c.cache = cache + return nil +} + +// listUnstructured performs one or more LIST calls, paginating the requests +// and aggregating the results. If aggregated, only the ResourceVersion, +// SelfLink, and Items will be populated. The default page size is 500. +func (c *CachingClusterReader) listUnstructured( + ctx context.Context, + gvk schema.GroupVersionKind, + namespace string, +) (*unstructured.UnstructuredList, error) { + mOpts := metav1.ListOptions{} + mOpts.SetGroupVersionKind(gvk) + obj, _, err := pager.New(c.listPageFunc(namespace)).List(ctx, mOpts) + if err != nil { + return nil, err + } + + switch t := obj.(type) { + case *unstructured.UnstructuredList: + // all in one + return t, nil + case *metainternalversion.List: + // aggregated result + u := &unstructured.UnstructuredList{} + u.SetGroupVersionKind(gvk) + // Only ResourceVersion & SelfLink are copied into the aggregated result + // by ListPager. + if t.ResourceVersion != "" { + u.SetResourceVersion(t.ResourceVersion) + } + if t.SelfLink != "" { // nolint:staticcheck + u.SetSelfLink(t.SelfLink) // nolint:staticcheck + } + u.Items = make([]unstructured.Unstructured, len(t.Items)) + for i, item := range t.Items { + ui, ok := item.(*unstructured.Unstructured) + if !ok { + return nil, fmt.Errorf("unexpected list item type: %t", item) + } + u.Items[i] = *ui + } + return u, nil + default: + return nil, fmt.Errorf("unexpected list type: %t", t) + } +} + +func (c *CachingClusterReader) listPageFunc(namespace string) pager.ListPageFunc { + return func(ctx context.Context, mOpts metav1.ListOptions) (runtime.Object, error) { + mOptsCopy := mOpts + labelSelector, err := labels.Parse(mOpts.LabelSelector) + if err != nil { + return nil, fmt.Errorf("failed to parse label selector: %w", err) + } + fieldSelector, err := fields.ParseSelector(mOpts.FieldSelector) + if err != nil { + return nil, fmt.Errorf("failed to parse field selector: %w", err) + } + cOpts := &client.ListOptions{ + LabelSelector: labelSelector, + FieldSelector: fieldSelector, + Namespace: namespace, + Limit: mOpts.Limit, + Continue: mOpts.Continue, + Raw: &mOptsCopy, + } + var list unstructured.UnstructuredList + list.SetGroupVersionKind(mOpts.GroupVersionKind()) + // Note: client.ListOptions only supports Exact ResourceVersion matching. + // So leave ResourceVersion blank to get Any ResourceVersion. + err = c.reader.List(ctx, &list, cOpts) + return &list, err + } +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/clusterreader/direct_reader.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/clusterreader/direct_reader.go new file mode 100644 index 00000000000..c94dab28b58 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/clusterreader/direct_reader.go @@ -0,0 +1,45 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package clusterreader + +import ( + "context" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/cli-utils/pkg/object" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// NewDirectClusterReader creates a new implementation of the engine.ClusterReader interface that makes +// calls directly to the cluster without any caching. +func NewDirectClusterReader(reader client.Reader, _ meta.RESTMapper, _ object.ObjMetadataSet) (engine.ClusterReader, error) { + return &DirectClusterReader{ + Reader: reader, + }, nil +} + +// DirectClusterReader is an implementation of the ClusterReader that just delegates all calls directly to +// the underlying clusterreader. No caching. +type DirectClusterReader struct { + Reader client.Reader +} + +func (n *DirectClusterReader) Get(ctx context.Context, key client.ObjectKey, obj *unstructured.Unstructured) error { + return n.Reader.Get(ctx, key, obj) +} + +func (n *DirectClusterReader) ListNamespaceScoped(ctx context.Context, list *unstructured.UnstructuredList, namespace string, selector labels.Selector) error { + return n.Reader.List(ctx, list, client.InNamespace(namespace), client.MatchingLabelsSelector{Selector: selector}) +} + +func (n *DirectClusterReader) ListClusterScoped(ctx context.Context, list *unstructured.UnstructuredList, selector labels.Selector) error { + return n.Reader.List(ctx, list, client.MatchingLabelsSelector{Selector: selector}) +} + +func (n *DirectClusterReader) Sync(_ context.Context) error { + return nil +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/clusterreader/dynamic_reader.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/clusterreader/dynamic_reader.go new file mode 100644 index 00000000000..b5827514e58 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/clusterreader/dynamic_reader.go @@ -0,0 +1,81 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package clusterreader + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/dynamic" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// DynamicClusterReader is an implementation of the ClusterReader that delegates +// all calls directly to the underlying DynamicClient. No caching. +type DynamicClusterReader struct { + DynamicClient dynamic.Interface + Mapper meta.RESTMapper +} + +func (n *DynamicClusterReader) Get(ctx context.Context, key client.ObjectKey, obj *unstructured.Unstructured) error { + mapping, err := n.Mapper.RESTMapping(obj.GroupVersionKind().GroupKind()) + if err != nil { + return fmt.Errorf("failed to map object: %w", err) + } + + serverObj, err := n.DynamicClient.Resource(mapping.Resource). + Namespace(key.Namespace). + Get(ctx, key.Name, metav1.GetOptions{}) + if err != nil { + return err + } + + serverObj.DeepCopyInto(obj) + return nil +} + +func (n *DynamicClusterReader) ListNamespaceScoped(ctx context.Context, list *unstructured.UnstructuredList, namespace string, selector labels.Selector) error { + mapping, err := n.Mapper.RESTMapping(list.GroupVersionKind().GroupKind()) + if err != nil { + return fmt.Errorf("failed to map object: %w", err) + } + + serverObj, err := n.DynamicClient.Resource(mapping.Resource). + Namespace(namespace). + List(ctx, metav1.ListOptions{ + LabelSelector: selector.String(), + }) + if err != nil { + return err + } + + serverObj.DeepCopyInto(list) + return nil +} + +func (n *DynamicClusterReader) ListClusterScoped(ctx context.Context, list *unstructured.UnstructuredList, selector labels.Selector) error { + mapping, err := n.Mapper.RESTMapping(list.GroupVersionKind().GroupKind()) + if err != nil { + return fmt.Errorf("failed to map object: %w", err) + } + + serverObj, err := n.DynamicClient.Resource(mapping.Resource). + List(ctx, metav1.ListOptions{ + LabelSelector: selector.String(), + }) + if err != nil { + return err + } + + serverObj.DeepCopyInto(list) + return nil +} + +func (n *DynamicClusterReader) Sync(_ context.Context) error { + return nil +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/collector/collector.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/collector/collector.go new file mode 100644 index 00000000000..1fe8e175578 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/collector/collector.go @@ -0,0 +1,141 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package collector + +import ( + "sort" + "sync" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/kstatus/status" + "github.com/fluxcd/cli-utils/pkg/object" +) + +func NewResourceStatusCollector(identifiers object.ObjMetadataSet) *ResourceStatusCollector { + resourceStatuses := make(map[object.ObjMetadata]*event.ResourceStatus) + for _, id := range identifiers { + resourceStatuses[id] = &event.ResourceStatus{ + Identifier: id, + Status: status.UnknownStatus, + } + } + return &ResourceStatusCollector{ + ResourceStatuses: resourceStatuses, + } +} + +// Observer is an interface that can be implemented to have the +// ResourceStatusCollector invoke the function on every event that +// comes through the eventChannel. +// The callback happens in the processing goroutine and while the +// goroutine holds the lock, so any processing in the callback +// must be done quickly. +type Observer interface { + Notify(*ResourceStatusCollector, event.Event) +} + +// ObserverFunc is a function implementation of the Observer +// interface. +type ObserverFunc func(*ResourceStatusCollector, event.Event) + +func (o ObserverFunc) Notify(rsc *ResourceStatusCollector, e event.Event) { + o(rsc, e) +} + +// ResourceStatusCollector is for use by clients of the polling library and provides +// a way to keep track of the latest status/state for all the polled resources. The collector +// is set up to listen to the eventChannel and keep the latest event for each resource. It also +// provides a way to fetch the latest state for all resources and the aggregated status at any point. +// The functions already handles synchronization so it can be used by multiple goroutines. +type ResourceStatusCollector struct { + mux sync.RWMutex + + LastEventType event.Type + + ResourceStatuses map[object.ObjMetadata]*event.ResourceStatus + + Error error +} + +// ListenerResult is the type of the object passed back to the caller to +// Listen and ListenWithObserver if a fatal error has been encountered. +type ListenerResult struct { + Err error +} + +// Listen kicks off the goroutine that will listen for the events on the +// eventChannel. It returns a channel that will be closed the collector stops +// listening to the eventChannel. +func (o *ResourceStatusCollector) Listen(eventChannel <-chan event.Event) <-chan ListenerResult { + return o.ListenWithObserver(eventChannel, nil) +} + +// ListenWithObserver kicks off the goroutine that will listen for the events on the +// eventChannel. It returns a channel that will be closed the collector stops +// listening to the eventChannel. +// The provided observer will be invoked on every event, after the event +// has been processed. +func (o *ResourceStatusCollector) ListenWithObserver(eventChannel <-chan event.Event, + observer Observer) <-chan ListenerResult { + completed := make(chan ListenerResult) + go func() { + defer close(completed) + for e := range eventChannel { + err := o.processEvent(e) + if err != nil { + completed <- ListenerResult{ + Err: err, + } + } + if observer != nil { + observer.Notify(o, e) + } + } + }() + return completed +} + +func (o *ResourceStatusCollector) processEvent(e event.Event) error { + o.mux.Lock() + defer o.mux.Unlock() + o.LastEventType = e.Type + if e.Type == event.ErrorEvent { + o.Error = e.Error + return e.Error + } + if e.Type == event.ResourceUpdateEvent { + resourceStatus := e.Resource + o.ResourceStatuses[resourceStatus.Identifier] = resourceStatus + } + return nil +} + +// Observation contains the latest state known by the collector as returned +// by a call to the LatestObservation function. +type Observation struct { + LastEventType event.Type + + ResourceStatuses []*event.ResourceStatus + + Error error +} + +// LatestObservation returns an Observation instance, which contains the +// latest information about the resources known by the collector. +func (o *ResourceStatusCollector) LatestObservation() *Observation { + o.mux.RLock() + defer o.mux.RUnlock() + + var resourceStatuses event.ResourceStatuses + for _, resourceStatus := range o.ResourceStatuses { + resourceStatuses = append(resourceStatuses, resourceStatus) + } + sort.Sort(resourceStatuses) + + return &Observation{ + LastEventType: o.LastEventType, + ResourceStatuses: resourceStatuses, + Error: o.Error, + } +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine/engine.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine/engine.go new file mode 100644 index 00000000000..f02b7785908 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine/engine.go @@ -0,0 +1,257 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package engine + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/object" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// ClusterReaderFactory provides an interface that can be implemented to provide custom +// ClusterReader implementations in the StatusPoller. +type ClusterReaderFactory interface { + New(reader client.Reader, mapper meta.RESTMapper, identifiers object.ObjMetadataSet) (ClusterReader, error) +} + +type ClusterReaderFactoryFunc func(client.Reader, meta.RESTMapper, object.ObjMetadataSet) (ClusterReader, error) + +func (c ClusterReaderFactoryFunc) New(r client.Reader, m meta.RESTMapper, ids object.ObjMetadataSet) (ClusterReader, error) { + return c(r, m, ids) +} + +// PollerEngine provides functionality for polling a cluster for status of a set of resources. +type PollerEngine struct { + Reader client.Reader + Mapper meta.RESTMapper + StatusReaders []StatusReader + DefaultStatusReader StatusReader + ClusterReaderFactory ClusterReaderFactory +} + +// Poll will create a new statusPollerRunner that will poll all the resources provided and report their status +// back on the event channel returned. The statusPollerRunner can be cancelled at any time by cancelling the +// context passed in. +// The context can be used to stop the polling process by using timeout, deadline or +// cancellation. +func (s *PollerEngine) Poll(ctx context.Context, identifiers object.ObjMetadataSet, options Options) <-chan event.Event { + eventChannel := make(chan event.Event) + + go func() { + defer close(eventChannel) + + err := s.validateIdentifiers(identifiers) + if err != nil { + handleError(eventChannel, err) + return + } + + clusterReader, err := s.ClusterReaderFactory.New(s.Reader, s.Mapper, identifiers) + if err != nil { + handleError(eventChannel, fmt.Errorf("error creating new ClusterReader: %w", err)) + return + } + + runner := &statusPollerRunner{ + clusterReader: clusterReader, + statusReaders: s.StatusReaders, + defaultStatusReader: s.DefaultStatusReader, + identifiers: identifiers, + previousResourceStatuses: make(map[object.ObjMetadata]*event.ResourceStatus), + eventChannel: eventChannel, + pollingInterval: options.PollInterval, + } + runner.Run(ctx) + }() + + return eventChannel +} + +func handleError(eventChannel chan event.Event, err error) { + eventChannel <- event.Event{ + Type: event.ErrorEvent, + Error: err, + } +} + +// validateIdentifiers makes sure that all namespaced resources +// passed in +func (s *PollerEngine) validateIdentifiers(identifiers object.ObjMetadataSet) error { + for _, id := range identifiers { + mapping, err := s.Mapper.RESTMapping(id.GroupKind) + if err != nil { + // If we can't find a match, just keep going. This can happen + // if CRDs and CRs are applied at the same time. + if meta.IsNoMatchError(err) { + continue + } + return err + } + if mapping.Scope.Name() == meta.RESTScopeNameNamespace && id.Namespace == "" { + return fmt.Errorf("resource %s %s is namespace scoped, but namespace is not set", + id.GroupKind.String(), id.Name) + } + } + return nil +} + +// Options contains the different parameters that can be used to adjust the +// behavior of the PollerEngine. +// Timeout is not one of the options here as this should be accomplished by +// setting a timeout on the context: https://golang.org/pkg/context/ +type Options struct { + + // PollInterval defines how often the PollerEngine should poll the cluster for the latest + // state of the resources. + PollInterval time.Duration +} + +// statusPollerRunner is responsible for polling of a set of resources. Each call to Poll will create +// a new statusPollerRunner, which means we can keep state in the runner and all data will only be accessed +// by a single goroutine, meaning we don't need synchronization. +// The statusPollerRunner uses an implementation of the ClusterReader interface to talk to the +// kubernetes cluster. Currently this can be either the cached ClusterReader that syncs all needed resources +// with LIST calls before each polling loop, or the normal ClusterReader that just forwards each call +// to the client.Reader from controller-runtime. +type statusPollerRunner struct { + // clusterReader is the interface for fetching and listing resources from the cluster. It can be implemented + // to make call directly to the cluster or use caching to reduce the number of calls to the cluster. + clusterReader ClusterReader + + // statusReaders contains the resource specific statusReaders. These will contain logic for how to + // compute status for specific GroupKinds. These will use an ClusterReader to fetch + // status of a resource and any generated resources. + statusReaders []StatusReader + + // defaultStatusReader is the generic engine that is used for all GroupKinds that + // doesn't have a specific engine in the statusReaders map. + defaultStatusReader StatusReader + + // identifiers contains the set of identifiers for the resources that should be polled. + // Each resource is identified by GroupKind, namespace and name. + identifiers object.ObjMetadataSet + + // previousResourceStatuses keeps track of the last event for each + // of the polled resources. This is used to make sure we only + // send events on the event channel when something has actually changed. + previousResourceStatuses map[object.ObjMetadata]*event.ResourceStatus + + // eventChannel is a channel where any updates to the status of resources + // will be sent. The caller of Poll will listen for updates. + eventChannel chan event.Event + + // pollingInterval determines how often we should poll the cluster for + // the latest state of resources. + pollingInterval time.Duration +} + +// Run starts the polling loop of the statusReaders. +func (r *statusPollerRunner) Run(ctx context.Context) { + // Sets up ticker that will trigger the regular polling loop at a regular interval. + ticker := time.NewTicker(r.pollingInterval) + defer func() { + ticker.Stop() + }() + + err := r.syncAndPoll(ctx) + if err != nil { + r.handleSyncAndPollErr(err) + return + } + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + // First sync and then compute status for all resources. + err := r.syncAndPoll(ctx) + if err != nil { + r.handleSyncAndPollErr(err) + return + } + } + } +} + +// handleSyncAndPollErr decides what to do if we encounter an error while +// fetching resources to compute status. Errors are usually returned +// as an ErrorEvent, but we handle context cancellation or deadline exceeded +// differently since they aren't really errors, but a signal that the +// process should shut down. +func (r *statusPollerRunner) handleSyncAndPollErr(err error) { + if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { + return + } + r.eventChannel <- event.Event{ + Type: event.ErrorEvent, + Error: err, + } +} + +func (r *statusPollerRunner) syncAndPoll(ctx context.Context) error { + // First trigger a sync of the ClusterReader. This may or may not actually + // result in calls to the cluster, depending on the implementation. + // If this call fails, there is no clean way to recover, so we just return an ErrorEvent + // and shut down. + err := r.clusterReader.Sync(ctx) + if err != nil { + return err + } + // Poll all resources and compute status. If the polling of resources has completed (based + // on information from the StatusAggregator and the value of pollUntilCancelled), we send + // a CompletedEvent and return. + return r.pollStatusForAllResources(ctx) +} + +// pollStatusForAllResources iterates over all the resources in the set and delegates +// to the appropriate engine to compute the status. +func (r *statusPollerRunner) pollStatusForAllResources(ctx context.Context) error { + for _, id := range r.identifiers { + // Check if the context has been cancelled on every iteration. + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + gk := id.GroupKind + statusReader := r.statusReaderForGroupKind(gk) + resourceStatus, err := statusReader.ReadStatus(ctx, r.clusterReader, id) + if err != nil { + return err + } + if r.isUpdatedResourceStatus(resourceStatus) { + r.previousResourceStatuses[id] = resourceStatus + r.eventChannel <- event.Event{ + Type: event.ResourceUpdateEvent, + Resource: resourceStatus, + } + } + } + return nil +} + +func (r *statusPollerRunner) statusReaderForGroupKind(gk schema.GroupKind) StatusReader { + for _, sr := range r.statusReaders { + if sr.Supports(gk) { + return sr + } + } + return r.defaultStatusReader +} + +func (r *statusPollerRunner) isUpdatedResourceStatus(resourceStatus *event.ResourceStatus) bool { + oldResourceStatus, found := r.previousResourceStatuses[resourceStatus.Identifier] + if !found { + return true + } + return !event.ResourceStatusEqual(resourceStatus, oldResourceStatus) +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine/reader.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine/reader.go new file mode 100644 index 00000000000..7bcfcac8e15 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine/reader.go @@ -0,0 +1,32 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package engine + +import ( + "context" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// ClusterReader is the interface provided to the statusReaders to talk to the cluster. Implementations +// of this interface allows different caching strategies, for example by pre-fetching resources using +// LIST calls rather than letting each engine run multiple GET calls against the cluster. This can +// significantly reduce the number of requests. +type ClusterReader interface { + // Get looks up the resource identifier by the key and the GVK in the provided obj reference. If something + // goes wrong or the resource doesn't exist, an error is returned. + Get(ctx context.Context, key client.ObjectKey, obj *unstructured.Unstructured) error + // ListNamespaceScoped looks up the resources of the GVK given in the list and matches the namespace and + // selector provided. + ListNamespaceScoped(ctx context.Context, list *unstructured.UnstructuredList, + namespace string, selector labels.Selector) error + // ListClusterScoped looks up the resources of the GVK given in the list and that matches the selector + // provided. + ListClusterScoped(ctx context.Context, list *unstructured.UnstructuredList, selector labels.Selector) error + // Sync is called by the engine before every polling loop, which provides an opportunity for the Reader + // to sync caches. + Sync(ctx context.Context) error +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine/status_reader.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine/status_reader.go new file mode 100644 index 00000000000..11eba055528 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine/status_reader.go @@ -0,0 +1,43 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package engine + +import ( + "context" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/object" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// StatusReader is the main interface for computing status for resources. In this context, +// a status reader is an object that can fetch a resource of a specific +// GroupKind from the cluster and compute its status. For resources that +// can own generated resources, the engine might also have knowledge about +// how to identify these generated resources and how to compute status for +// these generated resources. +type StatusReader interface { + // Supports tells the caller whether the StatusReader can compute status for + // the provided GroupKind. + Supports(schema.GroupKind) bool + + // ReadStatus will fetch the resource identified by the given identifier + // from the cluster and return an ResourceStatus that will contain + // information about the latest state of the resource, its computed status + // and information about any generated resources. Errors would usually be + // added to the event.ResourceStatus, but in the case of fatal errors + // that aren't connected to the particular resource, an error can also + // be returned. Currently, only context cancellation and deadline exceeded + // will cause an error to be returned. + ReadStatus(ctx context.Context, reader ClusterReader, resource object.ObjMetadata) (*event.ResourceStatus, error) + + // ReadStatusForObject is similar to ReadStatus, but instead of looking up the + // resource based on an identifier, it will use the passed-in resource. + // Errors would usually be added to the event.ResourceStatus, but in the case + // of fatal errors that aren't connected to the particular resource, an error + // can also be returned. Currently, only context cancellation and deadline exceeded + // will cause an error to be returned. + ReadStatusForObject(ctx context.Context, reader ClusterReader, object *unstructured.Unstructured) (*event.ResourceStatus, error) +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/event/event.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/event/event.go new file mode 100644 index 00000000000..835a9e7c730 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/event/event.go @@ -0,0 +1,165 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package event + +import ( + "fmt" + + "github.com/fluxcd/cli-utils/pkg/kstatus/status" + "github.com/fluxcd/cli-utils/pkg/object" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +// Type is the type that describes the type of an Event that is passed back to the caller +// as resources in the cluster are being polled. +// +//go:generate stringer -type=Type -linecomment +type Type int + +const ( + // ResourceUpdateEvent describes events related to a change in the status of one of the polled resources. + ResourceUpdateEvent Type = iota // Update + // ErrorEvent signals that the engine has encountered an error that it can not recover from. The engine + // is shutting down and the event channel will be closed after this event. + ErrorEvent // Error + // SyncEvent signals that the engine has completed its initial + // synchronization, and the cache is primed. After this point, it's safe to + // assume that you won't miss events caused by your own subsequent actions. + SyncEvent // Sync +) + +// Event defines that type that is passed back through the event channel to notify the caller of changes +// as resources are being polled. +type Event struct { + // Type defines the type of event. + Type Type + + // Resource is only available for ResourceUpdateEvents. It includes information about the resource, + // including the resource status, any errors and the resource itself (as an unstructured). + Resource *ResourceStatus + + // Error is only available for ErrorEvents. It contains the error that caused the engine to + // give up. + Error error +} + +// String returns a string suitable for logging +func (e Event) String() string { + if e.Error != nil { + return fmt.Sprintf("Event{ Type: %q, Resource: %v, Error: %q }", + e.Type, e.Resource, e.Error) + } + return fmt.Sprintf("Event{ Type: %q, Resource: %v }", + e.Type, e.Resource) +} + +// ResourceStatus contains information about a resource after we have +// fetched it from the cluster and computed status. +type ResourceStatus struct { + // Identifier contains the information necessary to locate the + // resource within a cluster. + Identifier object.ObjMetadata + + // Status is the computed status for this resource. + Status status.Status + + // Resource contains the actual manifest for the resource that + // was fetched from the cluster and used to compute status. + Resource *unstructured.Unstructured + + // Errors contains the error if something went wrong during the + // process of fetching the resource and computing the status. + Error error + + // Message is text describing the status of the resource. + Message string + + // GeneratedResources is a slice of ResourceStatus that + // contains information and status for any generated resources + // of the current resource. + GeneratedResources ResourceStatuses +} + +// String returns a string suitable for logging +func (rs ResourceStatus) String() string { + if rs.Error != nil { + return fmt.Sprintf("ResourceStatus{ Identifier: %q, Status: %q, Message: %q, Resource: %v, GeneratedResources: %v, Error: %q }", + rs.Identifier, rs.Status, rs.Message, rs.Resource, rs.GeneratedResources, rs.Error) + } + return fmt.Sprintf("ResourceStatus{ Identifier: %q, Status: %q, Message: %q, Resource: %v, GeneratedResources: %v }", + rs.Identifier, rs.Status, rs.Message, rs.Resource, rs.GeneratedResources) +} + +type ResourceStatuses []*ResourceStatus + +func (g ResourceStatuses) Len() int { + return len(g) +} + +func (g ResourceStatuses) Less(i, j int) bool { + idI := g[i].Identifier + idJ := g[j].Identifier + + if idI.Namespace != idJ.Namespace { + return idI.Namespace < idJ.Namespace + } + if idI.GroupKind.Group != idJ.GroupKind.Group { + return idI.GroupKind.Group < idJ.GroupKind.Group + } + if idI.GroupKind.Kind != idJ.GroupKind.Kind { + return idI.GroupKind.Kind < idJ.GroupKind.Kind + } + return idI.Name < idJ.Name +} + +func (g ResourceStatuses) Swap(i, j int) { + g[i], g[j] = g[j], g[i] +} + +// ResourceStatusEqual checks if two instances of ResourceStatus are the same. +// This is used to determine whether status has changed for a particular resource. +// Important to note that this does not check all fields, but only the ones +// that are considered part of the status for a resource. So if the status +// or the message of an ResourceStatus (or any of its generated ResourceStatuses) +// have changed, this will return true. Changes to the state of the resource +// itself that doesn't impact status are not considered. +func ResourceStatusEqual(or1, or2 *ResourceStatus) bool { + if or1.Identifier != or2.Identifier || + or1.Status != or2.Status || + or1.Message != or2.Message { + return false + } + + // Check if generation has changed to make sure that even if + // an update to a resource doesn't affect the status, a status event + // will still be sent. + if getGeneration(or1) != getGeneration(or2) { + return false + } + + if or1.Error != nil && or2.Error != nil && or1.Error.Error() != or2.Error.Error() { + return false + } + if (or1.Error == nil && or2.Error != nil) || (or1.Error != nil && or2.Error == nil) { + return false + } + + if len(or1.GeneratedResources) != len(or2.GeneratedResources) { + return false + } + + for i := range or1.GeneratedResources { + if !ResourceStatusEqual(or1.GeneratedResources[i], or2.GeneratedResources[i]) { + return false + } + } + return true +} + +func getGeneration(r *ResourceStatus) int64 { + if r.Resource == nil { + return 0 + } + return r.Resource.GetGeneration() +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/event/type_string.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/event/type_string.go new file mode 100644 index 00000000000..eef6a210d79 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/event/type_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -type=Type -linecomment"; DO NOT EDIT. + +package event + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ResourceUpdateEvent-0] + _ = x[ErrorEvent-1] + _ = x[SyncEvent-2] +} + +const _Type_name = "UpdateErrorSync" + +var _Type_index = [...]uint8{0, 6, 11, 15} + +func (i Type) String() string { + if i < 0 || i >= Type(len(_Type_index)-1) { + return "Type(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Type_name[_Type_index[i]:_Type_index[i+1]] +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/common.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/common.go new file mode 100644 index 00000000000..db4e904d99a --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/common.go @@ -0,0 +1,228 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package statusreaders + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "sort" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/kstatus/status" + "github.com/fluxcd/cli-utils/pkg/object" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" +) + +// baseStatusReader is the implementation of the StatusReader interface defined +// in the engine package. It contains the basic logic needed for every resource. +// In order to handle resource specific logic, it must include an implementation +// of the resourceTypeStatusReader interface. +// In practice we will create many instances of baseStatusReader, each with a different +// implementation of the resourceTypeStatusReader interface and therefore each +// of the instances will be able to handle different resource types. +type baseStatusReader struct { + // mapper provides a way to look up the resource types that are available + // in the cluster. + mapper meta.RESTMapper + + // resourceStatusReader is an resource-type specific implementation + // of the resourceTypeStatusReader interface. While the baseStatusReader + // contains the logic shared between all resource types, this implementation + // will contain the resource specific info. + resourceStatusReader resourceTypeStatusReader +} + +// resourceTypeStatusReader is an interface that can be implemented differently +// for each resource type. +type resourceTypeStatusReader interface { + Supports(gk schema.GroupKind) bool + ReadStatusForObject(ctx context.Context, reader engine.ClusterReader, object *unstructured.Unstructured) (*event.ResourceStatus, error) +} + +func (b *baseStatusReader) Supports(gk schema.GroupKind) bool { + return b.resourceStatusReader.Supports(gk) +} + +// ReadStatus reads the object identified by the passed-in identifier and computes it's status. It reads +// the resource here, but computing status is delegated to the ReadStatusForObject function. +func (b *baseStatusReader) ReadStatus(ctx context.Context, reader engine.ClusterReader, identifier object.ObjMetadata) (*event.ResourceStatus, error) { + object, err := b.lookupResource(ctx, reader, identifier) + if err != nil { + return errIdentifierToResourceStatus(err, identifier) + } + return b.resourceStatusReader.ReadStatusForObject(ctx, reader, object) +} + +// ReadStatusForObject computes the status for the passed-in object. Since this is specific for each +// resource type, the actual work is delegated to the implementation of the resourceTypeStatusReader interface. +func (b *baseStatusReader) ReadStatusForObject(ctx context.Context, reader engine.ClusterReader, object *unstructured.Unstructured) (*event.ResourceStatus, error) { + return b.resourceStatusReader.ReadStatusForObject(ctx, reader, object) +} + +// lookupResource looks up a resource with the given identifier. It will use the rest mapper to resolve +// the version of the GroupKind given in the identifier. +// If the resource is found, it is returned. If it is not found or something +// went wrong, the function will return an error. +func (b *baseStatusReader) lookupResource(ctx context.Context, reader engine.ClusterReader, identifier object.ObjMetadata) (*unstructured.Unstructured, error) { + GVK, err := gvk(identifier.GroupKind, b.mapper) + if err != nil { + return nil, err + } + + var u unstructured.Unstructured + u.SetGroupVersionKind(GVK) + key := types.NamespacedName{ + Name: identifier.Name, + Namespace: identifier.Namespace, + } + err = reader.Get(ctx, key, &u) + if err != nil { + return nil, err + } + return &u, nil +} + +// statusForGenResourcesFunc defines the function type used by the statusForGeneratedResource function. +// TODO: Find a better solution for this. Maybe put the logic for looking up generated resources +// into a separate type. +type statusForGenResourcesFunc func(ctx context.Context, mapper meta.RESTMapper, reader engine.ClusterReader, statusReader resourceTypeStatusReader, + object *unstructured.Unstructured, gk schema.GroupKind, selectorPath ...string) (event.ResourceStatuses, error) + +// statusForGeneratedResources provides a way to fetch the statuses for all resources of a given GroupKind +// that match the selector in the provided resource. Typically, this is used to fetch the status of generated +// resources. +func statusForGeneratedResources(ctx context.Context, mapper meta.RESTMapper, reader engine.ClusterReader, statusReader resourceTypeStatusReader, + object *unstructured.Unstructured, gk schema.GroupKind, selectorPath ...string) (event.ResourceStatuses, error) { + selector, err := toSelector(object, selectorPath...) + if err != nil { + return event.ResourceStatuses{}, err + } + + var objectList unstructured.UnstructuredList + gvk, err := gvk(gk, mapper) + if err != nil { + return event.ResourceStatuses{}, err + } + objectList.SetGroupVersionKind(gvk) + err = reader.ListNamespaceScoped(ctx, &objectList, object.GetNamespace(), selector) + if err != nil { + return event.ResourceStatuses{}, err + } + + var resourceStatuses event.ResourceStatuses + for i := range objectList.Items { + generatedObject := objectList.Items[i] + resourceStatus, err := statusReader.ReadStatusForObject(ctx, reader, &generatedObject) + if err != nil { + return event.ResourceStatuses{}, err + } + resourceStatuses = append(resourceStatuses, resourceStatus) + } + sort.Sort(resourceStatuses) + return resourceStatuses, nil +} + +// gvk looks up the GVK from a GroupKind using the rest mapper. +func gvk(gk schema.GroupKind, mapper meta.RESTMapper) (schema.GroupVersionKind, error) { + mapping, err := mapper.RESTMapping(gk) + if err != nil { + return schema.GroupVersionKind{}, err + } + return mapping.GroupVersionKind, nil +} + +func toSelector(resource *unstructured.Unstructured, path ...string) (labels.Selector, error) { + selector, found, err := unstructured.NestedMap(resource.Object, path...) + if err != nil { + return nil, err + } + if !found { + return nil, fmt.Errorf("no selector found") + } + bytes, err := json.Marshal(selector) + if err != nil { + return nil, err + } + var s metav1.LabelSelector + err = json.Unmarshal(bytes, &s) + if err != nil { + return nil, err + } + return metav1.LabelSelectorAsSelector(&s) +} + +// errResourceToResourceStatus construct the appropriate ResourceStatus +// object based on an error and the resource itself. +func errResourceToResourceStatus(err error, resource *unstructured.Unstructured, genResources ...*event.ResourceStatus) (*event.ResourceStatus, error) { + // If the error is from the context, we don't attach that to the ResourceStatus, + // but just return it directly so the caller can decide how to handle this + // situation. + if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) || isRateLimiterContextDeadlineExceeded(err) { + return nil, err + } + identifier := object.UnstructuredToObjMetadata(resource) + if apierrors.IsNotFound(err) { + return &event.ResourceStatus{ + Identifier: identifier, + Status: status.NotFoundStatus, + Message: "Resource not found", + }, nil + } + return &event.ResourceStatus{ + Identifier: identifier, + Status: status.UnknownStatus, + Resource: resource, + Error: err, + GeneratedResources: genResources, + }, nil +} + +// errIdentifierToResourceStatus construct the appropriate ResourceStatus +// object based on an error and the identifier for a resource. +func errIdentifierToResourceStatus(err error, identifier object.ObjMetadata) (*event.ResourceStatus, error) { + // If the error is from the context, we don't attach that to the ResourceStatus, + // but just return it directly so the caller can decide how to handle this + // situation. + if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) || isRateLimiterContextDeadlineExceeded(err) { + return nil, err + } + if apierrors.IsNotFound(err) { + return &event.ResourceStatus{ + Identifier: identifier, + Status: status.NotFoundStatus, + Message: "Resource not found", + }, nil + } + return &event.ResourceStatus{ + Identifier: identifier, + Status: status.UnknownStatus, + Error: err, + }, nil +} + +// isRateLimiterContextDeadlineExceeded checks if the error is a rate limiter "would exceed context deadline" error +// this allows us to treat it the same way as the context.Canceled and context.DeadlineExceeded errors +// instead of attaching the error to the ResourceStatus, caller can decide how to handle this +func isRateLimiterContextDeadlineExceeded(err error) bool { + for { + next := errors.Unwrap(err) + if next == nil { + break + } + err = next + } + + // there's no dedicated error type for this, hence we check the error message + // https://cs.opensource.google/go/x/time/+/refs/tags/v0.10.0:rate/rate.go;l=276 + return err != nil && err.Error() == "rate: Wait(n=1) would exceed context deadline" +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/default.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/default.go new file mode 100644 index 00000000000..11eeb89911e --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/default.go @@ -0,0 +1,86 @@ +// Copyright 2021 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package statusreaders + +import ( + "context" + "fmt" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/kstatus/status" + "github.com/fluxcd/cli-utils/pkg/object" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// NewDefaultStatusReader returns a DelegatingStatusReader that wraps a list of +// statusreaders to cover all built-in Kubernetes resources and other CRDs that +// follow known status conventions. +func NewDefaultStatusReader(mapper meta.RESTMapper) engine.StatusReader { + return NewStatusReader(mapper) +} + +// NewStatusReader returns a DelegatingStatusReader that includes the statusreaders +// for the build-in Kubernetes resources and also any provided custom status readers. +func NewStatusReader(mapper meta.RESTMapper, statusReaders ...engine.StatusReader) engine.StatusReader { + defaultStatusReader := NewGenericStatusReader(mapper, status.Compute) + + replicaSetStatusReader := NewReplicaSetStatusReader(mapper, defaultStatusReader) + deploymentStatusReader := NewDeploymentResourceReader(mapper, replicaSetStatusReader) + statefulSetStatusReader := NewStatefulSetResourceReader(mapper, defaultStatusReader) + + statusReaders = append(statusReaders, + deploymentStatusReader, + statefulSetStatusReader, + replicaSetStatusReader, + defaultStatusReader, + ) + + return &DelegatingStatusReader{ + StatusReaders: statusReaders, + } +} + +type DelegatingStatusReader struct { + StatusReaders []engine.StatusReader +} + +func (dsr *DelegatingStatusReader) Supports(gk schema.GroupKind) bool { + for _, sr := range dsr.StatusReaders { + if sr.Supports(gk) { + return true + } + } + return false +} + +func (dsr *DelegatingStatusReader) ReadStatus( + ctx context.Context, + reader engine.ClusterReader, + id object.ObjMetadata, +) (*event.ResourceStatus, error) { + gk := id.GroupKind + for _, sr := range dsr.StatusReaders { + if sr.Supports(gk) { + return sr.ReadStatus(ctx, reader, id) + } + } + return nil, fmt.Errorf("no status reader supports this resource: %v", gk) +} + +func (dsr *DelegatingStatusReader) ReadStatusForObject( + ctx context.Context, + reader engine.ClusterReader, + obj *unstructured.Unstructured, +) (*event.ResourceStatus, error) { + gk := obj.GroupVersionKind().GroupKind() + for _, sr := range dsr.StatusReaders { + if sr.Supports(gk) { + return sr.ReadStatusForObject(ctx, reader, obj) + } + } + return nil, fmt.Errorf("no status reader supports this resource: %v", gk) +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/deployment.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/deployment.go new file mode 100644 index 00000000000..6234792a960 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/deployment.go @@ -0,0 +1,72 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package statusreaders + +import ( + "context" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/kstatus/status" + "github.com/fluxcd/cli-utils/pkg/object" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func NewDeploymentResourceReader(mapper meta.RESTMapper, rsStatusReader resourceTypeStatusReader) engine.StatusReader { + return &baseStatusReader{ + mapper: mapper, + resourceStatusReader: &deploymentResourceReader{ + mapper: mapper, + rsStatusReader: rsStatusReader, + }, + } +} + +// deploymentResourceReader is a resourceTypeStatusReader that can fetch Deployment +// resources from the cluster, knows how to find any ReplicaSets belonging to the +// Deployment, and compute status for the deployment. +type deploymentResourceReader struct { + mapper meta.RESTMapper + + // rsStatusReader is the implementation of the resourceTypeStatusReader + // the knows how to compute the status for ReplicaSets. + rsStatusReader resourceTypeStatusReader +} + +var _ resourceTypeStatusReader = &deploymentResourceReader{} + +func (d *deploymentResourceReader) Supports(gk schema.GroupKind) bool { + return gk == appsv1.SchemeGroupVersion.WithKind("Deployment").GroupKind() +} + +func (d *deploymentResourceReader) ReadStatusForObject(ctx context.Context, reader engine.ClusterReader, + deployment *unstructured.Unstructured) (*event.ResourceStatus, error) { + identifier := object.UnstructuredToObjMetadata(deployment) + + replicaSetStatuses, err := statusForGeneratedResources(ctx, d.mapper, reader, d.rsStatusReader, deployment, + appsv1.SchemeGroupVersion.WithKind("ReplicaSet").GroupKind(), "spec", "selector") + if err != nil { + return errResourceToResourceStatus(err, deployment) + } + + // Currently this engine just uses the status library for computing + // status for the deployment. But we do have the status and state for all + // ReplicaSets and Pods in the ObservedReplicaSets data structure, so the + // rules can be improved to take advantage of this information. + res, err := status.Compute(deployment) + if err != nil { + return errResourceToResourceStatus(err, deployment, replicaSetStatuses...) + } + + return &event.ResourceStatus{ + Identifier: identifier, + Status: res.Status, + Resource: deployment, + Message: res.Message, + GeneratedResources: replicaSetStatuses, + }, nil +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/generic.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/generic.go new file mode 100644 index 00000000000..239a150b0d6 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/generic.go @@ -0,0 +1,65 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package statusreaders + +import ( + "context" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/kstatus/status" + "github.com/fluxcd/cli-utils/pkg/object" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// StatusFunc returns the status of the given object. This func is passed into +// NewGenericStatusReader so that the returned StatusReader can be used for custom types. +// An example of a StatusFunc is status.Compute. +type StatusFunc func(u *unstructured.Unstructured) (*status.Result, error) + +func NewGenericStatusReader(mapper meta.RESTMapper, statusFunc StatusFunc) engine.StatusReader { + return &baseStatusReader{ + mapper: mapper, + resourceStatusReader: &genericStatusReader{ + mapper: mapper, + statusFunc: statusFunc, + }, + } +} + +// genericStatusReader is a resourceTypeStatusReader that will be used for +// any resource that doesn't have a specific engine. It will just delegate +// computation of status to the status library. +// This should work pretty well for resources that doesn't have any +// generated resources and where status can be computed only based on the +// resource itself. +type genericStatusReader struct { + mapper meta.RESTMapper + + statusFunc StatusFunc +} + +var _ resourceTypeStatusReader = &genericStatusReader{} + +func (g *genericStatusReader) Supports(schema.GroupKind) bool { + return true +} + +func (g *genericStatusReader) ReadStatusForObject(_ context.Context, _ engine.ClusterReader, resource *unstructured.Unstructured) (*event.ResourceStatus, error) { + identifier := object.UnstructuredToObjMetadata(resource) + + res, err := g.statusFunc(resource) + if err != nil { + return errResourceToResourceStatus(err, resource) + } + + return &event.ResourceStatus{ + Identifier: identifier, + Status: res.Status, + Resource: resource, + Message: res.Message, + }, nil +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/pod_controller.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/pod_controller.go new file mode 100644 index 00000000000..5e333d280e4 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/pod_controller.go @@ -0,0 +1,140 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package statusreaders + +import ( + "context" + "fmt" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/kstatus/status" + "github.com/fluxcd/cli-utils/pkg/object" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func newPodControllerStatusReader(mapper meta.RESTMapper, podStatusReader resourceTypeStatusReader) *podControllerStatusReader { + return &podControllerStatusReader{ + mapper: mapper, + podStatusReader: podStatusReader, + groupKind: schema.GroupKind{ + Group: "", + Kind: "Pod", + }, + statusFunc: status.Compute, + statusForGenResourcesFunc: statusForGeneratedResources, + } +} + +// podControllerStatusReader encapsulates the logic needed to compute the status +// for resource types that act as controllers for pods. This is quite common, so +// the logic is here instead of duplicated in each resource specific StatusReader. +type podControllerStatusReader struct { + mapper meta.RESTMapper + podStatusReader resourceTypeStatusReader + groupKind schema.GroupKind + + statusFunc func(u *unstructured.Unstructured) (*status.Result, error) + // TODO(mortent): See if we can avoid this. For now it is useful for testing. + statusForGenResourcesFunc statusForGenResourcesFunc +} + +func (p *podControllerStatusReader) readStatus(ctx context.Context, reader engine.ClusterReader, obj *unstructured.Unstructured) (*event.ResourceStatus, error) { + identifier := object.UnstructuredToObjMetadata(obj) + + podResourceStatuses, err := p.statusForGenResourcesFunc(ctx, p.mapper, reader, p.podStatusReader, obj, + p.groupKind, "spec", "selector") + if err != nil { + return errResourceToResourceStatus(err, obj) + } + + res, err := p.statusFunc(obj) + if err != nil { + return errResourceToResourceStatus(err, obj, podResourceStatuses...) + } + + // If the status comes back as pending, we take a look at the pods to make sure + // none of them have terminally failed. Pods that are pending scheduling are + // excluded, as this is a transient state that cluster autoscalers can resolve. + // Pods that are being deleted (e.g. during a rolling update) are also excluded. + if res.Status == status.InProgressStatus { + var failedPods []*event.ResourceStatus + for _, podResourceStatus := range podResourceStatuses { + if podResourceStatus.Status == status.FailedStatus { + if isTransientPodFailure(podResourceStatus) { + continue + } + failedPods = append(failedPods, podResourceStatus) + } + } + if len(failedPods) > 0 { + return &event.ResourceStatus{ + Identifier: identifier, + Status: status.FailedStatus, + Resource: obj, + Message: fmt.Sprintf("%d pods have failed", len(failedPods)), + GeneratedResources: podResourceStatuses, + }, nil + } + } + + return &event.ResourceStatus{ + Identifier: identifier, + Status: res.Status, + Resource: obj, + Message: res.Message, + GeneratedResources: podResourceStatuses, + }, nil +} + +// isTransientPodFailure returns true if the pod's failure is likely transient +// and should not cause the parent controller to be marked as failed. This +// includes pods that are pending scheduling (which an autoscaler may resolve) +// and pods that are being deleted (during a rolling update). +func isTransientPodFailure(podStatus *event.ResourceStatus) bool { + pod := podStatus.Resource + if pod == nil { + // If the resource is not available, we cannot determine whether the + // failure is transient. Treat it as transient to avoid prematurely + // marking the parent controller as failed. + return true + } + + // Pods being deleted are expected during rolling updates. + if pod.GetDeletionTimestamp() != nil { + return true + } + + // Pods that are pending scheduling due to insufficient resources are + // transient failures that a cluster autoscaler can resolve. + if isPodUnschedulable(pod) { + return true + } + + return false +} + +// isPodUnschedulable returns true if the object is a pod with a PodScheduled +// condition indicating it is Unschedulable. +func isPodUnschedulable(obj *unstructured.Unstructured) bool { + gk := obj.GroupVersionKind().GroupKind() + if gk != (schema.GroupKind{Kind: "Pod"}) { + return false + } + objWithConditions, err := status.GetObjectWithConditions(obj.Object) + if err != nil { + return false + } + for _, cond := range objWithConditions.Status.Conditions { + if cond.Type == string(corev1.PodScheduled) && + cond.Status == corev1.ConditionFalse && + cond.Reason == corev1.PodReasonUnschedulable { + return true + } + } + return false +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/replicaset.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/replicaset.go new file mode 100644 index 00000000000..d65e9930572 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/replicaset.go @@ -0,0 +1,44 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package statusreaders + +import ( + "context" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func NewReplicaSetStatusReader(mapper meta.RESTMapper, podStatusReader resourceTypeStatusReader) engine.StatusReader { + return &baseStatusReader{ + mapper: mapper, + resourceStatusReader: &replicaSetStatusReader{ + mapper: mapper, + podStatusReader: podStatusReader, + }, + } +} + +// replicaSetStatusReader is an engine that can fetch ReplicaSet resources +// from the cluster, knows how to find any Pods belonging to the ReplicaSet, +// and compute status for the ReplicaSet. +type replicaSetStatusReader struct { + mapper meta.RESTMapper + + podStatusReader resourceTypeStatusReader +} + +var _ resourceTypeStatusReader = &replicaSetStatusReader{} + +func (r *replicaSetStatusReader) Supports(gk schema.GroupKind) bool { + return gk == appsv1.SchemeGroupVersion.WithKind("ReplicaSet").GroupKind() +} + +func (r *replicaSetStatusReader) ReadStatusForObject(ctx context.Context, reader engine.ClusterReader, rs *unstructured.Unstructured) (*event.ResourceStatus, error) { + return newPodControllerStatusReader(r.mapper, r.podStatusReader).readStatus(ctx, reader, rs) +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/statefulset.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/statefulset.go new file mode 100644 index 00000000000..4b578a523a4 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders/statefulset.go @@ -0,0 +1,45 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package statusreaders + +import ( + "context" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func NewStatefulSetResourceReader(mapper meta.RESTMapper, podResourceReader resourceTypeStatusReader) engine.StatusReader { + return &baseStatusReader{ + mapper: mapper, + resourceStatusReader: &statefulSetResourceReader{ + mapper: mapper, + podResourceReader: podResourceReader, + }, + } +} + +// statefulSetResourceReader is an implementation of the ResourceReader interface +// that can fetch StatefulSet resources from the cluster, knows how to find any +// Pods belonging to the StatefulSet, and compute status for the StatefulSet. +type statefulSetResourceReader struct { + mapper meta.RESTMapper + + podResourceReader resourceTypeStatusReader +} + +var _ resourceTypeStatusReader = &statefulSetResourceReader{} + +func (s *statefulSetResourceReader) Supports(gk schema.GroupKind) bool { + return gk == appsv1.SchemeGroupVersion.WithKind("StatefulSet").GroupKind() +} + +func (s *statefulSetResourceReader) ReadStatusForObject(ctx context.Context, reader engine.ClusterReader, + statefulSet *unstructured.Unstructured) (*event.ResourceStatus, error) { + return newPodControllerStatusReader(s.mapper, s.podResourceReader).readStatus(ctx, reader, statefulSet) +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/status/core.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/status/core.go new file mode 100644 index 00000000000..4cd6704e408 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/status/core.go @@ -0,0 +1,627 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package status + +import ( + "fmt" + "math" + "strings" + "time" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +// GetConditionsFn defines the signature for functions to compute the +// status of a built-in resource. +type GetConditionsFn func(*unstructured.Unstructured) (*Result, error) + +// legacyTypes defines the mapping from GroupKind to a function that can +// compute the status for the given resource. +var legacyTypes = map[string]GetConditionsFn{ + "Service": serviceConditions, + "Pod": podConditions, + "Secret": alwaysReady, + "PersistentVolumeClaim": pvcConditions, + "apps/StatefulSet": stsConditions, + "apps/DaemonSet": daemonsetConditions, + "extensions/DaemonSet": daemonsetConditions, + "apps/Deployment": deploymentConditions, + "extensions/Deployment": deploymentConditions, + "apps/ReplicaSet": replicasetConditions, + "extensions/ReplicaSet": replicasetConditions, + "policy/PodDisruptionBudget": pdbConditions, + "batch/CronJob": alwaysReady, + "ConfigMap": alwaysReady, + "batch/Job": jobConditions, + "apiextensions.k8s.io/CustomResourceDefinition": crdConditions, +} + +const ( + tooFewReady = "LessReady" + tooFewAvailable = "LessAvailable" + tooFewUpdated = "LessUpdated" + tooFewReplicas = "LessReplicas" + extraPods = "ExtraPods" + + onDeleteUpdateStrategy = "OnDelete" + + // How long a pod can be unscheduled before it is reported as + // unschedulable. + ScheduleWindow = 15 * time.Second +) + +// GetLegacyConditionsFn returns a function that can compute the status for the +// given resource, or nil if the resource type is not known. +func GetLegacyConditionsFn(u *unstructured.Unstructured) GetConditionsFn { + gvk := u.GroupVersionKind() + g := gvk.Group + k := gvk.Kind + key := g + "/" + k + if g == "" { + key = k + } + return legacyTypes[key] +} + +// alwaysReady Used for resources that are always ready +func alwaysReady(u *unstructured.Unstructured) (*Result, error) { + return &Result{ + Status: CurrentStatus, + Message: "Resource is always ready", + Conditions: []Condition{}, + }, nil +} + +// stsConditions return standardized Conditions for Statefulset +// +// StatefulSet does define the .status.conditions property, but the controller never +// actually sets any Conditions. Thus, status must be computed only based on the other +// properties under .status. We don't have any way to find out if a reconcile for a +// StatefulSet has failed. +func stsConditions(u *unstructured.Unstructured) (*Result, error) { + obj := u.UnstructuredContent() + + // updateStrategy==ondelete is a user managed statefulset. + updateStrategy := GetStringField(obj, ".spec.updateStrategy.type", "") + if updateStrategy == onDeleteUpdateStrategy { + return &Result{ + Status: CurrentStatus, + Message: "StatefulSet is using the ondelete update strategy", + Conditions: []Condition{}, + }, nil + } + + // Replicas + specReplicas := GetIntField(obj, ".spec.replicas", 1) + readyReplicas := GetIntField(obj, ".status.readyReplicas", 0) + currentReplicas := GetIntField(obj, ".status.currentReplicas", 0) + updatedReplicas := GetIntField(obj, ".status.updatedReplicas", 0) + statusReplicas := GetIntField(obj, ".status.replicas", 0) + partition := GetIntField(obj, ".spec.updateStrategy.rollingUpdate.partition", -1) + + if specReplicas > statusReplicas { + message := fmt.Sprintf("Replicas: %d/%d", statusReplicas, specReplicas) + return newInProgressStatus(tooFewReplicas, message), nil + } + + if specReplicas > readyReplicas { + message := fmt.Sprintf("Ready: %d/%d", readyReplicas, specReplicas) + return newInProgressStatus(tooFewReady, message), nil + } + + if statusReplicas > specReplicas { + message := fmt.Sprintf("Pending termination: %d", statusReplicas-specReplicas) + return newInProgressStatus(extraPods, message), nil + } + + // https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions + if partition != -1 { + if updatedReplicas < (specReplicas - partition) { + message := fmt.Sprintf("updated: %d/%d", updatedReplicas, specReplicas-partition) + return newInProgressStatus("PartitionRollout", message), nil + } + // Partition case All ok + return &Result{ + Status: CurrentStatus, + Message: fmt.Sprintf("Partition rollout complete. updated: %d", updatedReplicas), + Conditions: []Condition{}, + }, nil + } + + if specReplicas > currentReplicas { + message := fmt.Sprintf("current: %d/%d", currentReplicas, specReplicas) + return newInProgressStatus("LessCurrent", message), nil + } + + // Revision + currentRevision := GetStringField(obj, ".status.currentRevision", "") + updatedRevision := GetStringField(obj, ".status.updateRevision", "") + if currentRevision != updatedRevision { + message := "Waiting for updated revision to match current" + return newInProgressStatus("RevisionMismatch", message), nil + } + + // All ok + return &Result{ + Status: CurrentStatus, + Message: fmt.Sprintf("All replicas scheduled as expected. Replicas: %d", statusReplicas), + Conditions: []Condition{}, + }, nil +} + +// deploymentConditions return standardized Conditions for Deployment. +// +// For Deployments, we look at .status.conditions as well as the other properties +// under .status. Status will be Failed if the progress deadline has been exceeded. +func deploymentConditions(u *unstructured.Unstructured) (*Result, error) { + obj := u.UnstructuredContent() + + progressing := false + + // Check if progressDeadlineSeconds is set. If not, the controller will not set + // the `Progressing` condition, so it will always consider a deployment to be + // progressing. The use of math.MaxInt32 is due to special handling in the + // controller: + // https://github.com/kubernetes/kubernetes/blob/a3ccea9d8743f2ff82e41b6c2af6dc2c41dc7b10/pkg/controller/deployment/util/deployment_util.go#L886 + progressDeadline := GetIntField(obj, ".spec.progressDeadlineSeconds", math.MaxInt32) + if progressDeadline == math.MaxInt32 { + progressing = true + } + + available := false + + objc, err := GetObjectWithConditions(obj) + if err != nil { + return nil, err + } + + for _, c := range objc.Status.Conditions { + switch c.Type { + case "Progressing": // appsv1.DeploymentProgressing: + // https://github.com/kubernetes/kubernetes/blob/a3ccea9d8743f2ff82e41b6c2af6dc2c41dc7b10/pkg/controller/deployment/progress.go#L52 + if c.Reason == "ProgressDeadlineExceeded" { + return &Result{ + Status: FailedStatus, + Message: "Progress deadline exceeded", + Conditions: []Condition{{ConditionStalled, corev1.ConditionTrue, c.Reason, c.Message}}, + }, nil + } + if c.Status == corev1.ConditionTrue && c.Reason == "NewReplicaSetAvailable" { + progressing = true + } + case "Available": // appsv1.DeploymentAvailable: + if c.Status == corev1.ConditionTrue { + available = true + } + } + } + + // replicas + specReplicas := GetIntField(obj, ".spec.replicas", 1) // Controller uses 1 as default if not specified. + statusReplicas := GetIntField(obj, ".status.replicas", 0) + updatedReplicas := GetIntField(obj, ".status.updatedReplicas", 0) + readyReplicas := GetIntField(obj, ".status.readyReplicas", 0) + availableReplicas := GetIntField(obj, ".status.availableReplicas", 0) + + // TODO spec.replicas zero case ?? + + if specReplicas > statusReplicas { + message := fmt.Sprintf("Replicas: %d/%d", statusReplicas, specReplicas) + return newInProgressStatus(tooFewReplicas, message), nil + } + + if specReplicas > updatedReplicas { + message := fmt.Sprintf("Updated: %d/%d", updatedReplicas, specReplicas) + return newInProgressStatus(tooFewUpdated, message), nil + } + + if statusReplicas > specReplicas { + message := fmt.Sprintf("Pending termination: %d", statusReplicas-specReplicas) + return newInProgressStatus(extraPods, message), nil + } + + if updatedReplicas > availableReplicas { + message := fmt.Sprintf("Available: %d/%d", availableReplicas, updatedReplicas) + return newInProgressStatus(tooFewAvailable, message), nil + } + + if specReplicas > readyReplicas { + message := fmt.Sprintf("Ready: %d/%d", readyReplicas, specReplicas) + return newInProgressStatus(tooFewReady, message), nil + } + + // check conditions + if !progressing { + message := "ReplicaSet not Available" + return newInProgressStatus("ReplicaSetNotAvailable", message), nil + } + if !available { + message := "Deployment not Available" + return newInProgressStatus("DeploymentNotAvailable", message), nil + } + // All ok + return &Result{ + Status: CurrentStatus, + Message: fmt.Sprintf("Deployment is available. Replicas: %d", statusReplicas), + Conditions: []Condition{}, + }, nil +} + +// replicasetConditions return standardized Conditions for Replicaset +func replicasetConditions(u *unstructured.Unstructured) (*Result, error) { + obj := u.UnstructuredContent() + + // Conditions + objc, err := GetObjectWithConditions(obj) + if err != nil { + return nil, err + } + + for _, c := range objc.Status.Conditions { + // https://github.com/kubernetes/kubernetes/blob/a3ccea9d8743f2ff82e41b6c2af6dc2c41dc7b10/pkg/controller/replicaset/replica_set_utils.go + if c.Type == "ReplicaFailure" && c.Status == corev1.ConditionTrue { + message := "Replica Failure condition. Check Pods" + return newInProgressStatus("ReplicaFailure", message), nil + } + } + + // Replicas + specReplicas := GetIntField(obj, ".spec.replicas", 1) // Controller uses 1 as default if not specified. + statusReplicas := GetIntField(obj, ".status.replicas", 0) + readyReplicas := GetIntField(obj, ".status.readyReplicas", 0) + availableReplicas := GetIntField(obj, ".status.availableReplicas", 0) + fullyLabelledReplicas := GetIntField(obj, ".status.fullyLabeledReplicas", 0) + + if specReplicas > fullyLabelledReplicas { + message := fmt.Sprintf("Labelled: %d/%d", fullyLabelledReplicas, specReplicas) + return newInProgressStatus("LessLabelled", message), nil + } + + if specReplicas > availableReplicas { + message := fmt.Sprintf("Available: %d/%d", availableReplicas, specReplicas) + return newInProgressStatus(tooFewAvailable, message), nil + } + + if specReplicas > readyReplicas { + message := fmt.Sprintf("Ready: %d/%d", readyReplicas, specReplicas) + return newInProgressStatus(tooFewReady, message), nil + } + + if statusReplicas > specReplicas { + message := fmt.Sprintf("Pending termination: %d", statusReplicas-specReplicas) + return newInProgressStatus(extraPods, message), nil + } + // All ok + return &Result{ + Status: CurrentStatus, + Message: fmt.Sprintf("ReplicaSet is available. Replicas: %d", statusReplicas), + Conditions: []Condition{}, + }, nil +} + +// daemonsetConditions return standardized Conditions for DaemonSet +func daemonsetConditions(u *unstructured.Unstructured) (*Result, error) { + // We check that the latest generation is equal to observed generation as + // part of checking generic properties but in that case, we are lenient and + // skip the check if those fields are unset. For daemonset, we know that if + // the daemonset controller has acted on a resource, these fields would not + // be unset. So, we ensure that here. + res, err := checkGenerationSet(u) + if err != nil || res != nil { + return res, err + } + + obj := u.UnstructuredContent() + + // replicas + desiredNumberScheduled := GetIntField(obj, ".status.desiredNumberScheduled", -1) + currentNumberScheduled := GetIntField(obj, ".status.currentNumberScheduled", 0) + updatedNumberScheduled := GetIntField(obj, ".status.updatedNumberScheduled", 0) + numberAvailable := GetIntField(obj, ".status.numberAvailable", 0) + numberReady := GetIntField(obj, ".status.numberReady", 0) + + if desiredNumberScheduled == -1 { + message := "Missing .status.desiredNumberScheduled" + return newInProgressStatus("NoDesiredNumber", message), nil + } + + if desiredNumberScheduled > currentNumberScheduled { + message := fmt.Sprintf("Current: %d/%d", currentNumberScheduled, desiredNumberScheduled) + return newInProgressStatus("LessCurrent", message), nil + } + + if desiredNumberScheduled > updatedNumberScheduled { + message := fmt.Sprintf("Updated: %d/%d", updatedNumberScheduled, desiredNumberScheduled) + return newInProgressStatus(tooFewUpdated, message), nil + } + + if desiredNumberScheduled > numberAvailable { + message := fmt.Sprintf("Available: %d/%d", numberAvailable, desiredNumberScheduled) + return newInProgressStatus(tooFewAvailable, message), nil + } + + if desiredNumberScheduled > numberReady { + message := fmt.Sprintf("Ready: %d/%d", numberReady, desiredNumberScheduled) + return newInProgressStatus(tooFewReady, message), nil + } + + // All ok + return &Result{ + Status: CurrentStatus, + Message: fmt.Sprintf("All replicas scheduled as expected. Replicas: %d", desiredNumberScheduled), + Conditions: []Condition{}, + }, nil +} + +// checkGenerationSet checks that the metadata.generation and +// status.observedGeneration fields are set. +func checkGenerationSet(u *unstructured.Unstructured) (*Result, error) { + _, found, err := unstructured.NestedInt64(u.Object, "metadata", "generation") + if err != nil { + return nil, fmt.Errorf("looking up metadata.generation from resource: %w", err) + } + if !found { + message := fmt.Sprintf("%s metadata.generation not found", u.GetKind()) + return &Result{ + Status: InProgressStatus, + Message: message, + Conditions: []Condition{newReconcilingCondition("NoGeneration", message)}, + }, nil + } + + _, found, err = unstructured.NestedInt64(u.Object, "status", "observedGeneration") + if err != nil { + return nil, fmt.Errorf("looking up status.observedGeneration from resource: %w", err) + } + if !found { + message := fmt.Sprintf("%s status.observedGeneration not found", u.GetKind()) + return &Result{ + Status: InProgressStatus, + Message: message, + Conditions: []Condition{newReconcilingCondition("NoObservedGeneration", message)}, + }, nil + } + + return nil, nil +} + +// pvcConditions return standardized Conditions for PVC +func pvcConditions(u *unstructured.Unstructured) (*Result, error) { + obj := u.UnstructuredContent() + + phase := GetStringField(obj, ".status.phase", "unknown") + if phase != "Bound" { // corev1.ClaimBound + message := fmt.Sprintf("PVC is not Bound. phase: %s", phase) + return newInProgressStatus("NotBound", message), nil + } + // All ok + return &Result{ + Status: CurrentStatus, + Message: "PVC is Bound", + Conditions: []Condition{}, + }, nil +} + +// podConditions return standardized Conditions for Pod +func podConditions(u *unstructured.Unstructured) (*Result, error) { + obj := u.UnstructuredContent() + objc, err := GetObjectWithConditions(obj) + if err != nil { + return nil, err + } + phase := GetStringField(obj, ".status.phase", "") + + switch phase { + case "Succeeded": + return &Result{ + Status: CurrentStatus, + Message: "Pod has completed successfully", + Conditions: []Condition{}, + }, nil + case "Failed": + return &Result{ + Status: CurrentStatus, + Message: "Pod has completed, but not successfully", + Conditions: []Condition{}, + }, nil + case "Running": + if hasConditionWithStatus(objc.Status.Conditions, "Ready", corev1.ConditionTrue) { + return &Result{ + Status: CurrentStatus, + Message: "Pod is Ready", + Conditions: []Condition{}, + }, nil + } + + containerNames, isCrashLooping, err := getCrashLoopingContainers(obj) + if err != nil { + return nil, err + } + if isCrashLooping { + return newFailedStatus("ContainerCrashLooping", + fmt.Sprintf("Containers in CrashLoop state: %s", strings.Join(containerNames, ","))), nil + } + + return newInProgressStatus("PodRunningNotReady", "Pod is running but is not Ready"), nil + case "Pending": + c, found := getConditionWithStatus(objc.Status.Conditions, "PodScheduled", corev1.ConditionFalse) + if found && c.Reason == "Unschedulable" { + if time.Now().Add(-ScheduleWindow).Before(u.GetCreationTimestamp().Time) { + // We give the pod 15 seconds to be scheduled before we report it + // as unschedulable. + return newInProgressStatus("PodNotScheduled", "Pod has not been scheduled"), nil + } + return newFailedStatus("PodUnschedulable", "Pod could not be scheduled"), nil + } + return newInProgressStatus("PodPending", "Pod is in the Pending phase"), nil + default: + // If the controller hasn't observed the pod yet, there is no phase. We consider this as it + // still being in progress. + if phase == "" { + return newInProgressStatus("PodNotObserved", "Pod phase not available"), nil + } + return nil, fmt.Errorf("unknown phase %s", phase) + } +} + +func getCrashLoopingContainers(obj map[string]interface{}) ([]string, bool, error) { + var containerNames []string + css, found, err := unstructured.NestedSlice(obj, "status", "containerStatuses") + if !found || err != nil { + return containerNames, found, err + } + for _, item := range css { + cs := item.(map[string]interface{}) + n, found := cs["name"] + if !found { + continue + } + name := n.(string) + s, found := cs["state"] + if !found { + continue + } + state := s.(map[string]interface{}) + + ws, found := state["waiting"] + if !found { + continue + } + waitingState := ws.(map[string]interface{}) + + r, found := waitingState["reason"] + if !found { + continue + } + reason := r.(string) + if reason == "CrashLoopBackOff" { + containerNames = append(containerNames, name) + } + } + if len(containerNames) > 0 { + return containerNames, true, nil + } + return containerNames, false, nil +} + +// pdbConditions computes the status for PodDisruptionBudgets. A PDB +// is currently considered Current if the disruption controller has +// observed the latest version of the PDB resource and has computed +// the AllowedDisruptions. PDBs do have ObservedGeneration in the +// Status object, so if this function gets called we know that +// the controller has observed the latest changes. +// The disruption controller does not set any conditions if +// computing the AllowedDisruptions fails (and there are many ways +// it can fail), but there is PR against OSS Kubernetes to address +// this: https://github.com/kubernetes/kubernetes/pull/86929 +func pdbConditions(_ *unstructured.Unstructured) (*Result, error) { + // All ok + return &Result{ + Status: CurrentStatus, + Message: "AllowedDisruptions has been computed.", + Conditions: []Condition{}, + }, nil +} + +// jobConditions return standardized Conditions for Job +// +// A job will have the InProgress status until it starts running. Then it will have the Current +// status while the job is running and after it has been completed successfully. It +// will have the Failed status if it the job has failed. +func jobConditions(u *unstructured.Unstructured) (*Result, error) { + obj := u.UnstructuredContent() + + parallelism := GetIntField(obj, ".spec.parallelism", 1) + completions := GetIntField(obj, ".spec.completions", parallelism) + succeeded := GetIntField(obj, ".status.succeeded", 0) + active := GetIntField(obj, ".status.active", 0) + failed := GetIntField(obj, ".status.failed", 0) + starttime := GetStringField(obj, ".status.startTime", "") + + // Conditions + // https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/job/utils.go#L24 + objc, err := GetObjectWithConditions(obj) + if err != nil { + return nil, err + } + for _, c := range objc.Status.Conditions { + switch c.Type { + case "Complete": + if c.Status == corev1.ConditionTrue { + message := fmt.Sprintf("Job Completed. succeeded: %d/%d", succeeded, completions) + return &Result{ + Status: CurrentStatus, + Message: message, + Conditions: []Condition{}, + }, nil + } + case "Failed": + if c.Status == corev1.ConditionTrue { + return newFailedStatus("JobFailed", + fmt.Sprintf("Job Failed. failed: %d/%d", failed, completions)), nil + } + } + } + + // replicas + if starttime == "" { + message := "Job not started" + return newInProgressStatus("JobNotStarted", message), nil + } + return &Result{ + Status: CurrentStatus, + Message: fmt.Sprintf("Job in progress. success:%d, active: %d, failed: %d", succeeded, active, failed), + Conditions: []Condition{}, + }, nil +} + +// serviceConditions return standardized Conditions for Service +func serviceConditions(u *unstructured.Unstructured) (*Result, error) { + obj := u.UnstructuredContent() + + specType := GetStringField(obj, ".spec.type", "ClusterIP") + specClusterIP := GetStringField(obj, ".spec.clusterIP", "") + + if specType == "LoadBalancer" { + if specClusterIP == "" { + message := "ClusterIP not set. Service type: LoadBalancer" + return newInProgressStatus("NoIPAssigned", message), nil + } + } + + return &Result{ + Status: CurrentStatus, + Message: "Service is ready", + Conditions: []Condition{}, + }, nil +} + +func crdConditions(u *unstructured.Unstructured) (*Result, error) { + obj := u.UnstructuredContent() + + objc, err := GetObjectWithConditions(obj) + if err != nil { + return nil, err + } + + for _, c := range objc.Status.Conditions { + if c.Type == "NamesAccepted" && c.Status == corev1.ConditionFalse { + return newFailedStatus(c.Reason, c.Message), nil + } + if c.Type == "Established" { + if c.Status == corev1.ConditionFalse && c.Reason != "Installing" { + return newFailedStatus(c.Reason, c.Message), nil + } + if c.Status == corev1.ConditionTrue { + return &Result{ + Status: CurrentStatus, + Message: "CRD is established", + Conditions: []Condition{}, + }, nil + } + } + } + return newInProgressStatus("Installing", "Install in progress"), nil +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/status/doc.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/status/doc.go new file mode 100644 index 00000000000..042fa76da77 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/status/doc.go @@ -0,0 +1,43 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +// Package kstatus contains functionality for computing the status +// of Kubernetes resources. +// +// The statuses defined in this package are: +// - InProgress +// - Current +// - Failed +// - Terminating +// - NotFound +// - Unknown +// +// Computing the status of a resources can be done by calling the +// Compute function in the status package. +// +// import ( +// "github.com/fluxcd/cli-utils/pkg/kstatus/status" +// ) +// +// res, err := status.Compute(resource) +// +// The package also defines a set of new conditions: +// - InProgress +// - Failed +// +// These conditions have been chosen to follow the +// "abnormal-true" pattern where conditions should be set to true +// for error/abnormal conditions and the absence of a condition means +// things are normal. +// +// The Augment function augments any unstructured resource with +// the standard conditions described above. The values of +// these conditions are decided based on other status information +// available in the resources. +// +// import ( +// "github.com/fluxcd/cli-utils/pkg/kstatus/status +// ) +// +// err := status.Augment(resource) +package status diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/status/generic.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/status/generic.go new file mode 100644 index 00000000000..6213d1b9652 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/status/generic.go @@ -0,0 +1,99 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package status + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +// checkGenericProperties looks at the properties that are available on +// all or most of the Kubernetes resources. If a decision can be made based +// on this information, there is no need to look at the resource-specidic +// rules. +// This also checks for the presence of the conditions defined in this package. +// If any of these are set on the resource, a decision is made solely based +// on this and none of the resource specific rules will be used. The goal here +// is that if controllers, built-in or custom, use these conditions, we can easily +// find status of resources. +func checkGenericProperties(u *unstructured.Unstructured) (*Result, error) { + obj := u.UnstructuredContent() + + // Check if the resource is scheduled for deletion + deletionTimestamp, found, err := unstructured.NestedString(obj, "metadata", "deletionTimestamp") + if err != nil { + return nil, fmt.Errorf("looking up metadata.deletionTimestamp from resource: %w", err) + } + if found && deletionTimestamp != "" { + return &Result{ + Status: TerminatingStatus, + Message: "Resource scheduled for deletion", + Conditions: []Condition{}, + }, nil + } + + res, err := checkGeneration(u) + if res != nil || err != nil { + return res, err + } + + // Check if the resource has any of the standard conditions. If so, we just use them + // and no need to look at anything else. + objWithConditions, err := GetObjectWithConditions(obj) + if err != nil { + return nil, err + } + + for _, cond := range objWithConditions.Status.Conditions { + if cond.Type == string(ConditionReconciling) && cond.Status == corev1.ConditionTrue { + return newInProgressStatus(cond.Reason, cond.Message), nil + } + if cond.Type == string(ConditionStalled) && cond.Status == corev1.ConditionTrue { + return &Result{ + Status: FailedStatus, + Message: cond.Message, + Conditions: []Condition{ + { + Type: ConditionStalled, + Status: corev1.ConditionTrue, + Reason: cond.Reason, + Message: cond.Message, + }, + }, + }, nil + } + } + + return nil, nil +} + +func checkGeneration(u *unstructured.Unstructured) (*Result, error) { + // ensure that the meta generation is observed + generation, found, err := unstructured.NestedInt64(u.Object, "metadata", "generation") + if err != nil { + return nil, fmt.Errorf("looking up metadata.generation from resource: %w", err) + } + if !found { + return nil, nil + } + observedGeneration, found, err := unstructured.NestedInt64(u.Object, "status", "observedGeneration") + if err != nil { + return nil, fmt.Errorf("looking up status.observedGeneration from resource: %w", err) + } + if found { + // Resource does not have this field, so we can't do this check. + // TODO(mortent): Verify behavior of not set vs does not exist. + if observedGeneration != generation { + message := fmt.Sprintf("%s generation is %d, but latest observed generation is %d", u.GetKind(), generation, observedGeneration) + return &Result{ + Status: InProgressStatus, + Message: message, + Conditions: []Condition{newReconcilingCondition("LatestGenerationNotObserved", message)}, + }, nil + } + } + return nil, nil +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/status/status.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/status/status.go new file mode 100644 index 00000000000..a8f2e28ded2 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/status/status.go @@ -0,0 +1,242 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package status + +import ( + "errors" + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +const ( + // The set of standard conditions defined in this package. These follow the "abnormality-true" + // convention where conditions should have a true value for abnormal/error situations and the absence + // of a condition should be interpreted as a false value, i.e. everything is normal. + ConditionStalled ConditionType = "Stalled" + ConditionReconciling ConditionType = "Reconciling" + + // The set of status conditions which can be assigned to resources. + InProgressStatus Status = "InProgress" + FailedStatus Status = "Failed" + CurrentStatus Status = "Current" + TerminatingStatus Status = "Terminating" + NotFoundStatus Status = "NotFound" + UnknownStatus Status = "Unknown" +) + +var ( + Statuses = []Status{InProgressStatus, FailedStatus, CurrentStatus, TerminatingStatus, UnknownStatus} +) + +// ConditionType defines the set of condition types allowed inside a Condition struct. +type ConditionType string + +// String returns the ConditionType as a string. +func (c ConditionType) String() string { + return string(c) +} + +// Status defines the set of statuses a resource can have. +type Status string + +// String returns the status as a string. +func (s Status) String() string { + return string(s) +} + +// StatusFromString turns a string into a Status. Will panic if the provided string is +// not a valid status. +func FromStringOrDie(text string) Status { + s := Status(text) + for _, r := range Statuses { + if s == r { + return s + } + } + panic(fmt.Errorf("string has invalid status: %s", s)) +} + +// Result contains the results of a call to compute the status of +// a resource. +type Result struct { + // Status + Status Status + // Message + Message string + // Conditions list of extracted conditions from Resource + Conditions []Condition +} + +// Condition defines the general format for conditions on Kubernetes resources. +// In practice, each kubernetes resource defines their own format for conditions, but +// most (maybe all) follows this structure. +type Condition struct { + // Type condition type + Type ConditionType `json:"type,omitempty"` + // Status String that describes the condition status + Status corev1.ConditionStatus `json:"status,omitempty"` + // Reason one work CamelCase reason + Reason string `json:"reason,omitempty"` + // Message Human readable reason string + Message string `json:"message,omitempty"` +} + +// Compute finds the status of a given unstructured resource. It does not +// fetch the state of the resource from a cluster, so the provided unstructured +// must have the complete state, including status. +// +// The returned result contains the status of the resource, which will be +// one of +// - InProgress +// - Current +// - Failed +// - Terminating +// +// It also contains a message that provides more information on why +// the resource has the given status. Finally, the result also contains +// a list of standard resources that would belong on the given resource. +func Compute(u *unstructured.Unstructured) (*Result, error) { + res, err := checkGenericProperties(u) + if err != nil { + return nil, err + } + + // If res is not nil, it means the generic checks was able to determine + // the status of the resource. We don't need to check the type-specific + // rules. + if res != nil { + return res, nil + } + + fn := GetLegacyConditionsFn(u) + if fn != nil { + return fn(u) + } + + // If neither the generic properties of the resource-specific rules + // can determine status, we do one last check to see if the resource + // does expose a Ready condition. Ready conditions do not adhere + // to the Kubernetes design recommendations, but they are pretty widely + // used. + res, err = checkReadyCondition(u) + if res != nil || err != nil { + return res, err + } + + // The resource is not one of the built-in types with specific + // rules and we were unable to make a decision based on the + // generic rules. In this case we assume that the absence of any known + // conditions means the resource is current. + return &Result{ + Status: CurrentStatus, + Message: "Resource is current", + Conditions: []Condition{}, + }, err +} + +// checkReadyCondition checks if a resource has a Ready condition, and +// if so, it will use the value of this condition to determine the +// status. +// There are a few challenges with this: +// - If a resource doesn't set the Ready condition until it is True, +// the library have no way of telling whether the resource is using the +// Ready condition, so it will fall back to the strategy for unknown +// resources, which is to assume they are always reconciled. +// - If the library sees the resource before the controller has had +// a chance to update the conditions, it also will not realize the +// resource use the Ready condition. +// - There is no way to determine if a resource with the Ready condition +// set to False is making progress or is doomed. +func checkReadyCondition(u *unstructured.Unstructured) (*Result, error) { + objWithConditions, err := GetObjectWithConditions(u.Object) + if err != nil { + return nil, err + } + + for _, cond := range objWithConditions.Status.Conditions { + if cond.Type != "Ready" { + continue + } + switch cond.Status { + case corev1.ConditionTrue: + return &Result{ + Status: CurrentStatus, + Message: "Resource is Ready", + Conditions: []Condition{}, + }, nil + case corev1.ConditionFalse: + return newInProgressStatus(cond.Reason, cond.Message), nil + case corev1.ConditionUnknown: + // For now we just treat an unknown condition value as + // InProgress. We should consider if there are better ways + // to handle it. + return newInProgressStatus(cond.Reason, cond.Message), nil + default: + // Do nothing in this case. + } + } + return nil, nil +} + +// Augment takes a resource and augments the resource with the +// standard status conditions. +func Augment(u *unstructured.Unstructured) error { + res, err := Compute(u) + if err != nil { + return err + } + + conditions, found, err := unstructured.NestedSlice(u.Object, "status", "conditions") + if err != nil { + return err + } + + if !found { + conditions = make([]interface{}, 0) + } + + currentTime := time.Now().UTC().Format(time.RFC3339) + + for _, resCondition := range res.Conditions { + present := false + for _, c := range conditions { + condition, ok := c.(map[string]interface{}) + if !ok { + return errors.New("condition does not have the expected structure") + } + conditionType, ok := condition["type"].(string) + if !ok { + return errors.New("condition type does not have the expected type") + } + if conditionType == string(resCondition.Type) { + conditionStatus, ok := condition["status"].(string) + if !ok { + return errors.New("condition status does not have the expected type") + } + if conditionStatus != string(resCondition.Status) { + condition["lastTransitionTime"] = currentTime + } + condition["status"] = string(resCondition.Status) + condition["lastUpdateTime"] = currentTime + condition["reason"] = resCondition.Reason + condition["message"] = resCondition.Message + present = true + } + } + if !present { + conditions = append(conditions, map[string]interface{}{ + "lastTransitionTime": currentTime, + "lastUpdateTime": currentTime, + "message": resCondition.Message, + "reason": resCondition.Reason, + "status": string(resCondition.Status), + "type": string(resCondition.Type), + }) + } + } + return unstructured.SetNestedSlice(u.Object, conditions, "status", "conditions") +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/status/util.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/status/util.go new file mode 100644 index 00000000000..b22152cc714 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/status/util.go @@ -0,0 +1,143 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package status + +import ( + "strings" + + corev1 "k8s.io/api/core/v1" + apiunstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + +// newReconcilingCondition creates an reconciling condition with the given +// reason and message. +func newReconcilingCondition(reason, message string) Condition { + return Condition{ + Type: ConditionReconciling, + Status: corev1.ConditionTrue, + Reason: reason, + Message: message, + } +} + +func newStalledCondition(reason, message string) Condition { + return Condition{ + Type: ConditionStalled, + Status: corev1.ConditionTrue, + Reason: reason, + Message: message, + } +} + +// newInProgressStatus creates a status Result with the InProgress status +// and an InProgress condition. +func newInProgressStatus(reason, message string) *Result { + return &Result{ + Status: InProgressStatus, + Message: message, + Conditions: []Condition{newReconcilingCondition(reason, message)}, + } +} + +func newFailedStatus(reason, message string) *Result { + return &Result{ + Status: FailedStatus, + Message: message, + Conditions: []Condition{newStalledCondition(reason, message)}, + } +} + +// ObjWithConditions Represent meta object with status.condition array +type ObjWithConditions struct { + // Status as expected to be present in most compliant kubernetes resources + Status ConditionStatus `json:"status" yaml:"status"` +} + +// ConditionStatus represent status with condition array +type ConditionStatus struct { + // Array of Conditions as expected to be present in kubernetes resources + Conditions []BasicCondition `json:"conditions" yaml:"conditions"` +} + +// BasicCondition fields that are expected in a condition +type BasicCondition struct { + // Type Condition type + Type string `json:"type" yaml:"type"` + // Status is one of True,False,Unknown + Status corev1.ConditionStatus `json:"status" yaml:"status"` + // Reason simple single word reason in CamleCase + // +optional + Reason string `json:"reason,omitempty" yaml:"reason"` + // Message human readable reason + // +optional + Message string `json:"message,omitempty" yaml:"message"` +} + +// GetObjectWithConditions return typed object +func GetObjectWithConditions(in map[string]interface{}) (*ObjWithConditions, error) { + var out = new(ObjWithConditions) + err := runtime.DefaultUnstructuredConverter.FromUnstructured(in, out) + if err != nil { + return nil, err + } + return out, nil +} + +func hasConditionWithStatus(conditions []BasicCondition, conditionType string, status corev1.ConditionStatus) bool { + _, found := getConditionWithStatus(conditions, conditionType, status) + return found +} + +func getConditionWithStatus(conditions []BasicCondition, conditionType string, status corev1.ConditionStatus) (BasicCondition, bool) { + for _, c := range conditions { + if c.Type == conditionType && c.Status == status { + return c, true + } + } + return BasicCondition{}, false +} + +// GetStringField return field as string defaulting to value if not found +func GetStringField(obj map[string]interface{}, fieldPath string, defaultValue string) string { + var rv = defaultValue + + fields := strings.Split(fieldPath, ".") + if fields[0] == "" { + fields = fields[1:] + } + + val, found, err := apiunstructured.NestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return rv + } + + if v, ok := val.(string); ok { + return v + } + return rv +} + +// GetIntField return field as string defaulting to value if not found +func GetIntField(obj map[string]interface{}, fieldPath string, defaultValue int) int { + fields := strings.Split(fieldPath, ".") + if fields[0] == "" { + fields = fields[1:] + } + + val, found, err := apiunstructured.NestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return defaultValue + } + + switch v := val.(type) { + case int: + return v + case int32: + return int(v) + case int64: + return int(v) + } + return defaultValue +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/blind_status_watcher.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/blind_status_watcher.go new file mode 100644 index 00000000000..2909f0c3967 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/blind_status_watcher.go @@ -0,0 +1,34 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package watcher + +import ( + "context" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/object" +) + +// BlindStatusWatcher sees nothing. +// BlindStatusWatcher sends no update or error events. +// BlindStatusWatcher waits patiently to be cancelled. +// BlindStatusWatcher implements the StatusWatcher interface. +type BlindStatusWatcher struct{} + +var _ StatusWatcher = BlindStatusWatcher{} + +// Watch nothing. See no changes. +func (w BlindStatusWatcher) Watch(ctx context.Context, _ object.ObjMetadataSet, _ Options) <-chan event.Event { + doneCh := ctx.Done() + eventCh := make(chan event.Event) + go func() { + // Send SyncEvent immediately. + eventCh <- event.Event{Type: event.SyncEvent} + // Block until the context is cancelled. + <-doneCh + // Signal to the caller there will be no more events. + close(eventCh) + }() + return eventCh +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/default_status_watcher.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/default_status_watcher.go new file mode 100644 index 00000000000..44acb195f22 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/default_status_watcher.go @@ -0,0 +1,176 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package watcher + +import ( + "context" + "fmt" + "time" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/clusterreader" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders" + "github.com/fluxcd/cli-utils/pkg/object" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/klog/v2" +) + +// DefaultStatusWatcher reports on status updates to a set of objects. +// +// Use NewDefaultStatusWatcher to build a DefaultStatusWatcher with default settings. +type DefaultStatusWatcher struct { + // DynamicClient is used to watch of resource objects. + DynamicClient dynamic.Interface + + // Mapper is used to map from GroupKind to GroupVersionKind. + Mapper meta.RESTMapper + + // ResyncPeriod is how often the objects are retrieved to re-synchronize, + // in case any events were missed. + ResyncPeriod time.Duration + + // StatusReader specifies a custom implementation of the + // engine.StatusReader interface that will be used to compute reconcile + // status for resource objects. + StatusReader engine.StatusReader + + // ClusterReader is used to look up generated objects on-demand. + // Generated objects (ex: Deployment > ReplicaSet > Pod) are sometimes + // required for computing parent object status, to compensate for + // controllers that aren't following status conventions. + ClusterReader engine.ClusterReader +} + +var _ StatusWatcher = &DefaultStatusWatcher{} + +// NewDefaultStatusWatcher constructs a DynamicStatusWatcher with defaults +// chosen for general use. If you need different settings, consider building a +// DynamicStatusWatcher directly. +func NewDefaultStatusWatcher(dynamicClient dynamic.Interface, mapper meta.RESTMapper) *DefaultStatusWatcher { + return &DefaultStatusWatcher{ + DynamicClient: dynamicClient, + Mapper: mapper, + ResyncPeriod: 1 * time.Hour, + StatusReader: statusreaders.NewDefaultStatusReader(mapper), + ClusterReader: &clusterreader.DynamicClusterReader{ + DynamicClient: dynamicClient, + Mapper: mapper, + }, + } +} + +// Watch the cluster for changes made to the specified objects. +// Returns an event channel on which these updates (and errors) will be reported. +// Each update event includes the computed status of the object. +func (w *DefaultStatusWatcher) Watch(ctx context.Context, ids object.ObjMetadataSet, opts Options) <-chan event.Event { + strategy := opts.RESTScopeStrategy + if strategy == RESTScopeAutomatic { + strategy = autoSelectRESTScopeStrategy(ids) + } + + var scope meta.RESTScope + var targets []GroupKindNamespace + switch strategy { + case RESTScopeRoot: + scope = meta.RESTScopeRoot + targets = rootScopeGKNs(ids) + klog.V(3).Infof("DynamicStatusWatcher starting in root-scoped mode (targets: %d)", len(targets)) + case RESTScopeNamespace: + scope = meta.RESTScopeNamespace + targets = namespaceScopeGKNs(ids) + klog.V(3).Infof("DynamicStatusWatcher starting in namespace-scoped mode (targets: %d)", len(targets)) + default: + return handleFatalError(fmt.Errorf("invalid RESTScopeStrategy: %v", strategy)) + } + + informer := &ObjectStatusReporter{ + InformerFactory: NewDynamicInformerFactory(w.DynamicClient, w.ResyncPeriod), + Mapper: w.Mapper, + StatusReader: w.StatusReader, + ClusterReader: w.ClusterReader, + Targets: targets, + ObjectFilter: &AllowListObjectFilter{AllowList: ids}, + RESTScope: scope, + } + return informer.Start(ctx) +} + +func handleFatalError(err error) <-chan event.Event { + eventCh := make(chan event.Event) + go func() { + defer close(eventCh) + eventCh <- event.Event{ + Type: event.ErrorEvent, + Error: err, + } + }() + return eventCh +} + +func autoSelectRESTScopeStrategy(ids object.ObjMetadataSet) RESTScopeStrategy { + if len(uniqueNamespaces(ids)) > 1 { + return RESTScopeRoot + } + return RESTScopeNamespace +} + +func rootScopeGKNs(ids object.ObjMetadataSet) []GroupKindNamespace { + gks := uniqueGKs(ids) + targets := make([]GroupKindNamespace, len(gks)) + for i, gk := range gks { + targets[i] = GroupKindNamespace{ + Group: gk.Group, + Kind: gk.Kind, + Namespace: "", + } + } + return targets +} + +func namespaceScopeGKNs(ids object.ObjMetadataSet) []GroupKindNamespace { + return uniqueGKNs(ids) +} + +// uniqueGKNs returns a set of unique GroupKindNamespaces from a set of object identifiers. +func uniqueGKNs(ids object.ObjMetadataSet) []GroupKindNamespace { + gknMap := make(map[GroupKindNamespace]struct{}) + for _, id := range ids { + gkn := GroupKindNamespace{Group: id.GroupKind.Group, Kind: id.GroupKind.Kind, Namespace: id.Namespace} + gknMap[gkn] = struct{}{} + } + gknList := make([]GroupKindNamespace, 0, len(gknMap)) + for gk := range gknMap { + gknList = append(gknList, gk) + } + return gknList +} + +// uniqueGKs returns a set of unique GroupKinds from a set of object identifiers. +func uniqueGKs(ids object.ObjMetadataSet) []schema.GroupKind { + gkMap := make(map[schema.GroupKind]struct{}) + for _, id := range ids { + gkn := schema.GroupKind{Group: id.GroupKind.Group, Kind: id.GroupKind.Kind} + gkMap[gkn] = struct{}{} + } + gkList := make([]schema.GroupKind, 0, len(gkMap)) + for gk := range gkMap { + gkList = append(gkList, gk) + } + return gkList +} + +func uniqueNamespaces(ids object.ObjMetadataSet) []string { + nsMap := make(map[string]struct{}) + for _, id := range ids { + nsMap[id.Namespace] = struct{}{} + } + nsList := make([]string, 0, len(nsMap)) + for ns := range nsMap { + nsList = append(nsList, ns) + } + return nsList +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/doc.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/doc.go new file mode 100644 index 00000000000..e86e0001ed9 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/doc.go @@ -0,0 +1,39 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +// Package watcher is a library for computing the status of kubernetes resource +// objects based on watching object state from a cluster. It keeps watching +// until it is cancelled through the provided context. Updates on the status of +// objects are streamed back to the caller through a channel. +// +// # Watching Resources +// +// In order to watch a set of resources objects, create a StatusWatcher +// and pass in the list of object identifiers to the Watch function. +// +// import ( +// "github.com/fluxcd/cli-utils/pkg/kstatus/watcher" +// ) +// +// ids := []prune.ObjMetadata{ +// { +// GroupKind: schema.GroupKind{ +// Group: "apps", +// Kind: "Deployment", +// }, +// Name: "dep", +// Namespace: "default", +// } +// } +// +// statusWatcher := watcher.NewDefaultStatusWatcher(dynamicClient, mapper) +// ctx, cancelFunc := context.WithCancel(context.Background()) +// eventCh := statusWatcher.Watch(ctx, ids, watcher.Options{}) +// for e := range eventCh { +// // Handle event +// if e.Type == event.ErrorEvent { +// cancelFunc() +// return e.Err +// } +// } +package watcher diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/dynamic_informer_factory.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/dynamic_informer_factory.go new file mode 100644 index 00000000000..52ef170a924 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/dynamic_informer_factory.go @@ -0,0 +1,64 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package watcher + +import ( + "context" + "time" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/tools/cache" +) + +type DynamicInformerFactory struct { + Client dynamic.Interface + ResyncPeriod time.Duration + Indexers cache.Indexers +} + +func NewDynamicInformerFactory(client dynamic.Interface, resyncPeriod time.Duration) *DynamicInformerFactory { + return &DynamicInformerFactory{ + Client: client, + ResyncPeriod: resyncPeriod, + Indexers: cache.Indexers{ + cache.NamespaceIndex: cache.MetaNamespaceIndexFunc, + }, + } +} + +func (f *DynamicInformerFactory) NewInformer(ctx context.Context, mapping *meta.RESTMapping, namespace string) cache.SharedIndexInformer { + // Unstructured example output need `"apiVersion"` and `"kind"` set. + example := &unstructured.Unstructured{} + example.SetGroupVersionKind(mapping.GroupVersionKind) + + lw := &cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + return f.Client.Resource(mapping.Resource). + Namespace(namespace). + List(ctx, options) + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + return f.Client.Resource(mapping.Resource). + Namespace(namespace). + Watch(ctx, options) + }, + } + + // Wrap the ListWatch with the client to allow the reflector to detect + // if the client supports WatchList semantics. This is important for + // fake clients used in tests, which do not support WatchList. + wrappedLW := cache.ToListWatcherWithWatchListSemantics(lw, f.Client) + + return cache.NewSharedIndexInformer( + wrappedLW, + example, + f.ResyncPeriod, + f.Indexers, + ) +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/event_funnel.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/event_funnel.go new file mode 100644 index 00000000000..71ba3312870 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/event_funnel.go @@ -0,0 +1,122 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package watcher + +import ( + "context" + "fmt" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "k8s.io/klog/v2" +) + +// eventFunnel wraps a list of event channels and multiplexes them down to a +// single event channel. New input channels can be added at runtime, and the +// output channel will remain open until all input channels are closed. +type eventFunnel struct { + // ctx closure triggers shutdown + ctx context.Context + // outCh is the funnel that consumes all events from input channels + outCh chan event.Event + // doneCh is closed after outCh is closed. + // This allows blocking until done without consuming events. + doneCh chan struct{} + // counterCh is used to track the number of open input channels. + counterCh chan int +} + +func newEventFunnel(ctx context.Context) *eventFunnel { + funnel := &eventFunnel{ + ctx: ctx, + outCh: make(chan event.Event), + doneCh: make(chan struct{}), + counterCh: make(chan int), + } + // Wait until the context is done and all input channels are closed. + // Then close out and done channels to signal completion. + go func() { + defer func() { + // Don't close counterCh, otherwise AddInputChannel may panic. + klog.V(5).Info("Closing funnel") + close(funnel.outCh) + close(funnel.doneCh) + }() + ctxDoneCh := ctx.Done() + + // Count input channels that have been added and not closed. + inputs := 0 + for { + select { + case delta := <-funnel.counterCh: + inputs += delta + klog.V(5).Infof("Funnel input channels (%+d): %d", delta, inputs) + case <-ctxDoneCh: + // Stop waiting for context closure. + // Nil channel avoids busy waiting. + ctxDoneCh = nil + } + if ctxDoneCh == nil && inputs <= 0 { + // Context is closed and all input channels are closed. + break + } + } + }() + return funnel +} + +// Add a new input channel to the multiplexer. +func (m *eventFunnel) AddInputChannel(inCh <-chan event.Event) error { + select { + case <-m.ctx.Done(): // skip, if context is closed + return &EventFunnelClosedError{ContextError: m.ctx.Err()} + case m.counterCh <- 1: // increment counter + } + + // Create a multiplexer for each new event channel. + go m.drain(inCh, m.outCh) + return nil +} + +// OutputChannel channel receives all events sent to input channels. +// This channel is closed after all input channels are closed. +func (m *eventFunnel) OutputChannel() <-chan event.Event { + return m.outCh +} + +// Done channel is closed after the Output channel is closed. +// This allows blocking until done without consuming events. +// If no input channels have been added yet, the done channel will be nil. +func (m *eventFunnel) Done() <-chan struct{} { + return m.doneCh +} + +// drain a single input channel to a single output channel. +func (m *eventFunnel) drain(inCh <-chan event.Event, outCh chan<- event.Event) { + defer func() { + m.counterCh <- -1 // decrement counter + }() + for event := range inCh { + outCh <- event + } +} + +type EventFunnelClosedError struct { + ContextError error +} + +func (e *EventFunnelClosedError) Error() string { + return fmt.Sprintf("event funnel closed: %v", e.ContextError) +} + +func (e *EventFunnelClosedError) Is(err error) bool { + fcErr, ok := err.(*EventFunnelClosedError) + if !ok { + return false + } + return e.ContextError == fcErr.ContextError +} + +func (e *EventFunnelClosedError) Unwrap() error { + return e.ContextError +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/object_filter.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/object_filter.go new file mode 100644 index 00000000000..eafd929936f --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/object_filter.go @@ -0,0 +1,30 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package watcher + +import ( + "github.com/fluxcd/cli-utils/pkg/object" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +// ObjectFilter allows for filtering objects. +type ObjectFilter interface { + // Filter returns true if the object should be skipped. + Filter(obj *unstructured.Unstructured) bool +} + +// AllowListObjectFilter filters objects not in the allow list. +// AllowListObjectFilter implements ObjectFilter. +type AllowListObjectFilter struct { + AllowList object.ObjMetadataSet +} + +var _ ObjectFilter = &AllowListObjectFilter{} + +// Filter returns true if the object should be skipped, because it is NOT in the +// AllowList. +func (f *AllowListObjectFilter) Filter(obj *unstructured.Unstructured) bool { + id := object.UnstructuredToObjMetadata(obj) + return !f.AllowList.Contains(id) +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/object_status_reporter.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/object_status_reporter.go new file mode 100644 index 00000000000..5d341bc20dd --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/object_status_reporter.go @@ -0,0 +1,895 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package watcher + +import ( + "context" + "errors" + "fmt" + "io" + "sync" + "time" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/tools/cache" + "k8s.io/klog/v2" + "k8s.io/utils/clock" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/kstatus/status" + "github.com/fluxcd/cli-utils/pkg/object" +) + +// GroupKindNamespace identifies an informer target. +// When used as an informer target, the namespace is optional. +// When the namespace is empty for namespaced resources, all namespaces are watched. +type GroupKindNamespace struct { + Group string + Kind string + Namespace string +} + +// String returns a serialized form suitable for logging. +func (gkn GroupKindNamespace) String() string { + return fmt.Sprintf("%s/%s/namespaces/%s", + gkn.Group, gkn.Kind, gkn.Namespace) +} + +func (gkn GroupKindNamespace) GroupKind() schema.GroupKind { + return schema.GroupKind{Group: gkn.Group, Kind: gkn.Kind} +} + +// ObjectStatusReporter reports on updates to objects (instances) using a +// network of informers to watch one or more resources (types). +// +// Unlike SharedIndexInformer, ObjectStatusReporter... +// - Reports object status. +// - Can watch multiple resource types simultaneously. +// - Specific objects can be ignored for efficiency by specifying an ObjectFilter. +// - Resolves GroupKinds into Resources at runtime, to pick up newly added +// resources. +// - Starts and Stops individual watches automaically to reduce errors when a +// CRD or Namespace is deleted. +// - Resources can be watched in root-scope mode or namespace-scope mode, +// allowing the caller to optimize for efficiency or least-privilege. +// - Gives unschedulable Pods (and objects that generate them) a 15s grace +// period before reporting them as Failed. +// - Resets the RESTMapper cache automatically when CRDs are modified. +// +// ObjectStatusReporter is NOT repeatable. It will panic if started more than +// once. If you need a repeatable factory, use DefaultStatusWatcher. +// +// TODO: support detection of added/removed api extensions at runtime +// TODO: Watch CRDs & Namespaces, even if not in the set of IDs. +// TODO: Retry with backoff if in namespace-scoped mode, to allow CRDs & namespaces to be created asynchronously +type ObjectStatusReporter struct { + // InformerFactory is used to build informers + InformerFactory *DynamicInformerFactory + + // Mapper is used to map from GroupKind to GroupVersionKind. + Mapper meta.RESTMapper + + // StatusReader specifies a custom implementation of the + // engine.StatusReader interface that will be used to compute reconcile + // status for resource objects. + StatusReader engine.StatusReader + + // ClusterReader is used to look up generated objects on-demand. + // Generated objects (ex: Deployment > ReplicaSet > Pod) are sometimes + // required for computing parent object status, to compensate for + // controllers that aren't following status conventions. + ClusterReader engine.ClusterReader + + // GroupKinds is the list of GroupKinds to watch. + Targets []GroupKindNamespace + + // ObjectFilter is used to decide which objects to ingore. + ObjectFilter ObjectFilter + + // RESTScope specifies whether to ListAndWatch resources at the namespace + // or cluster (root) level. Using root scope is more efficient, but + // namespace scope may require fewer permissions. + RESTScope meta.RESTScope + + // lock guards modification of the subsequent stateful fields + lock sync.Mutex + + // gk2gkn maps GKs to GKNs to make it easy/cheap to look up. + gk2gkn map[schema.GroupKind]map[GroupKindNamespace]struct{} + + // ns2gkn maps Namespaces to GKNs to make it easy/cheap to look up. + ns2gkn map[string]map[GroupKindNamespace]struct{} + + // informerRefs tracks which informers have been started and stopped + informerRefs map[GroupKindNamespace]*informerReference + + // context will be cancelled when the reporter should stop. + context context.Context + + // cancel function that stops the context. + // This should only be called after the terminal error event has been sent. + cancel context.CancelFunc + + // funnel multiplexes multiple input channels into one output channel, + // allowing input channels to be added and removed at runtime. + funnel *eventFunnel + + // taskManager makes it possible to cancel scheduled tasks. + taskManager *taskManager + + started bool + stopped bool +} + +func (w *ObjectStatusReporter) Start(ctx context.Context) <-chan event.Event { + w.lock.Lock() + defer w.lock.Unlock() + + if w.started { + panic("ObjectStatusInformer cannot be restarted") + } + + w.taskManager = &taskManager{} + + // Map GroupKinds to sets of GroupKindNamespaces for fast lookups. + // This is the only time we modify the map. + // So it should be safe to read from multiple threads after this. + w.gk2gkn = make(map[schema.GroupKind]map[GroupKindNamespace]struct{}) + for _, gkn := range w.Targets { + gk := gkn.GroupKind() + m, found := w.gk2gkn[gk] + if !found { + m = make(map[GroupKindNamespace]struct{}) + w.gk2gkn[gk] = m + } + m[gkn] = struct{}{} + } + + // Map namespaces to sets of GroupKindNamespaces for fast lookups. + // This is the only time we modify the map. + // So it should be safe to read from multiple threads after this. + w.ns2gkn = make(map[string]map[GroupKindNamespace]struct{}) + for _, gkn := range w.Targets { + ns := gkn.Namespace + m, found := w.ns2gkn[ns] + if !found { + m = make(map[GroupKindNamespace]struct{}) + w.ns2gkn[ns] = m + } + m[gkn] = struct{}{} + } + + // Initialize the informer map with references to track their start/stop. + // This is the only time we modify the map. + // So it should be safe to read from multiple threads after this. + w.informerRefs = make(map[GroupKindNamespace]*informerReference, len(w.Targets)) + for _, gkn := range w.Targets { + w.informerRefs[gkn] = &informerReference{} + } + + ctx, cancel := context.WithCancel(ctx) + w.context = ctx + w.cancel = cancel + + // Use an event funnel to multiplex events through multiple input channels + // into out output channel. We can't use the normal fan-in pattern, because + // we need to be able to add and remove new input channels at runtime, as + // new informers are created and destroyed. + w.funnel = newEventFunnel(ctx) + + // Send start requests. + for _, gkn := range w.Targets { + w.startInformer(gkn) + } + + w.started = true + + // Block until the event funnel is closed. + // The event funnel will close after all the informer channels are closed. + // The informer channels will close after the informers have stopped. + // The informers will stop after their context is cancelled. + go func() { + <-w.funnel.Done() + + w.lock.Lock() + defer w.lock.Unlock() + w.stopped = true + }() + + // Wait until all informers are synced or stopped, then send a SyncEvent. + syncEventCh := make(chan event.Event) + err := w.funnel.AddInputChannel(syncEventCh) + if err != nil { + // Reporter already stopped. + return handleFatalError(fmt.Errorf("reporter failed to start: %v", err)) + } + go func() { + defer close(syncEventCh) + // TODO: should we use something less aggressive, like wait.BackoffUntil? + if cache.WaitForCacheSync(ctx.Done(), w.HasSynced) { + syncEventCh <- event.Event{ + Type: event.SyncEvent, + } + } + }() + + return w.funnel.OutputChannel() +} + +// Stop triggers the cancellation of the reporter context, and closure of the +// event channel without sending an error event. +func (w *ObjectStatusReporter) Stop() { + klog.V(4).Info("Stopping reporter") + w.cancel() +} + +// HasSynced returns true if all the started informers have been synced. +// +// Use the following to block waiting for synchronization: +// synced := cache.WaitForCacheSync(stopCh, informer.HasSynced) +func (w *ObjectStatusReporter) HasSynced() bool { + w.lock.Lock() + defer w.lock.Unlock() + + if w.stopped || !w.started { + return false + } + + pending := make([]GroupKindNamespace, 0, len(w.informerRefs)) + for gke, informer := range w.informerRefs { + if informer.HasStarted() && !informer.HasSynced() { + pending = append(pending, gke) + } + } + if len(pending) > 0 { + klog.V(5).Infof("Informers pending synchronization: %v", pending) + return false + } + return true +} + +// startInformer adds the specified GroupKindNamespace to the start channel to +// be started asynchronously. +func (w *ObjectStatusReporter) startInformer(gkn GroupKindNamespace) { + ctx, ok := w.informerRefs[gkn].Start(w.context) + if !ok { + klog.V(5).Infof("Watch start skipped (already started): %v", gkn) + // already started + return + } + go w.startInformerWithRetry(ctx, gkn) +} + +// stopInformer stops the informer watching the specified GroupKindNamespace. +func (w *ObjectStatusReporter) stopInformer(gkn GroupKindNamespace) { + w.informerRefs[gkn].Stop() +} + +func (w *ObjectStatusReporter) startInformerWithRetry(ctx context.Context, gkn GroupKindNamespace) { + realClock := &clock.RealClock{} + // TODO nolint can be removed once https://github.com/kubernetes/kubernetes/issues/118638 is resolved + backoffManager := wait.NewExponentialBackoffManager(800*time.Millisecond, 30*time.Second, 2*time.Minute, 2.0, 1.0, realClock) //nolint:staticcheck + retryCtx, retryCancel := context.WithCancel(ctx) + + wait.BackoffUntil(func() { + err := w.startInformerNow( + ctx, + gkn, + ) + if err != nil { + if meta.IsNoMatchError(err) { + // CRD (or api extension) not installed + // TODO: retry if CRDs are not being watched + klog.V(3).Infof("Watch start error (blocking until CRD is added): %v: %v", gkn, err) + // Cancel the parent context, which will stop the retries too. + w.stopInformer(gkn) + return + } + + // Create a temporary input channel to send the error event. + eventCh := make(chan event.Event) + defer close(eventCh) + err := w.funnel.AddInputChannel(eventCh) + if err != nil { + // Reporter already stopped. + // This is fine. 🔥 + klog.V(5).Infof("Informer failed to start: %v", err) + return + } + // Send error event and stop the reporter! + w.handleFatalError(eventCh, err) + return + } + // Success! - Stop retrying + retryCancel() + }, backoffManager, true, retryCtx.Done()) +} + +// startInformerNow starts an informer to watch for changes to a +// GroupKindNamespace. Changes are filtered and passed by event channel into the +// funnel. Each update event includes the computed status of the object. +// An error is returned if the informer could not be created. +func (w *ObjectStatusReporter) startInformerNow( + ctx context.Context, + gkn GroupKindNamespace, +) error { + // Look up the mapping for this GroupKind. + // If it doesn't exist, either delay watching or emit an error. + gk := gkn.GroupKind() + mapping, err := w.Mapper.RESTMapping(gk) + if err != nil { + // Might be a NoResourceMatchError/NoKindMatchError + return err + } + + informer := w.InformerFactory.NewInformer(ctx, mapping, gkn.Namespace) + + w.informerRefs[gkn].SetInformer(informer) + + eventCh := make(chan event.Event) + + // Add this event channel to the output multiplexer + err = w.funnel.AddInputChannel(eventCh) + if err != nil { + // Reporter already stopped. + return fmt.Errorf("informer failed to build event handler: %w", err) + } + + // Handler called when ListAndWatch errors. + // Custom handler stops the informer if the resource is NotFound (CRD deleted). + err = informer.SetWatchErrorHandler(func(r *cache.Reflector, err error) { + w.watchErrorHandler(gkn, eventCh, err) + }) + if err != nil { + // Should never happen. + // Informer can't have started yet. We just created it. + return fmt.Errorf("failed to set error handler on new informer for %v: %v", mapping.Resource, err) + } + + _, err = informer.AddEventHandler(w.eventHandler(ctx, eventCh)) + if err != nil { + // Should never happen. + return fmt.Errorf("failed add event handler on new informer for %v: %v", mapping.Resource, err) + } + + // Start the informer in the background. + // Informer will be stopped when the context is cancelled. + go func() { + klog.V(3).Infof("Watch starting: %v", gkn) + informer.Run(ctx.Done()) + klog.V(3).Infof("Watch stopped: %v", gkn) + // Signal to the caller there will be no more events for this GroupKind. + close(eventCh) + }() + + return nil +} + +func (w *ObjectStatusReporter) forEachTargetWithGroupKind(gk schema.GroupKind, fn func(GroupKindNamespace)) { + for gkn := range w.gk2gkn[gk] { + fn(gkn) + } +} + +func (w *ObjectStatusReporter) forEachTargetWithNamespace(ns string, fn func(GroupKindNamespace)) { + for gkn := range w.ns2gkn[ns] { + fn(gkn) + } +} + +// readStatusFromObject is a convenience function to read object status with a +// StatusReader using a ClusterReader to retrieve generated objects. +func (w *ObjectStatusReporter) readStatusFromObject( + ctx context.Context, + obj *unstructured.Unstructured, +) (*event.ResourceStatus, error) { + return w.StatusReader.ReadStatusForObject(ctx, w.ClusterReader, obj) +} + +// readStatusFromCluster is a convenience function to read object status with a +// StatusReader using a ClusterReader to retrieve the object and its generated +// objects. +func (w *ObjectStatusReporter) readStatusFromCluster( + ctx context.Context, + id object.ObjMetadata, +) (*event.ResourceStatus, error) { + return w.StatusReader.ReadStatus(ctx, w.ClusterReader, id) +} + +// deletedStatus builds a ResourceStatus for a deleted object. +// +// StatusReader.ReadStatusForObject doesn't handle nil objects as input. So +// this builds the status manually. +// TODO: find a way to delegate this back to the status package. +func deletedStatus(id object.ObjMetadata) *event.ResourceStatus { + // Status is always NotFound after deltion. + // Passed obj represents the last known state, not the current state. + result := &event.ResourceStatus{ + Identifier: id, + Status: status.NotFoundStatus, + Message: "Resource not found", + } + + return &event.ResourceStatus{ + Identifier: id, + Resource: nil, // deleted object has no + Status: result.Status, + Message: result.Message, + // If deleted with foreground deletion, a finalizer will have blocked + // deletion until all the generated resources are deleted. + // TODO: Handle lookup of generated resources when not using foreground deletion. + GeneratedResources: nil, + } +} + +// eventHandler builds an event handler to compute object status. +// Returns an event channel on which these stats updates will be reported. +func (w *ObjectStatusReporter) eventHandler( + ctx context.Context, + eventCh chan<- event.Event, +) cache.ResourceEventHandler { + var handler cache.ResourceEventHandlerFuncs + + handler.AddFunc = func(iobj interface{}) { + // Bail early if the context is cancelled, to avoid unnecessary work. + if ctx.Err() != nil { + return + } + + obj, ok := iobj.(*unstructured.Unstructured) + if !ok { + panic(fmt.Sprintf("AddFunc received unexpected object type %T", iobj)) + } + id := object.UnstructuredToObjMetadata(obj) + if w.ObjectFilter.Filter(obj) { + klog.V(7).Infof("Watch Event Skipped: AddFunc: %s", id) + return + } + klog.V(5).Infof("AddFunc: Computing status for object: %s", id) + + // cancel any scheduled status update for this object + w.taskManager.Cancel(id) + + rs, err := w.readStatusFromObject(ctx, obj) + if err != nil { + // Send error event and stop the reporter! + w.handleFatalError(eventCh, fmt.Errorf("failed to compute object status: %s: %w", id, err)) + return + } + + if object.IsNamespace(obj) { + klog.V(5).Infof("AddFunc: Namespace added: %v", id) + w.onNamespaceAdd(obj) + } else if object.IsCRD(obj) { + klog.V(5).Infof("AddFunc: CRD added: %v", id) + w.onCRDAdd(obj) + } + + if isObjectUnschedulable(rs) { + klog.V(5).Infof("AddFunc: object unschedulable: %v", id) + // schedule delayed status update + w.taskManager.Schedule(ctx, id, status.ScheduleWindow, + w.newStatusCheckTaskFunc(ctx, eventCh, id)) + } + + klog.V(7).Infof("AddFunc: sending update event: %v", rs) + eventCh <- event.Event{ + Type: event.ResourceUpdateEvent, + Resource: rs, + } + } + + handler.UpdateFunc = func(_, iobj interface{}) { + // Bail early if the context is cancelled, to avoid unnecessary work. + if ctx.Err() != nil { + return + } + + obj, ok := iobj.(*unstructured.Unstructured) + if !ok { + panic(fmt.Sprintf("UpdateFunc received unexpected object type %T", iobj)) + } + id := object.UnstructuredToObjMetadata(obj) + if w.ObjectFilter.Filter(obj) { + klog.V(7).Infof("UpdateFunc: Watch Event Skipped: %s", id) + return + } + klog.V(5).Infof("UpdateFunc: Computing status for object: %s", id) + + // cancel any scheduled status update for this object + w.taskManager.Cancel(id) + + rs, err := w.readStatusFromObject(ctx, obj) + if err != nil { + // Send error event and stop the reporter! + w.handleFatalError(eventCh, fmt.Errorf("failed to compute object status: %s: %w", id, err)) + return + } + + if object.IsNamespace(obj) { + klog.V(5).Infof("UpdateFunc: Namespace updated: %v", id) + w.onNamespaceUpdate(obj) + } else if object.IsCRD(obj) { + klog.V(5).Infof("UpdateFunc: CRD updated: %v", id) + w.onCRDUpdate(obj) + } + + if isObjectUnschedulable(rs) { + klog.V(5).Infof("UpdateFunc: object unschedulable: %v", id) + // schedule delayed status update + w.taskManager.Schedule(ctx, id, status.ScheduleWindow, + w.newStatusCheckTaskFunc(ctx, eventCh, id)) + } + + klog.V(7).Infof("UpdateFunc: sending update event: %v", rs) + eventCh <- event.Event{ + Type: event.ResourceUpdateEvent, + Resource: rs, + } + } + + handler.DeleteFunc = func(iobj interface{}) { + // Bail early if the context is cancelled, to avoid unnecessary work. + if ctx.Err() != nil { + return + } + + if tombstone, ok := iobj.(cache.DeletedFinalStateUnknown); ok { + // Last state unknown. Possibly stale. + // TODO: Should we propegate this uncertainty to the caller? + iobj = tombstone.Obj + } + obj, ok := iobj.(*unstructured.Unstructured) + if !ok { + panic(fmt.Sprintf("DeleteFunc received unexpected object type %T", iobj)) + } + id := object.UnstructuredToObjMetadata(obj) + if w.ObjectFilter.Filter(obj) { + klog.V(7).Infof("DeleteFunc: Watch Event Skipped: %s", id) + return + } + klog.V(5).Infof("DeleteFunc: Computing status for object: %s", id) + + // cancel any scheduled status update for this object + w.taskManager.Cancel(id) + + if object.IsNamespace(obj) { + klog.V(5).Infof("DeleteFunc: Namespace deleted: %v", id) + w.onNamespaceDelete(obj) + } else if object.IsCRD(obj) { + klog.V(5).Infof("DeleteFunc: CRD deleted: %v", id) + w.onCRDDelete(obj) + } + + rs := deletedStatus(id) + klog.V(7).Infof("DeleteFunc: sending update event: %v", rs) + eventCh <- event.Event{ + Type: event.ResourceUpdateEvent, + Resource: rs, + } + } + + return handler +} + +// onCRDAdd handles creating a new informer to watch the new resource type. +func (w *ObjectStatusReporter) onCRDAdd(obj *unstructured.Unstructured) { + gk, found := object.GetCRDGroupKind(obj) + if !found { + id := object.UnstructuredToObjMetadata(obj) + klog.Warningf("Invalid CRD added: missing group and/or kind: %v", id) + // Don't return an error, because this should not inturrupt the task queue. + // TODO: Allow non-fatal errors to be reported using a specific error type. + return + } + klog.V(3).Infof("CRD added for %s", gk) + + klog.V(3).Info("Resetting RESTMapper") + // Reset mapper to invalidate cache. + meta.MaybeResetRESTMapper(w.Mapper) + + w.forEachTargetWithGroupKind(gk, func(gkn GroupKindNamespace) { + w.startInformer(gkn) + }) +} + +// onCRDUpdate handles creating a new informer to watch the updated resource type. +func (w *ObjectStatusReporter) onCRDUpdate(newObj *unstructured.Unstructured) { + gk, found := object.GetCRDGroupKind(newObj) + if !found { + id := object.UnstructuredToObjMetadata(newObj) + klog.Warningf("Invalid CRD updated: missing group and/or kind: %v", id) + // Don't return an error, because this should not inturrupt the task queue. + // TODO: Allow non-fatal errors to be reported using a specific error type. + return + } + klog.V(3).Infof("CRD updated for %s", gk) + + klog.V(3).Info("Resetting RESTMapper") + // Reset mapper to invalidate cache. + meta.MaybeResetRESTMapper(w.Mapper) + + w.forEachTargetWithGroupKind(gk, func(gkn GroupKindNamespace) { + w.startInformer(gkn) + }) +} + +// onCRDDelete handles stopping the informer watching the deleted resource type. +func (w *ObjectStatusReporter) onCRDDelete(oldObj *unstructured.Unstructured) { + gk, found := object.GetCRDGroupKind(oldObj) + if !found { + id := object.UnstructuredToObjMetadata(oldObj) + klog.Warningf("Invalid CRD deleted: missing group and/or kind: %v", id) + // Don't return an error, because this should not inturrupt the task queue. + // TODO: Allow non-fatal errors to be reported using a specific error type. + return + } + klog.V(3).Infof("CRD deleted for %s", gk) + + w.forEachTargetWithGroupKind(gk, func(gkn GroupKindNamespace) { + w.stopInformer(gkn) + }) + + klog.V(3).Info("Resetting RESTMapper") + // Reset mapper to invalidate cache. + meta.MaybeResetRESTMapper(w.Mapper) +} + +// onNamespaceAdd handles creating new informers to watch this namespace. +func (w *ObjectStatusReporter) onNamespaceAdd(obj *unstructured.Unstructured) { + if w.RESTScope == meta.RESTScopeRoot { + // When watching resources across all namespaces, + // we don't need to start or stop any + // namespace-specific informers. + return + } + namespace := obj.GetName() + w.forEachTargetWithNamespace(namespace, func(gkn GroupKindNamespace) { + w.startInformer(gkn) + }) +} + +// onNamespaceUpdate handles creating new informers to watch this namespace. +func (w *ObjectStatusReporter) onNamespaceUpdate(obj *unstructured.Unstructured) { + if w.RESTScope == meta.RESTScopeRoot { + // When watching resources across all namespaces, + // we don't need to start or stop any + // namespace-specific informers. + return + } + namespace := obj.GetName() + w.forEachTargetWithNamespace(namespace, func(gkn GroupKindNamespace) { + w.startInformer(gkn) + }) +} + +// onNamespaceDelete handles stopping informers watching this namespace. +func (w *ObjectStatusReporter) onNamespaceDelete(obj *unstructured.Unstructured) { + if w.RESTScope == meta.RESTScopeRoot { + // When watching resources across all namespaces, + // we don't need to start or stop any + // namespace-specific informers. + return + } + namespace := obj.GetName() + w.forEachTargetWithNamespace(namespace, func(gkn GroupKindNamespace) { + w.stopInformer(gkn) + }) +} + +// newStatusCheckTaskFunc returns a taskFund that reads the status of an object +// from the cluster and sends it over the event channel. +// +// This method should only be used for generated resource objects, as it's much +// slower at scale than watching the resource for updates. +func (w *ObjectStatusReporter) newStatusCheckTaskFunc( + ctx context.Context, + eventCh chan<- event.Event, + id object.ObjMetadata, +) taskFunc { + return func() { + klog.V(5).Infof("Re-reading object status: %s", id) + // check again + rs, err := w.readStatusFromCluster(ctx, id) + if err != nil { + // Send error event and stop the reporter! + // TODO: retry N times before terminating + w.handleFatalError(eventCh, err) + return + } + eventCh <- event.Event{ + Type: event.ResourceUpdateEvent, + Resource: rs, + } + } +} + +func (w *ObjectStatusReporter) handleFatalError(eventCh chan<- event.Event, err error) { + klog.V(5).Infof("Reporter error: %v", err) + if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { + return + } + eventCh <- event.Event{ + Type: event.ErrorEvent, + Error: err, + } + w.Stop() +} + +// watchErrorHandler logs errors and cancels the informer for this GroupKind +// if the NotFound error is received, which usually means the CRD was deleted. +// Based on DefaultWatchErrorHandler from k8s.io/client-go@v0.23.2/tools/cache/reflector.go +func (w *ObjectStatusReporter) watchErrorHandler(gkn GroupKindNamespace, eventCh chan<- event.Event, err error) { + switch { + // Stop channel closed + case err == io.EOF: + klog.V(5).Infof("ListAndWatch error (termination expected): %v: %v", gkn, err) + + // Watch connection closed + case err == io.ErrUnexpectedEOF: + klog.V(1).Infof("ListAndWatch error (retry expected): %v: %v", gkn, err) + + // Context done + case errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded): + klog.V(5).Infof("ListAndWatch error (termination expected): %v: %v", gkn, err) + + // resourceVersion too old + case apierrors.IsResourceExpired(err): + // Keep retrying + klog.V(5).Infof("ListAndWatch error (retry expected): %v: %v", gkn, err) + + // Resource unregistered (DEPRECATED, see NotFound) + case apierrors.IsGone(err): + klog.V(5).Infof("ListAndWatch error (retry expected): %v: %v", gkn, err) + + // Resource not registered + case apierrors.IsNotFound(err): + klog.V(3).Infof("ListAndWatch error (termination expected): %v: stopping all informers for this GroupKind: %v", gkn, err) + w.forEachTargetWithGroupKind(gkn.GroupKind(), func(gkn GroupKindNamespace) { + w.stopInformer(gkn) + }) + + // Insufficient permissions + case apierrors.IsForbidden(err): + klog.V(3).Infof("ListAndWatch error (termination expected): %v: stopping all informers: %v", gkn, err) + w.handleFatalError(eventCh, err) + + // Unexpected error + default: + klog.Warningf("ListAndWatch error (retry expected): %v: %v", gkn, err) + } +} + +// informerReference tracks informer lifecycle. +type informerReference struct { + // lock guards the subsequent stateful fields + lock sync.Mutex + + informer cache.SharedIndexInformer + context context.Context + cancel context.CancelFunc + started bool +} + +// Start returns a wrapped context that can be cancelled. +// Returns nil & false if already started. +func (ir *informerReference) Start(ctx context.Context) (context.Context, bool) { + ir.lock.Lock() + defer ir.lock.Unlock() + + if ir.started { + return nil, false + } + + ctx, cancel := context.WithCancel(ctx) + ir.context = ctx + ir.cancel = cancel + ir.started = true + + return ctx, true +} + +func (ir *informerReference) SetInformer(informer cache.SharedIndexInformer) { + ir.lock.Lock() + defer ir.lock.Unlock() + + ir.informer = informer +} + +func (ir *informerReference) HasSynced() bool { + ir.lock.Lock() + defer ir.lock.Unlock() + + if !ir.started { + return false + } + if ir.informer == nil { + return false + } + return ir.informer.HasSynced() +} + +func (ir *informerReference) HasStarted() bool { + ir.lock.Lock() + defer ir.lock.Unlock() + + return ir.started +} + +// Stop cancels the context, if it's been started. +func (ir *informerReference) Stop() { + ir.lock.Lock() + defer ir.lock.Unlock() + + if !ir.started { + return + } + + ir.cancel() + ir.started = false + ir.context = nil +} + +type taskFunc func() + +// taskManager manages a set of tasks with object identifiers. +// This makes starting and stopping the tasks thread-safe. +type taskManager struct { + lock sync.Mutex + cancelFuncs map[object.ObjMetadata]context.CancelFunc +} + +func (tm *taskManager) Schedule(parentCtx context.Context, id object.ObjMetadata, delay time.Duration, task taskFunc) { + tm.lock.Lock() + defer tm.lock.Unlock() + + if tm.cancelFuncs == nil { + tm.cancelFuncs = make(map[object.ObjMetadata]context.CancelFunc) + } + + cancel, found := tm.cancelFuncs[id] + if found { + // Cancel the existing scheduled task and replace it. + cancel() + } + + taskCtx, cancel := context.WithTimeout(context.Background(), delay) + tm.cancelFuncs[id] = cancel + + go func() { + klog.V(5).Infof("Task scheduled (%v) for object (%s)", delay, id) + select { + case <-parentCtx.Done(): + // stop waiting + cancel() + case <-taskCtx.Done(): + if taskCtx.Err() == context.DeadlineExceeded { + klog.V(5).Infof("Task executing (after %v) for object (%v)", delay, id) + task() + } + // else stop waiting + } + }() +} + +func (tm *taskManager) Cancel(id object.ObjMetadata) { + tm.lock.Lock() + defer tm.lock.Unlock() + + cancelFunc, found := tm.cancelFuncs[id] + if !found { + // already cancelled or not added + return + } + delete(tm.cancelFuncs, id) + cancelFunc() + if len(tm.cancelFuncs) == 0 { + tm.cancelFuncs = nil + } +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/restscopestrategy_string.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/restscopestrategy_string.go new file mode 100644 index 00000000000..e2504b6e2b9 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/restscopestrategy_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -type=RESTScopeStrategy -linecomment"; DO NOT EDIT. + +package watcher + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[RESTScopeAutomatic-0] + _ = x[RESTScopeRoot-1] + _ = x[RESTScopeNamespace-2] +} + +const _RESTScopeStrategy_name = "automaticrootnamespace" + +var _RESTScopeStrategy_index = [...]uint8{0, 9, 13, 22} + +func (i RESTScopeStrategy) String() string { + if i < 0 || i >= RESTScopeStrategy(len(_RESTScopeStrategy_index)-1) { + return "RESTScopeStrategy(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _RESTScopeStrategy_name[_RESTScopeStrategy_index[i]:_RESTScopeStrategy_index[i+1]] +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/unschedulable.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/unschedulable.go new file mode 100644 index 00000000000..9ff8b508413 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/unschedulable.go @@ -0,0 +1,69 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package watcher + +import ( + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/kstatus/status" + "github.com/fluxcd/cli-utils/pkg/object" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// isObjectUnschedulable returns true if the object or any of its generated resources +// is an unschedulable pod. +// +// This status is computed recursively, so it can handle objects that generate +// objects that generate pods, as long as the input ResourceStatus has those +// GeneratedResources computed. +func isObjectUnschedulable(rs *event.ResourceStatus) bool { + if rs.Error != nil { + return false + } + if rs.Status != status.InProgressStatus { + return false + } + if isPodUnschedulable(rs.Resource) { + return true + } + // recurse through generated resources + for _, subRS := range rs.GeneratedResources { + if isObjectUnschedulable(subRS) { + return true + } + } + return false +} + +// isPodUnschedulable returns true if the object is a pod and is unschedulable +// according to a False PodScheduled condition. +func isPodUnschedulable(obj *unstructured.Unstructured) bool { + if obj == nil { + return false + } + gk := obj.GroupVersionKind().GroupKind() + if gk != (schema.GroupKind{Kind: "Pod"}) { + return false + } + icnds, found, err := object.NestedField(obj.Object, "status", "conditions") + if err != nil || !found { + return false + } + cnds, ok := icnds.([]interface{}) + if !ok { + return false + } + for _, icnd := range cnds { + cnd, ok := icnd.(map[string]interface{}) + if !ok { + return false + } + if cnd["type"] == "PodScheduled" && + cnd["status"] == "False" && + cnd["reason"] == "Unschedulable" { + return true + } + } + return false +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/watcher.go b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/watcher.go new file mode 100644 index 00000000000..b22774719b0 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/kstatus/watcher/watcher.go @@ -0,0 +1,38 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package watcher + +import ( + "context" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/object" +) + +// StatusWatcher watches a set of objects for status updates. +type StatusWatcher interface { + + // Watch a set of objects for status updates. + // Watching should stop if the context is cancelled. + // Events should only be sent for the specified objects. + // The event channel should be closed when the watching stops. + Watch(context.Context, object.ObjMetadataSet, Options) <-chan event.Event +} + +// Options can be provided when creating a new StatusWatcher to customize the +// behavior. +type Options struct { + // RESTScopeStrategy specifies which strategy to use when listing and + // watching resources. By default, the strategy is selected automatically. + RESTScopeStrategy RESTScopeStrategy +} + +//go:generate stringer -type=RESTScopeStrategy -linecomment +type RESTScopeStrategy int + +const ( + RESTScopeAutomatic RESTScopeStrategy = iota // automatic + RESTScopeRoot // root + RESTScopeNamespace // namespace +) diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/object/error.go b/vendor/github.com/fluxcd/cli-utils/pkg/object/error.go new file mode 100644 index 00000000000..ddd8591d1d3 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/object/error.go @@ -0,0 +1,24 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package object + +import ( + "fmt" +) + +// InvalidAnnotationError represents an invalid annotation. +// Fields are exposed to allow callers to perform introspection. +type InvalidAnnotationError struct { + Annotation string + Cause error +} + +func (iae InvalidAnnotationError) Error() string { + return fmt.Sprintf("invalid %q annotation: %v", + iae.Annotation, iae.Cause) +} + +func (iae InvalidAnnotationError) Unwrap() error { + return iae.Cause +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/object/field.go b/vendor/github.com/fluxcd/cli-utils/pkg/object/field.go new file mode 100644 index 00000000000..7d530b4f98d --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/object/field.go @@ -0,0 +1,114 @@ +// Copyright 2021 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package object + +import ( + "fmt" + "regexp" + "strings" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// NestedField gets a value from a KRM map, if it exists, otherwise nil. +// Fields can be string (map key) or int (array index). +func NestedField(obj map[string]interface{}, fields ...interface{}) (interface{}, bool, error) { + var val interface{} = obj + + for i, field := range fields { + if val == nil { + return nil, false, nil + } + switch typedField := field.(type) { + case string: + if m, ok := val.(map[string]interface{}); ok { + val, ok = m[typedField] + if !ok { + // not in map + return nil, false, nil + } + } else { + return nil, false, InvalidType(fields[:i+1], val, "map[string]interface{}") + } + case int: + if s, ok := val.([]interface{}); ok { + if typedField >= len(s) { + // index out of range + return nil, false, nil + } + val = s[typedField] + } else { + return nil, false, InvalidType(fields[:i+1], val, "[]interface{}") + } + default: + return nil, false, InvalidType(fields[:i+1], val, "string or int") + } + } + return val, true, nil +} + +// InvalidType returns a *Error indicating "invalid value type". This is used +// to report malformed values (e.g. found int, expected string). +func InvalidType(fieldPath []interface{}, value interface{}, validTypes string) *field.Error { + return Invalid(fieldPath, value, + fmt.Sprintf("found type %T, expected %s", value, validTypes)) +} + +// Invalid returns a *Error indicating "invalid value". This is used +// to report malformed values (e.g. failed regex match, too long, out of bounds). +func Invalid(fieldPath []interface{}, value interface{}, detail string) *field.Error { + return &field.Error{ + Type: field.ErrorTypeInvalid, + Field: FieldPath(fieldPath), + BadValue: value, + Detail: detail, + } +} + +// NotFound returns a *Error indicating "value not found". This is +// used to report failure to find a requested value (e.g. looking up an ID). +func NotFound(fieldPath []interface{}, value interface{}) *field.Error { + return &field.Error{ + Type: field.ErrorTypeNotFound, + Field: FieldPath(fieldPath), + BadValue: value, + Detail: "", + } +} + +// FieldPath formats a list of KRM field keys as a JSONPath expression. +// The only valid field keys in KRM are strings (map keys) and ints (list keys). +// Simple strings (see isSimpleString) will be delimited with a period. +// Complex strings will be wrapped with square brackets and double quotes. +// Integers will be wrapped with square brackets. +// All other types will be formatted best-effort within square brackets. +func FieldPath(fieldPath []interface{}) string { + var sb strings.Builder + for _, field := range fieldPath { + switch typedField := field.(type) { + case string: + if isSimpleString(typedField) { + _, _ = fmt.Fprintf(&sb, ".%s", typedField) + } else { + _, _ = fmt.Fprintf(&sb, "[%q]", typedField) + } + case int: + _, _ = fmt.Fprintf(&sb, "[%d]", typedField) + default: + // invalid type. try anyway... + _, _ = fmt.Fprintf(&sb, "[%#v]", typedField) + } + } + return sb.String() +} + +var simpleStringRegex = regexp.MustCompile(`^[a-zA-Z]([a-zA-Z0-9_-]*[a-zA-Z0-9])?$`) + +// isSimpleString returns true if the input follows the following rules: +// - contains only alphanumeric characters, '_' or '-' +// - starts with an alphabetic character +// - ends with an alphanumeric character +func isSimpleString(s string) bool { + return simpleStringRegex.FindString(s) != "" +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/object/infos.go b/vendor/github.com/fluxcd/cli-utils/pkg/object/infos.go new file mode 100644 index 00000000000..93b8e6cfc05 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/object/infos.go @@ -0,0 +1,91 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package object + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/cli-runtime/pkg/resource" + "sigs.k8s.io/kustomize/kyaml/kio/kioutil" +) + +// InfosToObjMetas returns object metadata (ObjMetadata) for the +// passed objects (infos); returns an error if one occurs. +func InfosToObjMetas(infos []*resource.Info) ([]ObjMetadata, error) { + objMetas := make([]ObjMetadata, 0, len(infos)) + for _, info := range infos { + objMeta, err := InfoToObjMeta(info) + if err != nil { + return nil, err + } + objMetas = append(objMetas, objMeta) + } + return objMetas, nil +} + +// InfoToObjMeta takes information from the provided info and +// returns an ObjMetadata that identifies the resource. +func InfoToObjMeta(info *resource.Info) (ObjMetadata, error) { + if info == nil || info.Object == nil { + return ObjMetadata{}, fmt.Errorf("attempting to transform info, but it is empty") + } + id := ObjMetadata{ + Namespace: info.Namespace, + Name: info.Name, + GroupKind: info.Object.GetObjectKind().GroupVersionKind().GroupKind(), + } + return id, nil +} + +// InfoToUnstructured transforms the passed info object into unstructured format. +func InfoToUnstructured(info *resource.Info) *unstructured.Unstructured { + return info.Object.(*unstructured.Unstructured) +} + +// UnstructuredToInfo transforms the passed Unstructured object into Info format, +// or an error if one occurs. +func UnstructuredToInfo(obj *unstructured.Unstructured) (*resource.Info, error) { + // make a copy of the input object to avoid modifying the input + obj = obj.DeepCopy() + + annos := obj.GetAnnotations() + + source := "unstructured" + path, ok := annos[kioutil.PathAnnotation] + if ok { + source = path + } + StripKyamlAnnotations(obj) + + return &resource.Info{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + Source: source, + Object: obj, + }, nil +} + +// InfosToUnstructureds transforms the passed objects in Info format to Unstructured. +func InfosToUnstructureds(infos []*resource.Info) []*unstructured.Unstructured { + var objs []*unstructured.Unstructured + for _, info := range infos { + objs = append(objs, InfoToUnstructured(info)) + } + return objs +} + +// UnstructuredsToInfos transforms the passed Unstructured objects into Info format +// or an error if one occurs. +func UnstructuredsToInfos(objs []*unstructured.Unstructured) ([]*resource.Info, error) { + var infos []*resource.Info + for _, obj := range objs { + inf, err := UnstructuredToInfo(obj) + if err != nil { + return infos, err + } + infos = append(infos, inf) + } + return infos, nil +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/object/objmetadata.go b/vendor/github.com/fluxcd/cli-utils/pkg/object/objmetadata.go new file mode 100644 index 00000000000..bc46cc38522 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/object/objmetadata.go @@ -0,0 +1,147 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 +// +// ObjMetadata is the minimal set of information to +// uniquely identify an object. The four fields are: +// +// Group/Kind (NOTE: NOT version) +// Namespace +// Name +// +// We specifically do not use the "version", because +// the APIServer does not recognize a version as a +// different resource. This metadata is used to identify +// resources for pruning and teardown. + +package object + +import ( + "fmt" + "strings" + + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +const ( + // Separates inventory fields. This string is allowable as a + // ConfigMap key, but it is not allowed as a character in + // resource name. + fieldSeparator = "_" + // Transform colons in the RBAC resource names to double + // underscore. + colonTranscoded = "__" +) + +var ( + NilObjMetadata = ObjMetadata{} +) + +// RBACGroupKind is a map of the RBAC resources. Needed since name validation +// is different than other k8s resources. +var RBACGroupKind = map[schema.GroupKind]bool{ + {Group: rbacv1.GroupName, Kind: "Role"}: true, + {Group: rbacv1.GroupName, Kind: "ClusterRole"}: true, + {Group: rbacv1.GroupName, Kind: "RoleBinding"}: true, + {Group: rbacv1.GroupName, Kind: "ClusterRoleBinding"}: true, +} + +// ObjMetadata organizes and stores the indentifying information +// for an object. This struct (as a string) is stored in a +// inventory object to keep track of sets of applied objects. +type ObjMetadata struct { + Namespace string + Name string + GroupKind schema.GroupKind +} + +// ParseObjMetadata takes a string, splits it into its four fields, +// and returns an ObjMetadata struct storing the four fields. +// Example inventory string: +// +// test-namespace_test-name_apps_ReplicaSet +// +// Returns an error if unable to parse and create the ObjMetadata struct. +// +// NOTE: name field can contain double underscore (__), which represents +// a colon. RBAC resources can have this additional character (:) in their name. +func ParseObjMetadata(s string) (ObjMetadata, error) { + // Parse first field namespace + index := strings.Index(s, fieldSeparator) + if index == -1 { + return NilObjMetadata, fmt.Errorf("unable to parse stored object metadata: %s", s) + } + namespace := s[:index] + s = s[index+1:] + // Next, parse last field kind + index = strings.LastIndex(s, fieldSeparator) + if index == -1 { + return NilObjMetadata, fmt.Errorf("unable to parse stored object metadata: %s", s) + } + kind := s[index+1:] + s = s[:index] + // Next, parse next to last field group + index = strings.LastIndex(s, fieldSeparator) + if index == -1 { + return NilObjMetadata, fmt.Errorf("unable to parse stored object metadata: %s", s) + } + group := s[index+1:] + // Finally, second field name. Name may contain colon transcoded as double underscore. + name := s[:index] + name = strings.ReplaceAll(name, colonTranscoded, ":") + // Check that there are no extra fields by search for fieldSeparator. + if strings.Contains(name, fieldSeparator) { + return NilObjMetadata, fmt.Errorf("too many fields within: %s", s) + } + // Create the ObjMetadata object from the four parsed fields. + id := ObjMetadata{ + Namespace: namespace, + Name: name, + GroupKind: schema.GroupKind{ + Group: group, + Kind: kind, + }, + } + return id, nil +} + +// Equals compares two ObjMetadata and returns true if they are equal. This does +// not contain any special treatment for the extensions API group. +func (o *ObjMetadata) Equals(other *ObjMetadata) bool { + if other == nil { + return false + } + return *o == *other +} + +// String create a string version of the ObjMetadata struct. For RBAC resources, +// the "name" field transcodes ":" into double underscore for valid storing +// as the label of a ConfigMap. +func (o ObjMetadata) String() string { + name := o.Name + if _, exists := RBACGroupKind[o.GroupKind]; exists { + name = strings.ReplaceAll(name, ":", colonTranscoded) + } + return fmt.Sprintf("%s%s%s%s%s%s%s", + o.Namespace, fieldSeparator, + name, fieldSeparator, + o.GroupKind.Group, fieldSeparator, + o.GroupKind.Kind) +} + +// RuntimeToObjMeta extracts the object metadata information from a +// runtime.Object and returns it as ObjMetadata. +func RuntimeToObjMeta(obj runtime.Object) (ObjMetadata, error) { + accessor, err := meta.Accessor(obj) + if err != nil { + return NilObjMetadata, err + } + id := ObjMetadata{ + Namespace: accessor.GetNamespace(), + Name: accessor.GetName(), + GroupKind: obj.GetObjectKind().GroupVersionKind().GroupKind(), + } + return id, nil +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/object/objmetadata_set.go b/vendor/github.com/fluxcd/cli-utils/pkg/object/objmetadata_set.go new file mode 100644 index 00000000000..31a8b473fae --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/object/objmetadata_set.go @@ -0,0 +1,211 @@ +// Copyright 2021 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 +// + +package object + +import ( + "hash/fnv" + "sort" + "strconv" +) + +// ObjMetadataSet is an ordered list of ObjMetadata that acts like an unordered +// set for comparison purposes. +type ObjMetadataSet []ObjMetadata + +// UnstructuredSetEquals returns true if the slice of objects in setA equals +// the slice of objects in setB. +func ObjMetadataSetEquals(setA []ObjMetadata, setB []ObjMetadata) bool { + return ObjMetadataSet(setA).Equal(ObjMetadataSet(setB)) +} + +// ObjMetadataSetFromMap constructs a set from a map +func ObjMetadataSetFromMap(mapA map[ObjMetadata]struct{}) ObjMetadataSet { + setA := make(ObjMetadataSet, 0, len(mapA)) + for f := range mapA { + setA = append(setA, f) + } + return setA +} + +// Equal returns true if the two sets contain equivalent objects. Duplicates are +// ignored. +// This function satisfies the cmp.Equal interface from github.com/google/go-cmp +func (setA ObjMetadataSet) Equal(setB ObjMetadataSet) bool { + mapA := make(map[ObjMetadata]struct{}, len(setA)) + for _, a := range setA { + mapA[a] = struct{}{} + } + mapB := make(map[ObjMetadata]struct{}, len(setB)) + for _, b := range setB { + mapB[b] = struct{}{} + } + if len(mapA) != len(mapB) { + return false + } + for b := range mapB { + if _, exists := mapA[b]; !exists { + return false + } + } + return true +} + +// Contains checks if the provided ObjMetadata exists in the set. +func (setA ObjMetadataSet) Contains(id ObjMetadata) bool { + for _, om := range setA { + if om == id { + return true + } + } + return false +} + +// Remove the object from the set and return the updated set. +func (setA ObjMetadataSet) Remove(obj ObjMetadata) ObjMetadataSet { + for i, a := range setA { + if a == obj { + setA[len(setA)-1], setA[i] = setA[i], setA[len(setA)-1] + return setA[:len(setA)-1] + } + } + return setA +} + +// Intersection returns the set of unique objects in both set A and set B. +func (setA ObjMetadataSet) Intersection(setB ObjMetadataSet) ObjMetadataSet { + var maxlen int + if len(setA) > len(setB) { + maxlen = len(setA) + } else { + maxlen = len(setB) + } + mapI := make(map[ObjMetadata]struct{}, maxlen) + mapB := setB.ToMap() + for _, a := range setA { + if _, ok := mapB[a]; ok { + mapI[a] = struct{}{} + } + } + intersection := make(ObjMetadataSet, 0, len(mapI)) + // Iterate over setA & setB to retain input order and have stable output + for _, id := range setA { + if _, ok := mapI[id]; ok { + intersection = append(intersection, id) + delete(mapI, id) + } + } + for _, id := range setB { + if _, ok := mapI[id]; ok { + intersection = append(intersection, id) + delete(mapI, id) + } + } + return intersection +} + +// Union returns the set of unique objects from the merging of set A and set B. +func (setA ObjMetadataSet) Union(setB ObjMetadataSet) ObjMetadataSet { + m := make(map[ObjMetadata]struct{}, len(setA)+len(setB)) + for _, a := range setA { + m[a] = struct{}{} + } + for _, b := range setB { + m[b] = struct{}{} + } + union := make(ObjMetadataSet, 0, len(m)) + // Iterate over setA & setB to retain input order and have stable output + for _, id := range setA { + if _, ok := m[id]; ok { + union = append(union, id) + delete(m, id) + } + } + for _, id := range setB { + if _, ok := m[id]; ok { + union = append(union, id) + delete(m, id) + } + } + return union +} + +// Diff returns the set of objects that exist in set A, but not in set B (A - B). +func (setA ObjMetadataSet) Diff(setB ObjMetadataSet) ObjMetadataSet { + // Create a map of the elements of A + m := make(map[ObjMetadata]struct{}, len(setA)) + for _, a := range setA { + m[a] = struct{}{} + } + // Remove from A each element of B + for _, b := range setB { + delete(m, b) // OK to delete even if b not in m + } + // Create/return slice from the map of remaining items + diff := make(ObjMetadataSet, 0, len(m)) + // Iterate over setA to retain input order and have stable output + for _, id := range setA { + if _, ok := m[id]; ok { + diff = append(diff, id) + delete(m, id) + } + } + return diff +} + +// Unique returns the set with duplicates removed. +// Order may or may not remain consistent. +func (setA ObjMetadataSet) Unique() ObjMetadataSet { + return ObjMetadataSetFromMap(setA.ToMap()) +} + +// Hash the objects in the set by serializing, sorting, concatonating, and +// hashing the result with the 32-bit FNV-1a algorithm. +func (setA ObjMetadataSet) Hash() string { + objStrs := make([]string, 0, len(setA)) + for _, obj := range setA { + objStrs = append(objStrs, obj.String()) + } + sort.Strings(objStrs) + h := fnv.New32a() + for _, obj := range objStrs { + // Hash32.Write never returns an error + // https://pkg.go.dev/hash#pkg-types + _, _ = h.Write([]byte(obj)) + } + return strconv.FormatUint(uint64(h.Sum32()), 16) +} + +// ToMap returns the set as a map, with objMeta keys and empty struct values. +func (setA ObjMetadataSet) ToMap() map[ObjMetadata]struct{} { + m := make(map[ObjMetadata]struct{}, len(setA)) + for _, objMeta := range setA { + m[objMeta] = struct{}{} + } + return m +} + +// ToStringMap returns the set as a serializable map, with objMeta keys and +// empty string values. +func (setA ObjMetadataSet) ToStringMap() map[string]string { + stringMap := make(map[string]string, len(setA)) + for _, objMeta := range setA { + stringMap[objMeta.String()] = "" + } + return stringMap +} + +// FromStringMap returns a set from a serializable map, with objMeta keys and +// empty string values. Errors if parsing fails. +func FromStringMap(in map[string]string) (ObjMetadataSet, error) { + var set ObjMetadataSet + for s := range in { + objMeta, err := ParseObjMetadata(s) + if err != nil { + return nil, err + } + set = append(set, objMeta) + } + return set, nil +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/object/strings.go b/vendor/github.com/fluxcd/cli-utils/pkg/object/strings.go new file mode 100644 index 00000000000..9a2196c6456 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/object/strings.go @@ -0,0 +1,27 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package object + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/yaml" +) + +// YamlStringer delays YAML marshalling for logging until String() is called. +type YamlStringer struct { + O *unstructured.Unstructured +} + +// String marshals the wrapped object to a YAML string. If serializing errors, +// the error string will be returned instead. This is primarily for use with +// verbose logging. +func (ys YamlStringer) String() string { + yamlBytes, err := yaml.Marshal(ys.O) + if err != nil { + return fmt.Sprintf("<>", err) + } + return string(yamlBytes) +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/object/unstructured.go b/vendor/github.com/fluxcd/cli-utils/pkg/object/unstructured.go new file mode 100644 index 00000000000..57a16d064d6 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/object/unstructured.go @@ -0,0 +1,214 @@ +// Copyright 2021 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 +// + +package object + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/kustomize/kyaml/kio/kioutil" +) + +var ( + namespaceGK = schema.GroupKind{Group: "", Kind: "Namespace"} + crdGK = schema.GroupKind{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"} +) + +// UnstructuredSetToObjMetadataSet converts a UnstructuredSet to a ObjMetadataSet. +func UnstructuredSetToObjMetadataSet(objs UnstructuredSet) ObjMetadataSet { + objMetas := make([]ObjMetadata, len(objs)) + for i, obj := range objs { + objMetas[i] = UnstructuredToObjMetadata(obj) + } + return objMetas +} + +// UnstructuredToObjMetadata extracts the identifying information from an +// Unstructured object and returns it as ObjMetadata object. +func UnstructuredToObjMetadata(obj *unstructured.Unstructured) ObjMetadata { + return ObjMetadata{ + Namespace: obj.GetNamespace(), + Name: obj.GetName(), + GroupKind: obj.GroupVersionKind().GroupKind(), + } +} + +// IsKindNamespace returns true if the passed Unstructured object is +// GroupKind == Core/Namespace (no version checked); false otherwise. +func IsKindNamespace(u *unstructured.Unstructured) bool { + if u == nil { + return false + } + gvk := u.GroupVersionKind() + return namespaceGK == gvk.GroupKind() +} + +// IsNamespaced returns true if the passed Unstructured object +// is namespace-scoped (not cluster-scoped); false otherwise. +func IsNamespaced(u *unstructured.Unstructured) bool { + if u == nil { + return false + } + return u.GetNamespace() != "" +} + +// IsNamespace returns true if the passed Unstructured object +// is Namespace in the core (empty string) group. +func IsNamespace(u *unstructured.Unstructured) bool { + if u == nil { + return false + } + gvk := u.GroupVersionKind() + // core group, any version + return gvk.Group == "" && gvk.Kind == "Namespace" +} + +// IsCRD returns true if the passed Unstructured object has +// GroupKind == Extensions/CustomResourceDefinition; false otherwise. +func IsCRD(u *unstructured.Unstructured) bool { + if u == nil { + return false + } + gvk := u.GroupVersionKind() + return crdGK == gvk.GroupKind() +} + +// GetCRDGroupKind returns the GroupKind stored in the passed +// Unstructured CustomResourceDefinition and true if the passed object +// is a CRD. +func GetCRDGroupKind(u *unstructured.Unstructured) (schema.GroupKind, bool) { + emptyGroupKind := schema.GroupKind{Group: "", Kind: ""} + if u == nil { + return emptyGroupKind, false + } + group, found, err := unstructured.NestedString(u.Object, "spec", "group") + if found && err == nil { + kind, found, err := unstructured.NestedString(u.Object, "spec", "names", "kind") + if found && err == nil { + return schema.GroupKind{Group: group, Kind: kind}, true + } + } + return emptyGroupKind, false +} + +// UnknownTypeError captures information about a type for which no information +// could be found in the cluster or among the known CRDs. +type UnknownTypeError struct { + GroupVersionKind schema.GroupVersionKind +} + +func (e *UnknownTypeError) Error() string { + return fmt.Sprintf("unknown resource type: %q", e.GroupVersionKind.String()) +} + +// LookupResourceScope tries to look up the scope of the type of the provided +// resource, looking at both the types known to the cluster (through the +// RESTMapper) and the provided CRDs. If no information about the type can +// be found, an UnknownTypeError wil be returned. +func LookupResourceScope(u *unstructured.Unstructured, crds []*unstructured.Unstructured, mapper meta.RESTMapper) (meta.RESTScope, error) { + gvk := u.GroupVersionKind() + // First see if we can find the type (and the scope) in the cluster through + // the RESTMapper. + mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version) + if err == nil { + // If we find the type in the cluster, we just look up the scope there. + return mapping.Scope, nil + } + // Not finding a match is not an error here, so only error out for other + // error types. + if !meta.IsNoMatchError(err) { + return nil, err + } + + // If we couldn't find the type in the cluster, check if we find a + // match in any of the provided CRDs. + for _, crd := range crds { + group, found, err := NestedField(crd.Object, "spec", "group") + if err != nil { + return nil, err + } + if !found || group == "" { + return nil, NotFound([]interface{}{"spec", "group"}, group) + } + kind, found, err := NestedField(crd.Object, "spec", "names", "kind") + if err != nil { + return nil, err + } + if !found || kind == "" { + return nil, NotFound([]interface{}{"spec", "kind"}, group) + } + if gvk.Kind != kind || gvk.Group != group { + continue + } + versionDefined, err := crdDefinesVersion(crd, gvk.Version) + if err != nil { + return nil, err + } + if !versionDefined { + return nil, &UnknownTypeError{ + GroupVersionKind: gvk, + } + } + scopeName, _, err := NestedField(crd.Object, "spec", "scope") + if err != nil { + return nil, err + } + switch scopeName { + case "Namespaced": + return meta.RESTScopeNamespace, nil + case "Cluster": + return meta.RESTScopeRoot, nil + default: + return nil, Invalid([]interface{}{"spec", "scope"}, scopeName, + "expected Namespaced or Cluster") + } + } + return nil, &UnknownTypeError{ + GroupVersionKind: gvk, + } +} + +func crdDefinesVersion(crd *unstructured.Unstructured, version string) (bool, error) { + versions, found, err := NestedField(crd.Object, "spec", "versions") + if err != nil { + return false, err + } + if !found { + return false, NotFound([]interface{}{"spec", "versions"}, versions) + } + versionsSlice, ok := versions.([]interface{}) + if !ok { + return false, InvalidType([]interface{}{"spec", "versions"}, versions, "[]interface{}") + } + if len(versionsSlice) == 0 { + return false, Invalid([]interface{}{"spec", "versions"}, versionsSlice, "must not be empty") + } + for i := range versionsSlice { + name, found, err := NestedField(crd.Object, "spec", "versions", i, "name") + if err != nil { + return false, err + } + if !found { + return false, NotFound([]interface{}{"spec", "versions", i, "name"}, name) + } + if name == version { + return true, nil + } + } + return false, nil +} + +// StripKyamlAnnotations removes any path and index annotations from the +// unstructured resource. +func StripKyamlAnnotations(u *unstructured.Unstructured) { + annos := u.GetAnnotations() + delete(annos, kioutil.PathAnnotation) + delete(annos, kioutil.LegacyPathAnnotation) //nolint:staticcheck + delete(annos, kioutil.IndexAnnotation) + delete(annos, kioutil.LegacyIndexAnnotation) //nolint:staticcheck + u.SetAnnotations(annos) +} diff --git a/vendor/github.com/fluxcd/cli-utils/pkg/object/unstructured_set.go b/vendor/github.com/fluxcd/cli-utils/pkg/object/unstructured_set.go new file mode 100644 index 00000000000..2ce830265a9 --- /dev/null +++ b/vendor/github.com/fluxcd/cli-utils/pkg/object/unstructured_set.go @@ -0,0 +1,54 @@ +// Copyright 2021 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 +// + +package object + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +// UnstructuredSet is an ordered list of Unstructured that acts like an +// unordered set for comparison purposes. +type UnstructuredSet []*unstructured.Unstructured + +// UnstructuredSetEquals returns true if the slice of objects in setA equals +// the slice of objects in setB. +func UnstructuredSetEquals(setA []*unstructured.Unstructured, setB []*unstructured.Unstructured) bool { + return UnstructuredSet(setA).Equal(UnstructuredSet(setB)) +} + +func (setA UnstructuredSet) Equal(setB UnstructuredSet) bool { + mapA := make(map[string]string, len(setA)) + for _, a := range setA { + jsonBytes, err := a.MarshalJSON() + if err != nil { + mapA[string(jsonBytes)] = err.Error() + } else { + mapA[string(jsonBytes)] = "" + } + } + mapB := make(map[string]string, len(setB)) + for _, b := range setB { + jsonBytes, err := b.MarshalJSON() + if err != nil { + mapB[string(jsonBytes)] = err.Error() + } else { + mapB[string(jsonBytes)] = "" + } + } + if len(mapA) != len(mapB) { + return false + } + for b, errB := range mapB { + if errA, exists := mapA[b]; !exists { + if !exists { + return false + } + if errA != errB { + return false + } + } + } + return true +} diff --git a/vendor/github.com/go-errors/errors/.travis.yml b/vendor/github.com/go-errors/errors/.travis.yml index 77a6bccf77e..1dc296026a7 100644 --- a/vendor/github.com/go-errors/errors/.travis.yml +++ b/vendor/github.com/go-errors/errors/.travis.yml @@ -2,7 +2,6 @@ language: go go: - "1.8.x" - - "1.10.x" - - "1.13.x" - - "1.14.x" + - "1.11.x" - "1.16.x" + - "1.21.x" diff --git a/vendor/github.com/go-errors/errors/README.md b/vendor/github.com/go-errors/errors/README.md index 3d78525940a..558bc883ef9 100644 --- a/vendor/github.com/go-errors/errors/README.md +++ b/vendor/github.com/go-errors/errors/README.md @@ -9,7 +9,7 @@ This is particularly useful when you want to understand the state of execution when an error was returned unexpectedly. It provides the type \*Error which implements the standard golang error -interface, so you can use this library interchangably with code that is +interface, so you can use this library interchangeably with code that is expecting a normal error return. Usage @@ -80,3 +80,5 @@ This package is licensed under the MIT license, see LICENSE.MIT for details. * v1.4.0 *BREAKING* v1.4.0 reverted all changes from v1.3.0 and is identical to v1.2.0 * v1.4.1 no code change, but now without an unnecessary cover.out file. * v1.4.2 performance improvement to ErrorStack() to avoid unnecessary work https://github.com/go-errors/errors/pull/40 +* v1.5.0 add errors.Join() and errors.Unwrap() copying the stdlib https://github.com/go-errors/errors/pull/40 +* v1.5.1 fix build on go1.13..go1.19 (broken by adding Join and Unwrap with wrong build constraints) diff --git a/vendor/github.com/go-errors/errors/error_1_13.go b/vendor/github.com/go-errors/errors/error_1_13.go index 0af2fc80659..34ab3e00eb5 100644 --- a/vendor/github.com/go-errors/errors/error_1_13.go +++ b/vendor/github.com/go-errors/errors/error_1_13.go @@ -1,3 +1,4 @@ +//go:build go1.13 // +build go1.13 package errors @@ -6,14 +7,17 @@ import ( baseErrors "errors" ) -// find error in any wrapped error +// As finds the first error in err's tree that matches target, and if one is found, sets +// target to that error value and returns true. Otherwise, it returns false. +// +// For more information see stdlib errors.As. func As(err error, target interface{}) bool { return baseErrors.As(err, target) } // Is detects whether the error is equal to a given error. Errors // are considered equal by this function if they are matched by errors.Is -// or if their contained errors are matched through errors.Is +// or if their contained errors are matched through errors.Is. func Is(e error, original error) bool { if baseErrors.Is(e, original) { return true diff --git a/vendor/github.com/go-errors/errors/error_backward.go b/vendor/github.com/go-errors/errors/error_backward.go index 80b0695e7e8..ff14c4bfa27 100644 --- a/vendor/github.com/go-errors/errors/error_backward.go +++ b/vendor/github.com/go-errors/errors/error_backward.go @@ -1,3 +1,4 @@ +//go:build !go1.13 // +build !go1.13 package errors @@ -55,3 +56,70 @@ func Is(e error, original error) bool { return false } + +// Disclaimer: functions Join and Unwrap are copied from the stdlib errors +// package v1.21.0. + +// Join returns an error that wraps the given errors. +// Any nil error values are discarded. +// Join returns nil if every value in errs is nil. +// The error formats as the concatenation of the strings obtained +// by calling the Error method of each element of errs, with a newline +// between each string. +// +// A non-nil error returned by Join implements the Unwrap() []error method. +func Join(errs ...error) error { + n := 0 + for _, err := range errs { + if err != nil { + n++ + } + } + if n == 0 { + return nil + } + e := &joinError{ + errs: make([]error, 0, n), + } + for _, err := range errs { + if err != nil { + e.errs = append(e.errs, err) + } + } + return e +} + +type joinError struct { + errs []error +} + +func (e *joinError) Error() string { + var b []byte + for i, err := range e.errs { + if i > 0 { + b = append(b, '\n') + } + b = append(b, err.Error()...) + } + return string(b) +} + +func (e *joinError) Unwrap() []error { + return e.errs +} + +// Unwrap returns the result of calling the Unwrap method on err, if err's +// type contains an Unwrap method returning error. +// Otherwise, Unwrap returns nil. +// +// Unwrap only calls a method of the form "Unwrap() error". +// In particular Unwrap does not unwrap errors returned by [Join]. +func Unwrap(err error) error { + u, ok := err.(interface { + Unwrap() error + }) + if !ok { + return nil + } + return u.Unwrap() +} diff --git a/vendor/github.com/go-errors/errors/join_unwrap_1_20.go b/vendor/github.com/go-errors/errors/join_unwrap_1_20.go new file mode 100644 index 00000000000..44df35ece92 --- /dev/null +++ b/vendor/github.com/go-errors/errors/join_unwrap_1_20.go @@ -0,0 +1,32 @@ +//go:build go1.20 +// +build go1.20 + +package errors + +import baseErrors "errors" + +// Join returns an error that wraps the given errors. +// Any nil error values are discarded. +// Join returns nil if every value in errs is nil. +// The error formats as the concatenation of the strings obtained +// by calling the Error method of each element of errs, with a newline +// between each string. +// +// A non-nil error returned by Join implements the Unwrap() []error method. +// +// For more information see stdlib errors.Join. +func Join(errs ...error) error { + return baseErrors.Join(errs...) +} + +// Unwrap returns the result of calling the Unwrap method on err, if err's +// type contains an Unwrap method returning error. +// Otherwise, Unwrap returns nil. +// +// Unwrap only calls a method of the form "Unwrap() error". +// In particular Unwrap does not unwrap errors returned by [Join]. +// +// For more information see stdlib errors.Unwrap. +func Unwrap(err error) error { + return baseErrors.Unwrap(err) +} diff --git a/vendor/github.com/go-errors/errors/join_unwrap_backward.go b/vendor/github.com/go-errors/errors/join_unwrap_backward.go new file mode 100644 index 00000000000..50c766976c8 --- /dev/null +++ b/vendor/github.com/go-errors/errors/join_unwrap_backward.go @@ -0,0 +1,71 @@ +//go:build !go1.20 +// +build !go1.20 + +package errors + +// Disclaimer: functions Join and Unwrap are copied from the stdlib errors +// package v1.21.0. + +// Join returns an error that wraps the given errors. +// Any nil error values are discarded. +// Join returns nil if every value in errs is nil. +// The error formats as the concatenation of the strings obtained +// by calling the Error method of each element of errs, with a newline +// between each string. +// +// A non-nil error returned by Join implements the Unwrap() []error method. +func Join(errs ...error) error { + n := 0 + for _, err := range errs { + if err != nil { + n++ + } + } + if n == 0 { + return nil + } + e := &joinError{ + errs: make([]error, 0, n), + } + for _, err := range errs { + if err != nil { + e.errs = append(e.errs, err) + } + } + return e +} + +type joinError struct { + errs []error +} + +func (e *joinError) Error() string { + var b []byte + for i, err := range e.errs { + if i > 0 { + b = append(b, '\n') + } + b = append(b, err.Error()...) + } + return string(b) +} + +func (e *joinError) Unwrap() []error { + return e.errs +} + +// Unwrap returns the result of calling the Unwrap method on err, if err's +// type contains an Unwrap method returning error. +// Otherwise, Unwrap returns nil. +// +// Unwrap only calls a method of the form "Unwrap() error". +// In particular Unwrap does not unwrap errors returned by [Join]. +func Unwrap(err error) error { + u, ok := err.(interface { + Unwrap() error + }) + if !ok { + return nil + } + return u.Unwrap() +} diff --git a/vendor/github.com/gofrs/flock/.gitignore b/vendor/github.com/gofrs/flock/.gitignore new file mode 100644 index 00000000000..daf913b1b34 --- /dev/null +++ b/vendor/github.com/gofrs/flock/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/gofrs/flock/.golangci.yml b/vendor/github.com/gofrs/flock/.golangci.yml new file mode 100644 index 00000000000..bc837b266a9 --- /dev/null +++ b/vendor/github.com/gofrs/flock/.golangci.yml @@ -0,0 +1,116 @@ +version: "2" + +formatters: + enable: + - gofumpt + - goimports + settings: + gofumpt: + extra-rules: true + +linters: + enable: + - asasalint + - bidichk + - dogsled + - dupword + - durationcheck + - err113 + - errname + - errorlint + - fatcontext + - forbidigo + - gocheckcompilerdirectives + - gochecknoinits + - gocritic + - godot + - godox + - goheader + - gomoddirectives + - goprintffuncname + - gosec + - inamedparam + - interfacebloat + - ireturn + - mirror + - misspell + - nolintlint + - revive + - staticcheck + - testifylint + - thelper + - unconvert + - unparam + - usestdlibvars + - whitespace + - wsl_v5 + settings: + gocritic: + disabled-checks: + - paramTypeCombine # already handle by gofumpt.extra-rules + - whyNoLint # already handle by nonolint + - unnamedResult + - hugeParam + - sloppyReassign + - rangeValCopy + - octalLiteral + - ptrToRefParam + - appendAssign + - ruleguard + - httpNoBody + - exposedSyncMutex + enabled-tags: + - diagnostic + - style + - performance + godox: + keywords: + - FIXME + goheader: + template: |- + Copyright 2015 Tim Heckman. All rights reserved. + Copyright 2018-{{ YEAR }} The Gofrs. All rights reserved. + Use of this source code is governed by the BSD 3-Clause + license that can be found in the LICENSE file. + gosec: + excludes: + - G115 + misspell: + locale: US + revive: + rules: + - name: struct-tag + - name: blank-imports + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + - name: error-return + - name: error-strings + - name: error-naming + - name: exported + - name: if-return + - name: increment-decrement + - name: var-naming + - name: var-declaration + - name: package-comments + - name: range + - name: receiver-naming + - name: time-naming + - name: unexported-return + - name: indent-error-flow + - name: errorf + - name: empty-block + - name: superfluous-else + - name: unused-parameter + - name: unreachable-code + - name: redefines-builtin-id + exclusions: + presets: + - comments + - common-false-positives + - std-error-handling + +issues: + max-issues-per-linter: 0 + max-same-issues: 0 + diff --git a/vendor/github.com/gofrs/flock/LICENSE b/vendor/github.com/gofrs/flock/LICENSE new file mode 100644 index 00000000000..c785e5e4b24 --- /dev/null +++ b/vendor/github.com/gofrs/flock/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2018-2025, The Gofrs +Copyright (c) 2015-2020, Tim Heckman +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of gofrs nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/gofrs/flock/Makefile b/vendor/github.com/gofrs/flock/Makefile new file mode 100644 index 00000000000..65c139d68c0 --- /dev/null +++ b/vendor/github.com/gofrs/flock/Makefile @@ -0,0 +1,15 @@ +.PHONY: lint test test_race build_cross_os + +default: lint test build_cross_os + +test: + go test -v -cover ./... + +test_race: + CGO_ENABLED=1 go test -v -race ./... + +lint: + golangci-lint run + +build_cross_os: + ./build.sh diff --git a/vendor/github.com/gofrs/flock/README.md b/vendor/github.com/gofrs/flock/README.md new file mode 100644 index 00000000000..f7ca0dd9c24 --- /dev/null +++ b/vendor/github.com/gofrs/flock/README.md @@ -0,0 +1,45 @@ +# flock + +[![Go Reference](https://pkg.go.dev/badge/github.com/gofrs/flock.svg)](https://pkg.go.dev/github.com/gofrs/flock) +[![License](https://img.shields.io/badge/license-BSD_3--Clause-brightgreen.svg?style=flat)](https://github.com/gofrs/flock/blob/main/LICENSE) +[![Go Report Card](https://goreportcard.com/badge/github.com/gofrs/flock)](https://goreportcard.com/report/github.com/gofrs/flock) + +`flock` implements a thread-safe file lock. + +It also includes a non-blocking `TryLock()` function to allow locking without blocking execution. + +## Installation + +```bash +go get -u github.com/gofrs/flock +``` + +## Usage + +```go +import "github.com/gofrs/flock" + +fileLock := flock.New("/var/lock/go-lock.lock") + +locked, err := fileLock.TryLock() + +if err != nil { + // handle locking error +} + +if locked { + // do work + fileLock.Unlock() +} +``` + +For more detailed usage information take a look at the package API docs on +[GoDoc](https://pkg.go.dev/github.com/gofrs/flock). + +## License + +`flock` is released under the BSD 3-Clause License. See the [`LICENSE`](./LICENSE) file for more details. + +## Project History + +This project was originally `github.com/theckman/go-flock`, it was transferred to Gofrs by the original author [Tim Heckman ](https://github.com/theckman). diff --git a/vendor/github.com/gofrs/flock/SECURITY.md b/vendor/github.com/gofrs/flock/SECURITY.md new file mode 100644 index 00000000000..01419bd592a --- /dev/null +++ b/vendor/github.com/gofrs/flock/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +## Supported Versions + +We support the latest version of this library. +We do not guarantee support of previous versions. + +If a defect is reported, it will generally be fixed on the latest version (provided it exists) irrespective of whether it was introduced in a prior version. + +## Reporting a Vulnerability + +To report a potential security vulnerability, please create a [security advisory](https://github.com/gofrs/flock/security/advisories/new). + +For us to respond to your report most effectively, please include any of the following: + +- Steps to reproduce or a proof-of-concept +- Any relevant information, including the versions used + +## Security Scorecard + +This project submits security [results](https://scorecard.dev/viewer/?uri=github.com/gofrs/flock) to the [OpenSSF Scorecard](https://securityscorecards.dev/). diff --git a/vendor/github.com/gofrs/flock/build.sh b/vendor/github.com/gofrs/flock/build.sh new file mode 100644 index 00000000000..60f7809f065 --- /dev/null +++ b/vendor/github.com/gofrs/flock/build.sh @@ -0,0 +1,18 @@ +#!/bin/bash -e + +# Not supported by flock: +# - plan9/* +# - js/wasm +# - wasp1/wasm + +for row in $(go tool dist list -json | jq -r '.[] | @base64'); do + _jq() { + echo ${row} | base64 --decode | jq -r ${1} + } + + GOOS=$(_jq '.GOOS') + GOARCH=$(_jq '.GOARCH') + + echo "$GOOS/$GOARCH" + GOOS=$GOOS GOARCH=$GOARCH go build +done diff --git a/vendor/github.com/gofrs/flock/flock.go b/vendor/github.com/gofrs/flock/flock.go new file mode 100644 index 00000000000..4cb0746a716 --- /dev/null +++ b/vendor/github.com/gofrs/flock/flock.go @@ -0,0 +1,222 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Copyright 2018-2025 The Gofrs. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +// Package flock implements a thread-safe interface for file locking. +// It also includes a non-blocking TryLock() function to allow locking +// without blocking execution. +// +// Package flock is released under the BSD 3-Clause License. See the LICENSE file +// for more details. +// +// While using this library, remember that the locking behaviors are not +// guaranteed to be the same on each platform. For example, some UNIX-like +// operating systems will transparently convert a shared lock to an exclusive +// lock. If you Unlock() the flock from a location where you believe that you +// have the shared lock, you may accidentally drop the exclusive lock. +package flock + +import ( + "context" + "io/fs" + "os" + "runtime" + "sync" + "time" +) + +type Option func(f *Flock) + +// SetFlag sets the flag used to create/open the file. +func SetFlag(flag int) Option { + return func(f *Flock) { + f.flag = flag + } +} + +// SetPermissions sets the OS permissions to set on the file. +func SetPermissions(perm fs.FileMode) Option { + return func(f *Flock) { + f.perm = perm + } +} + +// Flock is the struct type to handle file locking. All fields are unexported, +// with access to some of the fields provided by getter methods (Path() and Locked()). +type Flock struct { + path string + m sync.RWMutex + fh *os.File + l bool + r bool + + // flag is the flag used to create/open the file. + flag int + // perm is the OS permissions to set on the file. + perm fs.FileMode +} + +// New returns a new instance of *Flock. The only parameter +// it takes is the path to the desired lockfile. +func New(path string, opts ...Option) *Flock { + // create it if it doesn't exist, and open the file read-only. + flags := os.O_CREATE + + switch runtime.GOOS { + case "aix", "solaris", "illumos": + // AIX cannot preform write-lock (i.e. exclusive) on a read-only file. + flags |= os.O_RDWR + default: + flags |= os.O_RDONLY + } + + f := &Flock{ + path: path, + flag: flags, + perm: fs.FileMode(0o600), + } + + for _, opt := range opts { + opt(f) + } + + return f +} + +// NewFlock returns a new instance of *Flock. The only parameter +// it takes is the path to the desired lockfile. +// +// Deprecated: Use New instead. +func NewFlock(path string) *Flock { + return New(path) +} + +// Close is equivalent to calling Unlock. +// +// This will release the lock and close the underlying file descriptor. +// It will not remove the file from disk, that's up to your application. +func (f *Flock) Close() error { + return f.Unlock() +} + +// Path returns the path as provided in NewFlock(). +func (f *Flock) Path() string { + return f.path +} + +// Locked returns the lock state (locked: true, unlocked: false). +// +// Warning: by the time you use the returned value, the state may have changed. +func (f *Flock) Locked() bool { + f.m.RLock() + defer f.m.RUnlock() + + return f.l +} + +// RLocked returns the read lock state (locked: true, unlocked: false). +// +// Warning: by the time you use the returned value, the state may have changed. +func (f *Flock) RLocked() bool { + f.m.RLock() + defer f.m.RUnlock() + + return f.r +} + +// Stat returns the FileInfo structure describing the lock file. +// If the lock file does not exist or cannot be accessed, an error is returned. +// +// This can be used to check the modification time of the lock file, +// which is useful for detecting stale locks. +func (f *Flock) Stat() (fs.FileInfo, error) { + f.m.RLock() + defer f.m.RUnlock() + + if f.fh != nil { + return f.fh.Stat() + } + + return os.Stat(f.path) +} + +func (f *Flock) String() string { + return f.path +} + +// TryLockContext repeatedly tries to take an exclusive lock until one of the conditions is met: +// - TryLock succeeds +// - TryLock fails with error +// - Context Done channel is closed. +func (f *Flock) TryLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) { + return tryCtx(ctx, f.TryLock, retryDelay) +} + +// TryRLockContext repeatedly tries to take a shared lock until one of the conditions is met: +// - TryRLock succeeds +// - TryRLock fails with error +// - Context Done channel is closed. +func (f *Flock) TryRLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) { + return tryCtx(ctx, f.TryRLock, retryDelay) +} + +func tryCtx(ctx context.Context, fn func() (bool, error), retryDelay time.Duration) (bool, error) { + if ctx.Err() != nil { + return false, ctx.Err() + } + + for { + if ok, err := fn(); ok || err != nil { + return ok, err + } + + select { + case <-ctx.Done(): + return false, ctx.Err() + case <-time.After(retryDelay): + } + } +} + +func (f *Flock) setFh(flag int) error { + // open a new os.File instance + fh, err := os.OpenFile(f.path, flag, f.perm) + if err != nil { + return err + } + + // set the file handle on the struct + f.fh = fh + + return nil +} + +// resetFh resets file handle: +// - tries to close the file (ignore errors) +// - sets fh to nil. +func (f *Flock) resetFh() { + if f.fh == nil { + return + } + + _ = f.fh.Close() + + f.fh = nil +} + +// ensure the file handle is closed if no lock is held. +func (f *Flock) ensureFhState() { + if f.l || f.r || f.fh == nil { + return + } + + f.resetFh() +} + +func (f *Flock) reset() { + f.l = false + f.r = false + + f.resetFh() +} diff --git a/vendor/github.com/gofrs/flock/flock_others.go b/vendor/github.com/gofrs/flock/flock_others.go new file mode 100644 index 00000000000..92d0f7e95a9 --- /dev/null +++ b/vendor/github.com/gofrs/flock/flock_others.go @@ -0,0 +1,45 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Copyright 2018-2025 The Gofrs. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +//go:build (!unix && !windows) || plan9 + +package flock + +import ( + "errors" + "io/fs" +) + +func (f *Flock) Lock() error { + return &fs.PathError{ + Op: "Lock", + Path: f.Path(), + Err: errors.ErrUnsupported, + } +} + +func (f *Flock) RLock() error { + return &fs.PathError{ + Op: "RLock", + Path: f.Path(), + Err: errors.ErrUnsupported, + } +} + +func (f *Flock) Unlock() error { + return &fs.PathError{ + Op: "Unlock", + Path: f.Path(), + Err: errors.ErrUnsupported, + } +} + +func (f *Flock) TryLock() (bool, error) { + return false, f.Lock() +} + +func (f *Flock) TryRLock() (bool, error) { + return false, f.RLock() +} diff --git a/vendor/github.com/gofrs/flock/flock_unix.go b/vendor/github.com/gofrs/flock/flock_unix.go new file mode 100644 index 00000000000..77de7a8837a --- /dev/null +++ b/vendor/github.com/gofrs/flock/flock_unix.go @@ -0,0 +1,211 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Copyright 2018-2025 The Gofrs. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +//go:build darwin || dragonfly || freebsd || illumos || linux || netbsd || openbsd + +package flock + +import ( + "errors" + "os" + + "golang.org/x/sys/unix" +) + +// Lock is a blocking call to try and take an exclusive file lock. +// It will wait until it is able to obtain the exclusive file lock. +// It's recommended that TryLock() be used over this function. +// This function may block the ability to query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already exclusive-locked, +// this function short-circuits and returns immediately assuming it can take the mutex lock. +// +// If the *Flock has a shared lock (RLock), +// this may transparently replace the shared lock with an exclusive lock on some UNIX-like operating systems. +// Be careful when using exclusive locks in conjunction with shared locks (RLock()), +// because calling Unlock() may accidentally release the exclusive lock that was once a shared lock. +func (f *Flock) Lock() error { + return f.lock(&f.l, unix.LOCK_EX) +} + +// RLock is a blocking call to try and take a shared file lock. +// It will wait until it is able to obtain the shared file lock. +// It's recommended that TryRLock() be used over this function. +// This function may block the ability to query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already shared-locked, +// this function short-circuits and returns immediately assuming it can take the mutex lock. +func (f *Flock) RLock() error { + return f.lock(&f.r, unix.LOCK_SH) +} + +func (f *Flock) lock(locked *bool, flag int) error { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return nil + } + + if f.fh == nil { + if err := f.setFh(f.flag); err != nil { + return err + } + + defer f.ensureFhState() + } + + err := unix.Flock(int(f.fh.Fd()), flag) + if err != nil { + shouldRetry, reopenErr := f.reopenFDOnError(err) + if reopenErr != nil { + return reopenErr + } + + if !shouldRetry { + return err + } + + err = unix.Flock(int(f.fh.Fd()), flag) + if err != nil { + return err + } + } + + *locked = true + + return nil +} + +// Unlock is a function to unlock the file. +// This file takes a RW-mutex lock, +// so while it is running the Locked() and RLocked() functions will be blocked. +// +// This function short-circuits if we are unlocked already. +// If not, it calls unix.LOCK_UN on the file and closes the file descriptor. +// It does not remove the file from disk. It's up to your application to do. +// +// Please note, +// if your shared lock became an exclusive lock, +// this may unintentionally drop the exclusive lock if called by the consumer that believes they have a shared lock. +// Please see Lock() for more details. +func (f *Flock) Unlock() error { + f.m.Lock() + defer f.m.Unlock() + + // If we aren't locked or if the lockfile instance is nil + // just return a nil error because we are unlocked. + if (!f.l && !f.r) || f.fh == nil { + return nil + } + + // Mark the file as unlocked. + err := unix.Flock(int(f.fh.Fd()), unix.LOCK_UN) + if err != nil { + return err + } + + f.reset() + + return nil +} + +// TryLock is the preferred function for taking an exclusive file lock. +// This function takes an RW-mutex lock before it tries to lock the file, +// so there is the possibility that this function may block for a short time +// if another goroutine is trying to take any action. +// +// The actual file lock is non-blocking. +// If we are unable to get the exclusive file lock, +// the function will return false instead of waiting for the lock. +// If we get the lock, we also set the *Flock instance as being exclusive-locked. +func (f *Flock) TryLock() (bool, error) { + return f.try(&f.l, unix.LOCK_EX) +} + +// TryRLock is the preferred function for taking a shared file lock. +// This function takes an RW-mutex lock before it tries to lock the file, +// so there is the possibility that this function may block for a short time +// if another goroutine is trying to take any action. +// +// The actual file lock is non-blocking. +// If we are unable to get the shared file lock, +// the function will return false instead of waiting for the lock. +// If we get the lock, we also set the *Flock instance as being share-locked. +func (f *Flock) TryRLock() (bool, error) { + return f.try(&f.r, unix.LOCK_SH) +} + +func (f *Flock) try(locked *bool, flag int) (bool, error) { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return true, nil + } + + if f.fh == nil { + if err := f.setFh(f.flag); err != nil { + return false, err + } + + defer f.ensureFhState() + } + + var retried bool + +retry: + err := unix.Flock(int(f.fh.Fd()), flag|unix.LOCK_NB) + + switch { + case errors.Is(err, unix.EWOULDBLOCK): + return false, nil + case err == nil: + *locked = true + return true, nil + } + + if !retried { + shouldRetry, reopenErr := f.reopenFDOnError(err) + if reopenErr != nil { + return false, reopenErr + } else if shouldRetry { + retried = true + goto retry + } + } + + return false, err +} + +// reopenFDOnError determines whether we should reopen the file handle in readwrite mode and try again. +// This comes from `util-linux/sys-utils/flock.c`: +// > Since Linux 3.4 (commit 55725513) +// > Probably NFSv4 where flock() is emulated by fcntl(). +// > https://github.com/util-linux/util-linux/blob/198e920aa24743ef6ace4e07cf6237de527f9261/sys-utils/flock.c#L374-L390 +func (f *Flock) reopenFDOnError(err error) (bool, error) { + if !errors.Is(err, unix.EIO) && !errors.Is(err, unix.EBADF) { + return false, nil + } + + st, err := f.fh.Stat() + if err != nil { + return false, nil + } + + if st.Mode()&f.perm != f.perm { + return false, nil + } + + f.resetFh() + + // reopen in read-write mode and set the file handle + err = f.setFh(f.flag | os.O_RDWR) + if err != nil { + return false, err + } + + return true, nil +} diff --git a/vendor/github.com/gofrs/flock/flock_unix_fcntl.go b/vendor/github.com/gofrs/flock/flock_unix_fcntl.go new file mode 100644 index 00000000000..05c2f88c65b --- /dev/null +++ b/vendor/github.com/gofrs/flock/flock_unix_fcntl.go @@ -0,0 +1,393 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Copyright 2018-2025 The Gofrs. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This code implements the filelock API using POSIX 'fcntl' locks, +// which attach to an (inode, process) pair rather than a file descriptor. +// To avoid unlocking files prematurely when the same file is opened through different descriptors, +// we allow only one read-lock at a time. +// +// This code is adapted from the Go package (go.22): +// https://github.com/golang/go/blob/release-branch.go1.22/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go + +//go:build aix || (solaris && !illumos) + +package flock + +import ( + "errors" + "io" + "io/fs" + "math/rand" + "sync" + "syscall" + "time" + + "golang.org/x/sys/unix" +) + +// https://github.com/golang/go/blob/09aeb6e33ab426eff4676a3baf694d5a3019e9fc/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go#L28 +type lockType int16 + +// String returns the name of the function corresponding to lt +// (Lock, RLock, or Unlock). +// https://github.com/golang/go/blob/09aeb6e33ab426eff4676a3baf694d5a3019e9fc/src/cmd/go/internal/lockedfile/internal/filelock/filelock.go#L67 +func (lt lockType) String() string { + switch lt { + case readLock: + return "RLock" + case writeLock: + return "Lock" + default: + return "Unlock" + } +} + +// https://github.com/golang/go/blob/09aeb6e33ab426eff4676a3baf694d5a3019e9fc/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go#L30-L33 +const ( + readLock lockType = unix.F_RDLCK + writeLock lockType = unix.F_WRLCK +) + +// https://github.com/golang/go/blob/09aeb6e33ab426eff4676a3baf694d5a3019e9fc/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go#L35 +type inode = uint64 + +// https://github.com/golang/go/blob/09aeb6e33ab426eff4676a3baf694d5a3019e9fc/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go#L37-L40 +type inodeLock struct { + owner *Flock + queue []<-chan *Flock +} + +type cmdType int + +const ( + tryLock cmdType = unix.F_SETLK + waitLock cmdType = unix.F_SETLKW +) + +var ( + mu sync.Mutex + inodes = map[*Flock]inode{} + locks = map[inode]inodeLock{} +) + +// Lock is a blocking call to try and take an exclusive file lock. +// It will wait until it is able to obtain the exclusive file lock. +// It's recommended that TryLock() be used over this function. +// This function may block the ability to query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already exclusive-locked, this function short-circuits and +// returns immediately assuming it can take the mutex lock. +// +// If the *Flock has a shared lock (RLock), +// this may transparently replace the shared lock with an exclusive lock on some UNIX-like operating systems. +// Be careful when using exclusive locks in conjunction with shared locks (RLock()), +// because calling Unlock() may accidentally release the exclusive lock that was once a shared lock. +func (f *Flock) Lock() error { + return f.lock(&f.l, writeLock) +} + +// RLock is a blocking call to try and take a shared file lock. +// It will wait until it is able to obtain the shared file lock. +// It's recommended that TryRLock() be used over this function. +// This function may block the ability to query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already shared-locked, this function short-circuits and +// returns immediately assuming it can take the mutex lock. +func (f *Flock) RLock() error { + return f.lock(&f.r, readLock) +} + +func (f *Flock) lock(locked *bool, flag lockType) error { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return nil + } + + if f.fh == nil { + if err := f.setFh(f.flag); err != nil { + return err + } + + defer f.ensureFhState() + } + + _, err := f.doLock(waitLock, flag, true) + if err != nil { + return err + } + + *locked = true + + return nil +} + +// https://github.com/golang/go/blob/09aeb6e33ab426eff4676a3baf694d5a3019e9fc/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go#L48 +func (f *Flock) doLock(cmd cmdType, lt lockType, blocking bool) (bool, error) { + // POSIX locks apply per inode and process, + // and the lock for an inode is released when *any* descriptor for that inode is closed. + // So we need to synchronize access to each inode internally, + // and must serialize lock and unlock calls that refer to the same inode through different descriptors. + fi, err := f.fh.Stat() + if err != nil { + return false, err + } + + // Note(ldez): don't replace `syscall.Stat_t` by `unix.Stat_t` because `FileInfo.Sys()` returns `syscall.Stat_t` + ino := fi.Sys().(*syscall.Stat_t).Ino + + mu.Lock() + + if i, dup := inodes[f]; dup && i != ino { + mu.Unlock() + return false, &fs.PathError{ + Op: lt.String(), + Path: f.Path(), + Err: errors.New("inode for file changed since last Lock or RLock"), + } + } + + inodes[f] = ino + + var wait chan *Flock + + l := locks[ino] + + switch { + case l.owner == f: + // This file already owns the lock, but the call may change its lock type. + case l.owner == nil: + // No owner: it's ours now. + l.owner = f + + case !blocking: + // Already owned: cannot take the lock. + mu.Unlock() + return false, nil + + default: + // Already owned: add a channel to wait on. + wait = make(chan *Flock) + l.queue = append(l.queue, wait) + } + + locks[ino] = l + + mu.Unlock() + + if wait != nil { + wait <- f + } + + // Spurious EDEADLK errors arise on platforms that compute deadlock graphs at + // the process, rather than thread, level. Consider processes P and Q, with + // threads P.1, P.2, and Q.3. The following trace is NOT a deadlock, but will be + // reported as a deadlock on systems that consider only process granularity: + // + // P.1 locks file A. + // Q.3 locks file B. + // Q.3 blocks on file A. + // P.2 blocks on file B. (This is erroneously reported as a deadlock.) + // P.1 unlocks file A. + // Q.3 unblocks and locks file A. + // Q.3 unlocks files A and B. + // P.2 unblocks and locks file B. + // P.2 unlocks file B. + // + // These spurious errors were observed in practice on AIX and Solaris in + // cmd/go: see https://golang.org/issue/32817. + // + // We work around this bug by treating EDEADLK as always spurious. If there + // really is a lock-ordering bug between the interacting processes, it will + // become a livelock instead, but that's not appreciably worse than if we had + // a proper flock implementation (which generally does not even attempt to + // diagnose deadlocks). + // + // In the above example, that changes the trace to: + // + // P.1 locks file A. + // Q.3 locks file B. + // Q.3 blocks on file A. + // P.2 spuriously fails to lock file B and goes to sleep. + // P.1 unlocks file A. + // Q.3 unblocks and locks file A. + // Q.3 unlocks files A and B. + // P.2 wakes up and locks file B. + // P.2 unlocks file B. + // + // We know that the retry loop will not introduce a *spurious* livelock + // because, according to the POSIX specification, EDEADLK is only to be + // returned when “the lock is blocked by a lock from another process”. + // If that process is blocked on some lock that we are holding, then the + // resulting livelock is due to a real deadlock (and would manifest as such + // when using, for example, the flock implementation of this package). + // If the other process is *not* blocked on some other lock that we are + // holding, then it will eventually release the requested lock. + + nextSleep := 1 * time.Millisecond + const maxSleep = 500 * time.Millisecond + for { + err = setlkw(f.fh.Fd(), cmd, lt) + if !errors.Is(err, unix.EDEADLK) { + break + } + + time.Sleep(nextSleep) + + nextSleep += nextSleep + if nextSleep > maxSleep { + nextSleep = maxSleep + } + // Apply 10% jitter to avoid synchronizing collisions when we finally unblock. + nextSleep += time.Duration((0.1*rand.Float64() - 0.05) * float64(nextSleep)) + } + + if err != nil { + f.doUnlock() + + if cmd == tryLock && errors.Is(err, unix.EACCES) { + return false, nil + } + + return false, &fs.PathError{ + Op: lt.String(), + Path: f.Path(), + Err: err, + } + } + + return true, nil +} + +func (f *Flock) Unlock() error { + f.m.Lock() + defer f.m.Unlock() + + // If we aren't locked or if the lockfile instance is nil + // just return a nil error because we are unlocked. + if (!f.l && !f.r) || f.fh == nil { + return nil + } + + if err := f.doUnlock(); err != nil { + return err + } + + f.reset() + + return nil +} + +// https://github.com/golang/go/blob/09aeb6e33ab426eff4676a3baf694d5a3019e9fc/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go#L163 +func (f *Flock) doUnlock() (err error) { + var owner *Flock + + mu.Lock() + + ino, ok := inodes[f] + if ok { + owner = locks[ino].owner + } + + mu.Unlock() + + if owner == f { + err = setlkw(f.fh.Fd(), waitLock, unix.F_UNLCK) + } + + mu.Lock() + + l := locks[ino] + + if len(l.queue) == 0 { + // No waiters: remove the map entry. + delete(locks, ino) + } else { + // The first waiter is sending us their file now. + // Receive it and update the queue. + l.owner = <-l.queue[0] + l.queue = l.queue[1:] + locks[ino] = l + } + + delete(inodes, f) + + mu.Unlock() + + return err +} + +// TryLock is the preferred function for taking an exclusive file lock. +// This function takes an RW-mutex lock before it tries to lock the file, +// so there is the possibility that this function may block for a short time +// if another goroutine is trying to take any action. +// +// The actual file lock is non-blocking. +// If we are unable to get the exclusive file lock, +// the function will return false instead of waiting for the lock. +// If we get the lock, we also set the *Flock instance as being exclusive-locked. +func (f *Flock) TryLock() (bool, error) { + return f.try(&f.l, writeLock) +} + +// TryRLock is the preferred function for taking a shared file lock. +// This function takes an RW-mutex lock before it tries to lock the file, +// so there is the possibility that this function may block for a short time +// if another goroutine is trying to take any action. +// +// The actual file lock is non-blocking. +// If we are unable to get the shared file lock, +// the function will return false instead of waiting for the lock. +// If we get the lock, we also set the *Flock instance as being share-locked. +func (f *Flock) TryRLock() (bool, error) { + return f.try(&f.r, readLock) +} + +func (f *Flock) try(locked *bool, flag lockType) (bool, error) { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return true, nil + } + + if f.fh == nil { + if err := f.setFh(f.flag); err != nil { + return false, err + } + + defer f.ensureFhState() + } + + hasLock, err := f.doLock(tryLock, flag, false) + if err != nil { + return false, err + } + + *locked = hasLock + + return hasLock, nil +} + +// setlkw calls FcntlFlock with cmd for the entire file indicated by fd. +// https://github.com/golang/go/blob/09aeb6e33ab426eff4676a3baf694d5a3019e9fc/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go#L198 +func setlkw(fd uintptr, cmd cmdType, lt lockType) error { + for { + err := unix.FcntlFlock(fd, int(cmd), &unix.Flock_t{ + Type: int16(lt), + Whence: io.SeekStart, + Start: 0, + Len: 0, // All bytes. + }) + if !errors.Is(err, unix.EINTR) { + return err + } + } +} diff --git a/vendor/github.com/gofrs/flock/flock_windows.go b/vendor/github.com/gofrs/flock/flock_windows.go new file mode 100644 index 00000000000..aa144f156e9 --- /dev/null +++ b/vendor/github.com/gofrs/flock/flock_windows.go @@ -0,0 +1,160 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Copyright 2018-2025 The Gofrs. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +//go:build windows + +package flock + +import ( + "errors" + + "golang.org/x/sys/windows" +) + +// Use of 0x00000000 for the shared lock is a guess based on some the MS Windows `LockFileEX` docs, +// which document the `LOCKFILE_EXCLUSIVE_LOCK` flag as: +// +// > The function requests an exclusive lock. Otherwise, it requests a shared lock. +// +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx +const winLockfileSharedLock = 0x00000000 + +// ErrorLockViolation is the error code returned from the Windows syscall when a lock would block, +// and you ask to fail immediately. +// +//nolint:errname // It should be renamed to `ErrLockViolation`. +const ErrorLockViolation windows.Errno = 0x21 // 33 + +// Lock is a blocking call to try and take an exclusive file lock. +// It will wait until it is able to obtain the exclusive file lock. +// It's recommended that TryLock() be used over this function. +// This function may block the ability to query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already locked, this function short-circuits and +// returns immediately assuming it can take the mutex lock. +func (f *Flock) Lock() error { + return f.lock(&f.l, windows.LOCKFILE_EXCLUSIVE_LOCK) +} + +// RLock is a blocking call to try and take a shared file lock. +// It will wait until it is able to obtain the shared file lock. +// It's recommended that TryRLock() be used over this function. +// This function may block the ability to query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already locked, this function short-circuits and +// returns immediately assuming it can take the mutex lock. +func (f *Flock) RLock() error { + return f.lock(&f.r, winLockfileSharedLock) +} + +func (f *Flock) lock(locked *bool, flag uint32) error { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return nil + } + + if f.fh == nil { + if err := f.setFh(f.flag); err != nil { + return err + } + + defer f.ensureFhState() + } + + err := windows.LockFileEx(windows.Handle(f.fh.Fd()), flag, 0, 1, 0, &windows.Overlapped{}) + if err != nil && !errors.Is(err, windows.Errno(0)) { + return err + } + + *locked = true + + return nil +} + +// Unlock is a function to unlock the file. +// This file takes a RW-mutex lock, +// so while it is running the Locked() and RLocked() functions will be blocked. +// +// This function short-circuits if we are unlocked already. +// If not, it calls UnlockFileEx() on the file and closes the file descriptor. +// It does not remove the file from disk. +// It's up to your application to do. +func (f *Flock) Unlock() error { + f.m.Lock() + defer f.m.Unlock() + + // if we aren't locked or if the lockfile instance is nil + // just return a nil error because we are unlocked + if (!f.l && !f.r) || f.fh == nil { + return nil + } + + // mark the file as unlocked + err := windows.UnlockFileEx(windows.Handle(f.fh.Fd()), 0, 1, 0, &windows.Overlapped{}) + if err != nil && !errors.Is(err, windows.Errno(0)) { + return err + } + + f.reset() + + return nil +} + +// TryLock is the preferred function for taking an exclusive file lock. +// This function does take a RW-mutex lock before it tries to lock the file, +// so there is the possibility that this function may block for a short time +// if another goroutine is trying to take any action. +// +// The actual file lock is non-blocking. +// If we are unable to get the exclusive file lock, +// the function will return false instead of waiting for the lock. +// If we get the lock, we also set the *Flock instance as being exclusive-locked. +func (f *Flock) TryLock() (bool, error) { + return f.try(&f.l, windows.LOCKFILE_EXCLUSIVE_LOCK) +} + +// TryRLock is the preferred function for taking a shared file lock. +// This function does take a RW-mutex lock before it tries to lock the file, +// so there is the possibility that this function may block for a short time if another goroutine is trying to take any action. +// +// The actual file lock is non-blocking. +// If we are unable to get the shared file lock, +// the function will return false instead of waiting for the lock. +// If we get the lock, we also set the *Flock instance as being shared-locked. +func (f *Flock) TryRLock() (bool, error) { + return f.try(&f.r, winLockfileSharedLock) +} + +func (f *Flock) try(locked *bool, flag uint32) (bool, error) { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return true, nil + } + + if f.fh == nil { + if err := f.setFh(f.flag); err != nil { + return false, err + } + + defer f.ensureFhState() + } + + err := windows.LockFileEx(windows.Handle(f.fh.Fd()), flag|windows.LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &windows.Overlapped{}) + if err != nil && !errors.Is(err, windows.Errno(0)) { + if errors.Is(err, ErrorLockViolation) || errors.Is(err, windows.ERROR_IO_PENDING) { + return false, nil + } + + return false, err + } + + *locked = true + + return true, nil +} diff --git a/vendor/github.com/ianlancetaylor/demangle/.gitignore b/vendor/github.com/ianlancetaylor/demangle/.gitignore new file mode 100644 index 00000000000..4a8b38f46b3 --- /dev/null +++ b/vendor/github.com/ianlancetaylor/demangle/.gitignore @@ -0,0 +1,13 @@ +*.o +*.a +*.so +._* +.nfs.* +a.out +*~ +*.orig +*.rej +*.exe +.*.swp +core +demangle.test diff --git a/vendor/github.com/ianlancetaylor/demangle/LICENSE b/vendor/github.com/ianlancetaylor/demangle/LICENSE new file mode 100644 index 00000000000..d29b37261f6 --- /dev/null +++ b/vendor/github.com/ianlancetaylor/demangle/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2015 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/ianlancetaylor/demangle/README.md b/vendor/github.com/ianlancetaylor/demangle/README.md new file mode 100644 index 00000000000..2c01cae088f --- /dev/null +++ b/vendor/github.com/ianlancetaylor/demangle/README.md @@ -0,0 +1,3 @@ +# github.com/ianlancetaylor/demangle + +A Go package that can be used to demangle C++ and Rust symbol names. diff --git a/vendor/github.com/ianlancetaylor/demangle/SECURITY.md b/vendor/github.com/ianlancetaylor/demangle/SECURITY.md new file mode 100644 index 00000000000..f4edf9e4e40 --- /dev/null +++ b/vendor/github.com/ianlancetaylor/demangle/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +Security updates are applied only to the latest release. + +## Reporting a Vulnerability + +If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released. + +Please disclose it at [security advisory](https://github.com/ianlancetaylor/demangle/security/advisories/new). + +This project is maintained by volunteers on a reasonable-effort basis. As such, please give us at least 90 days to work on a fix before public exposure. diff --git a/vendor/github.com/ianlancetaylor/demangle/ast.go b/vendor/github.com/ianlancetaylor/demangle/ast.go new file mode 100644 index 00000000000..9e1fb0661d1 --- /dev/null +++ b/vendor/github.com/ianlancetaylor/demangle/ast.go @@ -0,0 +1,5256 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package demangle + +import ( + "fmt" + "strings" +) + +// AST is an abstract syntax tree representing a C++ declaration. +// This is sufficient for the demangler but is by no means a general C++ AST. +// This abstract syntax tree is only used for C++ symbols, not Rust symbols. +type AST interface { + // Internal method to convert to demangled string. + print(*printState) + + // Traverse each element of an AST. If the function returns + // false, traversal of children of that element is skipped. + Traverse(func(AST) bool) + + // Copy an AST with possible transformations. + // If the skip function returns true, no copy is required. + // If the copy function returns nil, no copy is required. + // The Copy method will do the right thing if copy returns nil + // for some components of an AST but not others, so a good + // copy function will only return non-nil for AST values that + // need to change. + // Copy itself returns either a copy or nil. + Copy(copy func(AST) AST, skip func(AST) bool) AST + + // Implement the fmt.GoStringer interface. + GoString() string + goString(indent int, field string) string +} + +// ASTToString returns the demangled name of the AST. +func ASTToString(a AST, options ...Option) string { + tparams := true + enclosingParams := true + llvmStyle := false + max := 0 + for _, o := range options { + switch { + case o == NoTemplateParams: + tparams = false + case o == NoEnclosingParams: + enclosingParams = false + case o == LLVMStyle: + llvmStyle = true + case isMaxLength(o): + max = maxLength(o) + } + } + + ps := printState{ + tparams: tparams, + enclosingParams: enclosingParams, + llvmStyle: llvmStyle, + max: max, + scopes: 1, + } + a.print(&ps) + s := ps.buf.String() + if max > 0 && len(s) > max { + s = s[:max] + } + return s +} + +// The printState type holds information needed to print an AST. +type printState struct { + tparams bool // whether to print template parameters + enclosingParams bool // whether to print enclosing parameters + llvmStyle bool + max int // maximum output length + + // The scopes field is used to avoid unnecessary parentheses + // around expressions that use > (or >>). It is incremented if + // we output a parenthesis or something else that means that > + // or >> won't be treated as ending a template. It starts out + // as 1, and is set to 0 when we start writing template + // arguments. We add parentheses around expressions using > if + // scopes is 0. The effect is that an expression with > gets + // parentheses if used as a template argument that is not + // inside some other set of parentheses. + scopes int + + buf strings.Builder + last byte // Last byte written to buffer. + + // The inner field is a list of items to print for a type + // name. This is used by types to implement the inside-out + // C++ declaration syntax. + inner []AST + + // The printing field is a list of items we are currently + // printing. This avoids endless recursion if a substitution + // reference creates a cycle in the graph. + printing []AST +} + +// writeByte adds a byte to the string being printed. +func (ps *printState) writeByte(b byte) { + ps.last = b + ps.buf.WriteByte(b) +} + +// writeString adds a string to the string being printed. +func (ps *printState) writeString(s string) { + if len(s) > 0 { + ps.last = s[len(s)-1] + } + ps.buf.WriteString(s) +} + +// Print an AST. +func (ps *printState) print(a AST) { + if ps.max > 0 && ps.buf.Len() > ps.max { + return + } + + c := 0 + for _, v := range ps.printing { + if v == a { + // We permit the type to appear once, and + // return without printing anything if we see + // it twice. This is for a case like + // _Z6outer2IsEPFilES1_, where the + // substitution is printed differently the + // second time because the set of inner types + // is different. + c++ + if c > 1 { + return + } + } + } + ps.printing = append(ps.printing, a) + + a.print(ps) + + ps.printing = ps.printing[:len(ps.printing)-1] +} + +// printList prints a list of AST values separated by commas, +// optionally skipping some. +func (ps *printState) printList(args []AST, skip func(AST) bool) { + first := true + for _, a := range args { + if skip != nil && skip(a) { + continue + } + if !first { + ps.writeString(", ") + } + + needsParen := false + if ps.llvmStyle { + if p, ok := a.(hasPrec); ok { + if p.prec() >= precComma { + needsParen = true + } + } + } + if needsParen { + ps.startScope('(') + } + + ps.print(a) + + if needsParen { + ps.endScope(')') + } + + first = false + } +} + +// startScope starts a scope. This is used to decide whether we need +// to parenthesize an expression using > or >>. +func (ps *printState) startScope(b byte) { + ps.scopes++ + ps.writeByte(b) +} + +// endScope closes a scope. +func (ps *printState) endScope(b byte) { + ps.scopes-- + ps.writeByte(b) +} + +// precedence is used for operator precedence. This is used to avoid +// unnecessary parentheses when printing expressions in the LLVM style. +type precedence int + +// The precedence values, in order from high to low. +const ( + precPrimary precedence = iota + precPostfix + precUnary + precCast + precPtrMem + precMul + precAdd + precShift + precSpaceship + precRel + precEqual + precAnd + precXor + precOr + precLogicalAnd + precLogicalOr + precCond + precAssign + precComma + precDefault +) + +// hasPrec matches the AST nodes that have a prec method that returns +// the node's precedence. +type hasPrec interface { + prec() precedence +} + +// Name is an unqualified name. +type Name struct { + Name string +} + +func (n *Name) print(ps *printState) { + ps.writeString(n.Name) +} + +func (n *Name) Traverse(fn func(AST) bool) { + fn(n) +} + +func (n *Name) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(n) { + return nil + } + return fn(n) +} + +func (n *Name) GoString() string { + return n.goString(0, "Name: ") +} + +func (n *Name) goString(indent int, field string) string { + return fmt.Sprintf("%*s%s%s", indent, "", field, n.Name) +} + +func (n *Name) prec() precedence { + return precPrimary +} + +// Typed is a typed name. +type Typed struct { + Name AST + Type AST +} + +func (t *Typed) print(ps *printState) { + // We are printing a typed name, so ignore the current set of + // inner names to print. Pass down our name as the one to use. + holdInner := ps.inner + defer func() { ps.inner = holdInner }() + + ps.inner = []AST{t} + ps.print(t.Type) + if len(ps.inner) > 0 { + // The type did not print the name; print it now in + // the default location. + ps.writeByte(' ') + ps.print(t.Name) + } +} + +func (t *Typed) printInner(ps *printState) { + ps.print(t.Name) +} + +func (t *Typed) Traverse(fn func(AST) bool) { + if fn(t) { + t.Name.Traverse(fn) + t.Type.Traverse(fn) + } +} + +func (t *Typed) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(t) { + return nil + } + name := t.Name.Copy(fn, skip) + typ := t.Type.Copy(fn, skip) + if name == nil && typ == nil { + return fn(t) + } + if name == nil { + name = t.Name + } + if typ == nil { + typ = t.Type + } + t = &Typed{Name: name, Type: typ} + if r := fn(t); r != nil { + return r + } + return t +} + +func (t *Typed) GoString() string { + return t.goString(0, "") +} + +func (t *Typed) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sTyped:\n%s\n%s", indent, "", field, + t.Name.goString(indent+2, "Name: "), + t.Type.goString(indent+2, "Type: ")) +} + +// Qualified is a name in a scope. +type Qualified struct { + Scope AST + Name AST + + // The LocalName field is true if this is parsed as a + // . We shouldn't really need this, but in some + // cases (for the unary sizeof operator) the standard + // demangler prints a local name slightly differently. We + // keep track of this for compatibility. + LocalName bool // A full local name encoding +} + +func (q *Qualified) print(ps *printState) { + ps.print(q.Scope) + ps.writeString("::") + ps.print(q.Name) +} + +func (q *Qualified) Traverse(fn func(AST) bool) { + if fn(q) { + q.Scope.Traverse(fn) + q.Name.Traverse(fn) + } +} + +func (q *Qualified) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(q) { + return nil + } + scope := q.Scope.Copy(fn, skip) + name := q.Name.Copy(fn, skip) + if scope == nil && name == nil { + return fn(q) + } + if scope == nil { + scope = q.Scope + } + if name == nil { + name = q.Name + } + q = &Qualified{Scope: scope, Name: name, LocalName: q.LocalName} + if r := fn(q); r != nil { + return r + } + return q +} + +func (q *Qualified) GoString() string { + return q.goString(0, "") +} + +func (q *Qualified) goString(indent int, field string) string { + s := "" + if q.LocalName { + s = " LocalName: true" + } + return fmt.Sprintf("%*s%sQualified:%s\n%s\n%s", indent, "", field, + s, q.Scope.goString(indent+2, "Scope: "), + q.Name.goString(indent+2, "Name: ")) +} + +func (q *Qualified) prec() precedence { + return precPrimary +} + +// Template is a template with arguments. +type Template struct { + Name AST + Args []AST +} + +func (t *Template) print(ps *printState) { + // Inner types apply to the template as a whole, they don't + // cross over into the template. + holdInner := ps.inner + defer func() { ps.inner = holdInner }() + + ps.inner = nil + ps.print(t.Name) + + if !ps.tparams { + // Do not print template parameters. + return + } + // We need an extra space after operator<. + if ps.last == '<' { + ps.writeByte(' ') + } + + scopes := ps.scopes + ps.scopes = 0 + + ps.writeByte('<') + ps.printList(t.Args, ps.isEmpty) + if ps.last == '>' && !ps.llvmStyle { + // Avoid syntactic ambiguity in old versions of C++. + ps.writeByte(' ') + } + ps.writeByte('>') + + ps.scopes = scopes +} + +func (t *Template) Traverse(fn func(AST) bool) { + if fn(t) { + t.Name.Traverse(fn) + for _, a := range t.Args { + a.Traverse(fn) + } + } +} + +func (t *Template) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(t) { + return nil + } + name := t.Name.Copy(fn, skip) + changed := name != nil + args := make([]AST, len(t.Args)) + for i, a := range t.Args { + ac := a.Copy(fn, skip) + if ac == nil { + args[i] = a + } else { + args[i] = ac + changed = true + } + } + if !changed { + return fn(t) + } + if name == nil { + name = t.Name + } + t = &Template{Name: name, Args: args} + if r := fn(t); r != nil { + return r + } + return t +} + +func (t *Template) GoString() string { + return t.goString(0, "") +} + +func (t *Template) goString(indent int, field string) string { + var args string + if len(t.Args) == 0 { + args = fmt.Sprintf("%*sArgs: nil", indent+2, "") + } else { + args = fmt.Sprintf("%*sArgs:", indent+2, "") + for i, a := range t.Args { + args += "\n" + args += a.goString(indent+4, fmt.Sprintf("%d: ", i)) + } + } + return fmt.Sprintf("%*s%sTemplate (%p):\n%s\n%s", indent, "", field, t, + t.Name.goString(indent+2, "Name: "), args) +} + +// TemplateParam is a template parameter. The Template field is +// filled in while parsing the demangled string. We don't normally +// see these while printing--they are replaced by the simplify +// function. +type TemplateParam struct { + Index int + Template *Template +} + +func (tp *TemplateParam) print(ps *printState) { + if tp.Template == nil { + panic("TemplateParam Template field is nil") + } + if tp.Index >= len(tp.Template.Args) { + panic("TemplateParam Index out of bounds") + } + ps.print(tp.Template.Args[tp.Index]) +} + +func (tp *TemplateParam) Traverse(fn func(AST) bool) { + fn(tp) + // Don't traverse Template--it points elsewhere in the AST. +} + +func (tp *TemplateParam) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(tp) { + return nil + } + return fn(tp) +} + +func (tp *TemplateParam) GoString() string { + return tp.goString(0, "") +} + +func (tp *TemplateParam) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sTemplateParam: Template: %p; Index %d", indent, "", field, tp.Template, tp.Index) +} + +// LambdaAuto is a lambda auto parameter. +type LambdaAuto struct { + Index int +} + +func (la *LambdaAuto) print(ps *printState) { + // We print the index plus 1 because that is what the standard + // demangler does. + if ps.llvmStyle { + ps.writeString("auto") + } else { + fmt.Fprintf(&ps.buf, "auto:%d", la.Index+1) + } +} + +func (la *LambdaAuto) Traverse(fn func(AST) bool) { + fn(la) +} + +func (la *LambdaAuto) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(la) { + return nil + } + return fn(la) +} + +func (la *LambdaAuto) GoString() string { + return la.goString(0, "") +} + +func (la *LambdaAuto) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sLambdaAuto: Index %d", indent, "", field, la.Index) +} + +// TemplateParamQualifiedArg is used when the mangled name includes +// both the template parameter declaration and the template argument. +// See https://github.com/itanium-cxx-abi/cxx-abi/issues/47. +type TemplateParamQualifiedArg struct { + Param AST + Arg AST +} + +func (tpqa *TemplateParamQualifiedArg) print(ps *printState) { + // We only demangle the actual template argument. + // That is what the LLVM demangler does. + // The parameter disambiguates the argument, + // but is hopefully not required by a human reader. + ps.print(tpqa.Arg) +} + +func (tpqa *TemplateParamQualifiedArg) Traverse(fn func(AST) bool) { + if fn(tpqa) { + tpqa.Param.Traverse(fn) + tpqa.Arg.Traverse(fn) + } +} + +func (tpqa *TemplateParamQualifiedArg) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(tpqa) { + return nil + } + param := tpqa.Param.Copy(fn, skip) + arg := tpqa.Arg.Copy(fn, skip) + if param == nil && arg == nil { + return fn(tpqa) + } + if param == nil { + param = tpqa.Param + } + if arg == nil { + arg = tpqa.Arg + } + tpqa = &TemplateParamQualifiedArg{Param: param, Arg: arg} + if r := fn(tpqa); r != nil { + return r + } + return tpqa +} + +func (tpqa *TemplateParamQualifiedArg) GoString() string { + return tpqa.goString(0, "") +} + +func (tpqa *TemplateParamQualifiedArg) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sTemplateParamQualifiedArg:\n%s\n%s", indent, "", field, + tpqa.Param.goString(indent+2, "Param: "), + tpqa.Arg.goString(indent+2, "Arg: ")) +} + +// Qualifiers is an ordered list of type qualifiers. +type Qualifiers struct { + Qualifiers []AST +} + +func (qs *Qualifiers) print(ps *printState) { + first := true + for _, q := range qs.Qualifiers { + if !first { + ps.writeByte(' ') + } + q.print(ps) + first = false + } +} + +func (qs *Qualifiers) Traverse(fn func(AST) bool) { + if fn(qs) { + for _, q := range qs.Qualifiers { + q.Traverse(fn) + } + } +} + +func (qs *Qualifiers) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(qs) { + return nil + } + changed := false + qualifiers := make([]AST, len(qs.Qualifiers)) + for i, q := range qs.Qualifiers { + qc := q.Copy(fn, skip) + if qc == nil { + qualifiers[i] = q + } else { + qualifiers[i] = qc + changed = true + } + } + if !changed { + return fn(qs) + } + qs = &Qualifiers{Qualifiers: qualifiers} + if r := fn(qs); r != nil { + return r + } + return qs +} + +func (qs *Qualifiers) GoString() string { + return qs.goString(0, "") +} + +func (qs *Qualifiers) goString(indent int, field string) string { + quals := fmt.Sprintf("%*s%s", indent, "", field) + for _, q := range qs.Qualifiers { + quals += "\n" + quals += q.goString(indent+2, "") + } + return quals +} + +// Qualifier is a single type qualifier. +type Qualifier struct { + Name string // qualifier name: const, volatile, etc. + Exprs []AST // can be non-nil for noexcept and throw +} + +func (q *Qualifier) print(ps *printState) { + ps.writeString(q.Name) + if len(q.Exprs) > 0 { + ps.startScope('(') + first := true + for _, e := range q.Exprs { + if el, ok := e.(*ExprList); ok && len(el.Exprs) == 0 { + continue + } + if !first { + ps.writeString(", ") + } + ps.print(e) + first = false + } + ps.endScope(')') + } +} + +func (q *Qualifier) Traverse(fn func(AST) bool) { + if fn(q) { + for _, e := range q.Exprs { + e.Traverse(fn) + } + } +} + +func (q *Qualifier) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(q) { + return nil + } + exprs := make([]AST, len(q.Exprs)) + changed := false + for i, e := range q.Exprs { + ec := e.Copy(fn, skip) + if ec == nil { + exprs[i] = e + } else { + exprs[i] = ec + changed = true + } + } + if !changed { + return fn(q) + } + q = &Qualifier{Name: q.Name, Exprs: exprs} + if r := fn(q); r != nil { + return r + } + return q +} + +func (q *Qualifier) GoString() string { + return q.goString(0, "Qualifier: ") +} + +func (q *Qualifier) goString(indent int, field string) string { + qs := fmt.Sprintf("%*s%s%s", indent, "", field, q.Name) + if len(q.Exprs) > 0 { + for i, e := range q.Exprs { + qs += "\n" + qs += e.goString(indent+2, fmt.Sprintf("%d: ", i)) + } + } + return qs +} + +// TypeWithQualifiers is a type with standard qualifiers. +type TypeWithQualifiers struct { + Base AST + Qualifiers AST +} + +func (twq *TypeWithQualifiers) print(ps *printState) { + // Give the base type a chance to print the inner types. + ps.inner = append(ps.inner, twq) + ps.print(twq.Base) + if len(ps.inner) > 0 { + // The qualifier wasn't printed by Base. + ps.writeByte(' ') + ps.print(twq.Qualifiers) + ps.inner = ps.inner[:len(ps.inner)-1] + } +} + +// Print qualifiers as an inner type by just printing the qualifiers. +func (twq *TypeWithQualifiers) printInner(ps *printState) { + ps.writeByte(' ') + ps.print(twq.Qualifiers) +} + +func (twq *TypeWithQualifiers) Traverse(fn func(AST) bool) { + if fn(twq) { + twq.Base.Traverse(fn) + } +} + +func (twq *TypeWithQualifiers) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(twq) { + return nil + } + base := twq.Base.Copy(fn, skip) + quals := twq.Qualifiers.Copy(fn, skip) + if base == nil && quals == nil { + return fn(twq) + } + if base == nil { + base = twq.Base + } + if quals == nil { + quals = twq.Qualifiers + } + twq = &TypeWithQualifiers{Base: base, Qualifiers: quals} + if r := fn(twq); r != nil { + return r + } + return twq +} + +func (twq *TypeWithQualifiers) GoString() string { + return twq.goString(0, "") +} + +func (twq *TypeWithQualifiers) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sTypeWithQualifiers:\n%s\n%s", indent, "", field, + twq.Qualifiers.goString(indent+2, "Qualifiers: "), + twq.Base.goString(indent+2, "Base: ")) +} + +// MethodWithQualifiers is a method with qualifiers. +type MethodWithQualifiers struct { + Method AST + Qualifiers AST + RefQualifier string // "" or "&" or "&&" +} + +func (mwq *MethodWithQualifiers) print(ps *printState) { + // Give the base type a chance to print the inner types. + ps.inner = append(ps.inner, mwq) + ps.print(mwq.Method) + if len(ps.inner) > 0 { + if mwq.Qualifiers != nil { + ps.writeByte(' ') + ps.print(mwq.Qualifiers) + } + if mwq.RefQualifier != "" { + ps.writeByte(' ') + ps.writeString(mwq.RefQualifier) + } + ps.inner = ps.inner[:len(ps.inner)-1] + } +} + +func (mwq *MethodWithQualifiers) printInner(ps *printState) { + if mwq.Qualifiers != nil { + ps.writeByte(' ') + ps.print(mwq.Qualifiers) + } + if mwq.RefQualifier != "" { + ps.writeByte(' ') + ps.writeString(mwq.RefQualifier) + } +} + +func (mwq *MethodWithQualifiers) Traverse(fn func(AST) bool) { + if fn(mwq) { + mwq.Method.Traverse(fn) + } +} + +func (mwq *MethodWithQualifiers) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(mwq) { + return nil + } + method := mwq.Method.Copy(fn, skip) + var quals AST + if mwq.Qualifiers != nil { + quals = mwq.Qualifiers.Copy(fn, skip) + } + if method == nil && quals == nil { + return fn(mwq) + } + if method == nil { + method = mwq.Method + } + if quals == nil { + quals = mwq.Qualifiers + } + mwq = &MethodWithQualifiers{Method: method, Qualifiers: quals, RefQualifier: mwq.RefQualifier} + if r := fn(mwq); r != nil { + return r + } + return mwq +} + +func (mwq *MethodWithQualifiers) GoString() string { + return mwq.goString(0, "") +} + +func (mwq *MethodWithQualifiers) goString(indent int, field string) string { + var q string + if mwq.Qualifiers != nil { + q += "\n" + mwq.Qualifiers.goString(indent+2, "Qualifiers: ") + } + if mwq.RefQualifier != "" { + if q != "" { + q += "\n" + } + q += fmt.Sprintf("%*s%s%s", indent+2, "", "RefQualifier: ", mwq.RefQualifier) + } + return fmt.Sprintf("%*s%sMethodWithQualifiers:%s\n%s", indent, "", field, + q, mwq.Method.goString(indent+2, "Method: ")) +} + +// BuiltinType is a builtin type, like "int". +type BuiltinType struct { + Name string +} + +func (bt *BuiltinType) print(ps *printState) { + name := bt.Name + if ps.llvmStyle && name == "decltype(nullptr)" { + name = "std::nullptr_t" + } + ps.writeString(name) +} + +func (bt *BuiltinType) Traverse(fn func(AST) bool) { + fn(bt) +} + +func (bt *BuiltinType) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(bt) { + return nil + } + return fn(bt) +} + +func (bt *BuiltinType) GoString() string { + return bt.goString(0, "") +} + +func (bt *BuiltinType) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sBuiltinType: %s", indent, "", field, bt.Name) +} + +func (bt *BuiltinType) prec() precedence { + return precPrimary +} + +// printBase is common print code for types that are printed with a +// simple suffix. +func printBase(ps *printState, qual, base AST) { + ps.inner = append(ps.inner, qual) + ps.print(base) + if len(ps.inner) > 0 { + qual.(innerPrinter).printInner(ps) + ps.inner = ps.inner[:len(ps.inner)-1] + } +} + +// PointerType is a pointer type. +type PointerType struct { + Base AST +} + +func (pt *PointerType) print(ps *printState) { + printBase(ps, pt, pt.Base) +} + +func (pt *PointerType) printInner(ps *printState) { + ps.writeString("*") +} + +func (pt *PointerType) Traverse(fn func(AST) bool) { + if fn(pt) { + pt.Base.Traverse(fn) + } +} + +func (pt *PointerType) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(pt) { + return nil + } + base := pt.Base.Copy(fn, skip) + if base == nil { + return fn(pt) + } + pt = &PointerType{Base: base} + if r := fn(pt); r != nil { + return r + } + return pt +} + +func (pt *PointerType) GoString() string { + return pt.goString(0, "") +} + +func (pt *PointerType) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sPointerType:\n%s", indent, "", field, + pt.Base.goString(indent+2, "")) +} + +// ReferenceType is a reference type. +type ReferenceType struct { + Base AST +} + +func (rt *ReferenceType) print(ps *printState) { + printBase(ps, rt, rt.Base) +} + +func (rt *ReferenceType) printInner(ps *printState) { + ps.writeString("&") +} + +func (rt *ReferenceType) Traverse(fn func(AST) bool) { + if fn(rt) { + rt.Base.Traverse(fn) + } +} + +func (rt *ReferenceType) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(rt) { + return nil + } + base := rt.Base.Copy(fn, skip) + if base == nil { + return fn(rt) + } + rt = &ReferenceType{Base: base} + if r := fn(rt); r != nil { + return r + } + return rt +} + +func (rt *ReferenceType) GoString() string { + return rt.goString(0, "") +} + +func (rt *ReferenceType) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sReferenceType:\n%s", indent, "", field, + rt.Base.goString(indent+2, "")) +} + +// RvalueReferenceType is an rvalue reference type. +type RvalueReferenceType struct { + Base AST +} + +func (rt *RvalueReferenceType) print(ps *printState) { + printBase(ps, rt, rt.Base) +} + +func (rt *RvalueReferenceType) printInner(ps *printState) { + ps.writeString("&&") +} + +func (rt *RvalueReferenceType) Traverse(fn func(AST) bool) { + if fn(rt) { + rt.Base.Traverse(fn) + } +} + +func (rt *RvalueReferenceType) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(rt) { + return nil + } + base := rt.Base.Copy(fn, skip) + if base == nil { + return fn(rt) + } + rt = &RvalueReferenceType{Base: base} + if r := fn(rt); r != nil { + return r + } + return rt +} + +func (rt *RvalueReferenceType) GoString() string { + return rt.goString(0, "") +} + +func (rt *RvalueReferenceType) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sRvalueReferenceType:\n%s", indent, "", field, + rt.Base.goString(indent+2, "")) +} + +// ComplexType is a complex type. +type ComplexType struct { + Base AST +} + +func (ct *ComplexType) print(ps *printState) { + printBase(ps, ct, ct.Base) +} + +func (ct *ComplexType) printInner(ps *printState) { + ps.writeString(" _Complex") +} + +func (ct *ComplexType) Traverse(fn func(AST) bool) { + if fn(ct) { + ct.Base.Traverse(fn) + } +} + +func (ct *ComplexType) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(ct) { + return nil + } + base := ct.Base.Copy(fn, skip) + if base == nil { + return fn(ct) + } + ct = &ComplexType{Base: base} + if r := fn(ct); r != nil { + return r + } + return ct +} + +func (ct *ComplexType) GoString() string { + return ct.goString(0, "") +} + +func (ct *ComplexType) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sComplexType:\n%s", indent, "", field, + ct.Base.goString(indent+2, "")) +} + +// ImaginaryType is an imaginary type. +type ImaginaryType struct { + Base AST +} + +func (it *ImaginaryType) print(ps *printState) { + printBase(ps, it, it.Base) +} + +func (it *ImaginaryType) printInner(ps *printState) { + ps.writeString(" _Imaginary") +} + +func (it *ImaginaryType) Traverse(fn func(AST) bool) { + if fn(it) { + it.Base.Traverse(fn) + } +} + +func (it *ImaginaryType) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(it) { + return nil + } + base := it.Base.Copy(fn, skip) + if base == nil { + return fn(it) + } + it = &ImaginaryType{Base: base} + if r := fn(it); r != nil { + return r + } + return it +} + +func (it *ImaginaryType) GoString() string { + return it.goString(0, "") +} + +func (it *ImaginaryType) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sImaginaryType:\n%s", indent, "", field, + it.Base.goString(indent+2, "")) +} + +// SuffixType is an type with an arbitrary suffix. +type SuffixType struct { + Base AST + Suffix string +} + +func (st *SuffixType) print(ps *printState) { + printBase(ps, st, st.Base) +} + +func (st *SuffixType) printInner(ps *printState) { + ps.writeByte(' ') + ps.writeString(st.Suffix) +} + +func (st *SuffixType) Traverse(fn func(AST) bool) { + if fn(st) { + st.Base.Traverse(fn) + } +} + +func (st *SuffixType) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(st) { + return nil + } + base := st.Base.Copy(fn, skip) + if base == nil { + return fn(st) + } + st = &SuffixType{Base: base, Suffix: st.Suffix} + if r := fn(st); r != nil { + return r + } + return st +} + +func (st *SuffixType) GoString() string { + return st.goString(0, "") +} + +func (st *SuffixType) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sSuffixType: %s\n%s", indent, "", field, + st.Suffix, st.Base.goString(indent+2, "Base: ")) +} + +// TransformedType is a builtin type with a template argument. +type TransformedType struct { + Name string + Base AST +} + +func (tt *TransformedType) print(ps *printState) { + ps.writeString(tt.Name) + ps.startScope('(') + ps.print(tt.Base) + ps.endScope(')') +} + +func (tt *TransformedType) Traverse(fn func(AST) bool) { + if fn(tt) { + tt.Base.Traverse(fn) + } +} + +func (tt *TransformedType) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(tt) { + return nil + } + base := tt.Base.Copy(fn, skip) + if base == nil { + return fn(tt) + } + tt = &TransformedType{Name: tt.Name, Base: base} + if r := fn(tt); r != nil { + return r + } + return tt +} + +func (tt *TransformedType) GoString() string { + return tt.goString(0, "") +} + +func (tt *TransformedType) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sTransformedType: %s\n%s", indent, "", field, + tt.Name, tt.Base.goString(indent+2, "Base: ")) +} + +// VendorQualifier is a type qualified by a vendor-specific qualifier. +type VendorQualifier struct { + Qualifier AST + Type AST +} + +func (vq *VendorQualifier) print(ps *printState) { + if ps.llvmStyle { + ps.print(vq.Type) + vq.printInner(ps) + } else { + ps.inner = append(ps.inner, vq) + ps.print(vq.Type) + if len(ps.inner) > 0 { + ps.printOneInner(nil) + } + } +} + +func (vq *VendorQualifier) printInner(ps *printState) { + ps.writeByte(' ') + ps.print(vq.Qualifier) +} + +func (vq *VendorQualifier) Traverse(fn func(AST) bool) { + if fn(vq) { + vq.Qualifier.Traverse(fn) + vq.Type.Traverse(fn) + } +} + +func (vq *VendorQualifier) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(vq) { + return nil + } + qualifier := vq.Qualifier.Copy(fn, skip) + typ := vq.Type.Copy(fn, skip) + if qualifier == nil && typ == nil { + return fn(vq) + } + if qualifier == nil { + qualifier = vq.Qualifier + } + if typ == nil { + typ = vq.Type + } + vq = &VendorQualifier{Qualifier: qualifier, Type: vq.Type} + if r := fn(vq); r != nil { + return r + } + return vq +} + +func (vq *VendorQualifier) GoString() string { + return vq.goString(0, "") +} + +func (vq *VendorQualifier) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sVendorQualifier:\n%s\n%s", indent, "", field, + vq.Qualifier.goString(indent+2, "Qualifier: "), + vq.Type.goString(indent+2, "Type: ")) +} + +// ArrayType is an array type. +type ArrayType struct { + Dimension AST + Element AST +} + +func (at *ArrayType) print(ps *printState) { + // Pass the array type down as an inner type so that we print + // multi-dimensional arrays correctly. + ps.inner = append(ps.inner, at) + ps.print(at.Element) + if ln := len(ps.inner); ln > 0 { + ps.inner = ps.inner[:ln-1] + at.printDimension(ps) + } +} + +func (at *ArrayType) printInner(ps *printState) { + at.printDimension(ps) +} + +// Print the array dimension. +func (at *ArrayType) printDimension(ps *printState) { + space := " " + for len(ps.inner) > 0 { + // We haven't gotten to the real type yet. Use + // parentheses around that type, except that if it is + // an array type we print it as a multi-dimensional + // array + in := ps.inner[len(ps.inner)-1] + if twq, ok := in.(*TypeWithQualifiers); ok { + in = twq.Base + } + if _, ok := in.(*ArrayType); ok { + if in == ps.inner[len(ps.inner)-1] { + space = "" + } + ps.printOneInner(nil) + } else { + ps.writeByte(' ') + ps.startScope('(') + ps.printInner(false) + ps.endScope(')') + } + } + ps.writeString(space) + ps.writeByte('[') + ps.print(at.Dimension) + ps.writeByte(']') +} + +func (at *ArrayType) Traverse(fn func(AST) bool) { + if fn(at) { + at.Dimension.Traverse(fn) + at.Element.Traverse(fn) + } +} + +func (at *ArrayType) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(at) { + return nil + } + dimension := at.Dimension.Copy(fn, skip) + element := at.Element.Copy(fn, skip) + if dimension == nil && element == nil { + return fn(at) + } + if dimension == nil { + dimension = at.Dimension + } + if element == nil { + element = at.Element + } + at = &ArrayType{Dimension: dimension, Element: element} + if r := fn(at); r != nil { + return r + } + return at +} + +func (at *ArrayType) GoString() string { + return at.goString(0, "") +} + +func (at *ArrayType) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sArrayType:\n%s\n%s", indent, "", field, + at.Dimension.goString(indent+2, "Dimension: "), + at.Element.goString(indent+2, "Element: ")) +} + +// FunctionType is a function type. +type FunctionType struct { + Return AST + Args []AST + + // The forLocalName field reports whether this FunctionType + // was created for a local name. With the default GNU demangling + // output we don't print the return type in that case. + ForLocalName bool +} + +func (ft *FunctionType) print(ps *printState) { + retType := ft.Return + if ft.ForLocalName && (!ps.enclosingParams || !ps.llvmStyle) { + retType = nil + } + if retType != nil { + // Pass the return type as an inner type in order to + // print the arguments in the right location. + ps.inner = append(ps.inner, ft) + ps.print(retType) + if len(ps.inner) == 0 { + // Everything was printed. + return + } + ps.inner = ps.inner[:len(ps.inner)-1] + ps.writeByte(' ') + } + ft.printArgs(ps) +} + +func (ft *FunctionType) printInner(ps *printState) { + ft.printArgs(ps) +} + +// printArgs prints the arguments of a function type. It looks at the +// inner types for spacing. +func (ft *FunctionType) printArgs(ps *printState) { + paren := false + space := false + for i := len(ps.inner) - 1; i >= 0; i-- { + switch ps.inner[i].(type) { + case *PointerType, *ReferenceType, *RvalueReferenceType: + paren = true + case *TypeWithQualifiers, *ComplexType, *ImaginaryType, *PtrMem: + space = true + paren = true + } + if paren { + break + } + } + + if paren { + if !space && (ps.last != '(' && ps.last != '*') { + space = true + } + if space && ps.last != ' ' { + ps.writeByte(' ') + } + ps.startScope('(') + } + + save := ps.printInner(true) + + if paren { + ps.endScope(')') + } + + ps.startScope('(') + if !ft.ForLocalName || ps.enclosingParams { + first := true + for _, a := range ft.Args { + if ps.isEmpty(a) { + continue + } + if !first { + ps.writeString(", ") + } + ps.print(a) + first = false + } + } + ps.endScope(')') + + ps.inner = save + ps.printInner(false) +} + +func (ft *FunctionType) Traverse(fn func(AST) bool) { + if fn(ft) { + if ft.Return != nil { + ft.Return.Traverse(fn) + } + for _, a := range ft.Args { + a.Traverse(fn) + } + } +} + +func (ft *FunctionType) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(ft) { + return nil + } + changed := false + var ret AST + if ft.Return != nil { + ret = ft.Return.Copy(fn, skip) + if ret == nil { + ret = ft.Return + } else { + changed = true + } + } + args := make([]AST, len(ft.Args)) + for i, a := range ft.Args { + ac := a.Copy(fn, skip) + if ac == nil { + args[i] = a + } else { + args[i] = ac + changed = true + } + } + if !changed { + return fn(ft) + } + ft = &FunctionType{ + Return: ret, + Args: args, + ForLocalName: ft.ForLocalName, + } + if r := fn(ft); r != nil { + return r + } + return ft +} + +func (ft *FunctionType) GoString() string { + return ft.goString(0, "") +} + +func (ft *FunctionType) goString(indent int, field string) string { + var forLocalName string + if ft.ForLocalName { + forLocalName = " ForLocalName: true" + } + var r string + if ft.Return == nil { + r = fmt.Sprintf("%*sReturn: nil", indent+2, "") + } else { + r = ft.Return.goString(indent+2, "Return: ") + } + var args string + if len(ft.Args) == 0 { + args = fmt.Sprintf("%*sArgs: nil", indent+2, "") + } else { + args = fmt.Sprintf("%*sArgs:", indent+2, "") + for i, a := range ft.Args { + args += "\n" + args += a.goString(indent+4, fmt.Sprintf("%d: ", i)) + } + } + return fmt.Sprintf("%*s%sFunctionType:%s\n%s\n%s", indent, "", field, + forLocalName, r, args) +} + +// FunctionParam is a parameter of a function, used for last-specified +// return type in a closure. +type FunctionParam struct { + Index int +} + +func (fp *FunctionParam) print(ps *printState) { + if fp.Index == 0 { + ps.writeString("this") + } else if ps.llvmStyle { + if fp.Index == 1 { + ps.writeString("fp") + } else { + fmt.Fprintf(&ps.buf, "fp%d", fp.Index-2) + } + } else { + fmt.Fprintf(&ps.buf, "{parm#%d}", fp.Index) + } +} + +func (fp *FunctionParam) Traverse(fn func(AST) bool) { + fn(fp) +} + +func (fp *FunctionParam) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(fp) { + return nil + } + return fn(fp) +} + +func (fp *FunctionParam) GoString() string { + return fp.goString(0, "") +} + +func (fp *FunctionParam) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sFunctionParam: %d", indent, "", field, fp.Index) +} + +func (fp *FunctionParam) prec() precedence { + return precPrimary +} + +// PtrMem is a pointer-to-member expression. +type PtrMem struct { + Class AST + Member AST +} + +func (pm *PtrMem) print(ps *printState) { + ps.inner = append(ps.inner, pm) + ps.print(pm.Member) + if len(ps.inner) > 0 { + ps.printOneInner(nil) + } +} + +func (pm *PtrMem) printInner(ps *printState) { + if ps.last != '(' { + ps.writeByte(' ') + } + ps.print(pm.Class) + ps.writeString("::*") +} + +func (pm *PtrMem) Traverse(fn func(AST) bool) { + if fn(pm) { + pm.Class.Traverse(fn) + pm.Member.Traverse(fn) + } +} + +func (pm *PtrMem) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(pm) { + return nil + } + class := pm.Class.Copy(fn, skip) + member := pm.Member.Copy(fn, skip) + if class == nil && member == nil { + return fn(pm) + } + if class == nil { + class = pm.Class + } + if member == nil { + member = pm.Member + } + pm = &PtrMem{Class: class, Member: member} + if r := fn(pm); r != nil { + return r + } + return pm +} + +func (pm *PtrMem) GoString() string { + return pm.goString(0, "") +} + +func (pm *PtrMem) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sPtrMem:\n%s\n%s", indent, "", field, + pm.Class.goString(indent+2, "Class: "), + pm.Member.goString(indent+2, "Member: ")) +} + +// FixedType is a fixed numeric type of unknown size. +type FixedType struct { + Base AST + Accum bool + Sat bool +} + +func (ft *FixedType) print(ps *printState) { + if ft.Sat { + ps.writeString("_Sat ") + } + if bt, ok := ft.Base.(*BuiltinType); ok && bt.Name == "int" { + // The standard demangler skips printing "int". + } else { + ps.print(ft.Base) + ps.writeByte(' ') + } + if ft.Accum { + ps.writeString("_Accum") + } else { + ps.writeString("_Fract") + } +} + +func (ft *FixedType) Traverse(fn func(AST) bool) { + if fn(ft) { + ft.Base.Traverse(fn) + } +} + +func (ft *FixedType) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(ft) { + return nil + } + base := ft.Base.Copy(fn, skip) + if base == nil { + return fn(ft) + } + ft = &FixedType{Base: base, Accum: ft.Accum, Sat: ft.Sat} + if r := fn(ft); r != nil { + return r + } + return ft +} + +func (ft *FixedType) GoString() string { + return ft.goString(0, "") +} + +func (ft *FixedType) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sFixedType: Accum: %t; Sat: %t\n%s", indent, "", field, + ft.Accum, ft.Sat, + ft.Base.goString(indent+2, "Base: ")) +} + +// BinaryFP is a binary floating-point type. +type BinaryFP struct { + Bits int +} + +func (bfp *BinaryFP) print(ps *printState) { + fmt.Fprintf(&ps.buf, "_Float%d", bfp.Bits) +} + +func (bfp *BinaryFP) Traverse(fn func(AST) bool) { + fn(bfp) +} + +func (bfp *BinaryFP) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(bfp) { + return nil + } + return fn(bfp) +} + +func (bfp *BinaryFP) GoString() string { + return bfp.goString(0, "") +} + +func (bfp *BinaryFP) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sBinaryFP: %d", indent, "", field, bfp.Bits) +} + +// BitIntType is the C++23 _BitInt(N) type. +type BitIntType struct { + Size AST + Signed bool +} + +func (bt *BitIntType) print(ps *printState) { + if !bt.Signed { + ps.writeString("unsigned ") + } + ps.writeString("_BitInt") + ps.startScope('(') + ps.print(bt.Size) + ps.endScope(')') +} + +func (bt *BitIntType) Traverse(fn func(AST) bool) { + if fn(bt) { + bt.Size.Traverse(fn) + } +} + +func (bt *BitIntType) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(bt) { + return nil + } + size := bt.Size.Copy(fn, skip) + if size == nil { + return fn(bt) + } + bt = &BitIntType{Size: size, Signed: bt.Signed} + if r := fn(bt); r != nil { + return r + } + return bt +} + +func (bt *BitIntType) GoString() string { + return bt.goString(0, "") +} + +func (bt *BitIntType) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sBitIntType: Signed: %t\n%s", indent, "", field, + bt.Signed, + bt.Size.goString(indent+2, "Size: ")) +} + +// VectorType is a vector type. +type VectorType struct { + Dimension AST + Base AST +} + +func (vt *VectorType) print(ps *printState) { + ps.inner = append(ps.inner, vt) + ps.print(vt.Base) + if len(ps.inner) > 0 { + ps.printOneInner(nil) + } +} + +func (vt *VectorType) printInner(ps *printState) { + end := byte(')') + if ps.llvmStyle { + ps.writeString(" vector[") + end = ']' + } else { + ps.writeString(" __vector(") + } + ps.print(vt.Dimension) + ps.writeByte(end) +} + +func (vt *VectorType) Traverse(fn func(AST) bool) { + if fn(vt) { + vt.Dimension.Traverse(fn) + vt.Base.Traverse(fn) + } +} + +func (vt *VectorType) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(vt) { + return nil + } + dimension := vt.Dimension.Copy(fn, skip) + base := vt.Base.Copy(fn, skip) + if dimension == nil && base == nil { + return fn(vt) + } + if dimension == nil { + dimension = vt.Dimension + } + if base == nil { + base = vt.Base + } + vt = &VectorType{Dimension: dimension, Base: base} + if r := fn(vt); r != nil { + return r + } + return vt +} + +func (vt *VectorType) GoString() string { + return vt.goString(0, "") +} + +func (vt *VectorType) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sVectorType:\n%s\n%s", indent, "", field, + vt.Dimension.goString(indent+2, "Dimension: "), + vt.Base.goString(indent+2, "Base: ")) +} + +// ElaboratedType is an elaborated struct/union/enum type. +type ElaboratedType struct { + Kind string + Type AST +} + +func (et *ElaboratedType) print(ps *printState) { + ps.writeString(et.Kind) + ps.writeString(" ") + et.Type.print(ps) +} + +func (et *ElaboratedType) Traverse(fn func(AST) bool) { + if fn(et) { + et.Type.Traverse(fn) + } +} + +func (et *ElaboratedType) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(et) { + return nil + } + typ := et.Type.Copy(fn, skip) + if typ == nil { + return fn(et) + } + et = &ElaboratedType{Kind: et.Kind, Type: typ} + if r := fn(et); r != nil { + return r + } + return et +} + +func (et *ElaboratedType) GoString() string { + return et.goString(0, "") +} + +func (et *ElaboratedType) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sElaboratedtype: Kind: %s\n%s", indent, "", field, + et.Kind, et.Type.goString(indent+2, "Expr: ")) +} + +// Decltype is the decltype operator. +type Decltype struct { + Expr AST +} + +func (dt *Decltype) print(ps *printState) { + ps.writeString("decltype") + if !ps.llvmStyle { + ps.writeString(" ") + } + ps.startScope('(') + ps.print(dt.Expr) + ps.endScope(')') +} + +func (dt *Decltype) Traverse(fn func(AST) bool) { + if fn(dt) { + dt.Expr.Traverse(fn) + } +} + +func (dt *Decltype) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(dt) { + return nil + } + expr := dt.Expr.Copy(fn, skip) + if expr == nil { + return fn(dt) + } + dt = &Decltype{Expr: expr} + if r := fn(dt); r != nil { + return r + } + return dt +} + +func (dt *Decltype) GoString() string { + return dt.goString(0, "") +} + +func (dt *Decltype) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sDecltype:\n%s", indent, "", field, + dt.Expr.goString(indent+2, "Expr: ")) +} + +// Operator is an operator. +type Operator struct { + Name string + precedence precedence +} + +func (op *Operator) print(ps *printState) { + ps.writeString("operator") + if isLower(op.Name[0]) { + ps.writeByte(' ') + } + n := op.Name + n = strings.TrimSuffix(n, " ") + ps.writeString(n) +} + +func (op *Operator) Traverse(fn func(AST) bool) { + fn(op) +} + +func (op *Operator) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(op) { + return nil + } + return fn(op) +} + +func (op *Operator) GoString() string { + return op.goString(0, "") +} + +func (op *Operator) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sOperator: %s", indent, "", field, op.Name) +} + +func (op *Operator) prec() precedence { + return op.precedence +} + +// Constructor is a constructor. +type Constructor struct { + Name AST + Base AST // base class of inheriting constructor +} + +func (c *Constructor) print(ps *printState) { + ps.print(c.Name) + // We don't include the base class in the demangled string. +} + +func (c *Constructor) Traverse(fn func(AST) bool) { + if fn(c) { + c.Name.Traverse(fn) + if c.Base != nil { + c.Base.Traverse(fn) + } + } +} + +func (c *Constructor) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(c) { + return nil + } + name := c.Name.Copy(fn, skip) + var base AST + if c.Base != nil { + base = c.Base.Copy(fn, skip) + } + if name == nil && base == nil { + return fn(c) + } + if name == nil { + name = c.Name + } + if base == nil { + base = c.Base + } + c = &Constructor{Name: name, Base: base} + if r := fn(c); r != nil { + return r + } + return c +} + +func (c *Constructor) GoString() string { + return c.goString(0, "") +} + +func (c *Constructor) goString(indent int, field string) string { + var sb strings.Builder + fmt.Fprintf(&sb, "%*s%sConstructor:\n", indent, "", field) + if c.Base != nil { + fmt.Fprintf(&sb, "%s\n", c.Base.goString(indent+2, "Base: ")) + } + fmt.Fprintf(&sb, "%s", c.Name.goString(indent+2, "Name: ")) + return sb.String() +} + +// Destructor is a destructor. +type Destructor struct { + Name AST +} + +func (d *Destructor) print(ps *printState) { + ps.writeByte('~') + ps.print(d.Name) +} + +func (d *Destructor) Traverse(fn func(AST) bool) { + if fn(d) { + d.Name.Traverse(fn) + } +} + +func (d *Destructor) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(d) { + return nil + } + name := d.Name.Copy(fn, skip) + if name == nil { + return fn(d) + } + d = &Destructor{Name: name} + if r := fn(d); r != nil { + return r + } + return d +} + +func (d *Destructor) GoString() string { + return d.goString(0, "") +} + +func (d *Destructor) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sDestructor:\n%s", indent, "", field, d.Name.goString(indent+2, "Name: ")) +} + +// GlobalCDtor is a global constructor or destructor. +type GlobalCDtor struct { + Ctor bool + Key AST +} + +func (gcd *GlobalCDtor) print(ps *printState) { + ps.writeString("global ") + if gcd.Ctor { + ps.writeString("constructors") + } else { + ps.writeString("destructors") + } + ps.writeString(" keyed to ") + ps.print(gcd.Key) +} + +func (gcd *GlobalCDtor) Traverse(fn func(AST) bool) { + if fn(gcd) { + gcd.Key.Traverse(fn) + } +} + +func (gcd *GlobalCDtor) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(gcd) { + return nil + } + key := gcd.Key.Copy(fn, skip) + if key == nil { + return fn(gcd) + } + gcd = &GlobalCDtor{Ctor: gcd.Ctor, Key: key} + if r := fn(gcd); r != nil { + return r + } + return gcd +} + +func (gcd *GlobalCDtor) GoString() string { + return gcd.goString(0, "") +} + +func (gcd *GlobalCDtor) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sGlobalCDtor: Ctor: %t\n%s", indent, "", field, + gcd.Ctor, gcd.Key.goString(indent+2, "Key: ")) +} + +// TaggedName is a name with an ABI tag. +type TaggedName struct { + Name AST + Tag AST +} + +func (t *TaggedName) print(ps *printState) { + ps.print(t.Name) + ps.writeString("[abi:") + ps.print(t.Tag) + ps.writeByte(']') +} + +func (t *TaggedName) Traverse(fn func(AST) bool) { + if fn(t) { + t.Name.Traverse(fn) + t.Tag.Traverse(fn) + } +} + +func (t *TaggedName) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(t) { + return nil + } + name := t.Name.Copy(fn, skip) + tag := t.Tag.Copy(fn, skip) + if name == nil && tag == nil { + return fn(t) + } + if name == nil { + name = t.Name + } + if tag == nil { + tag = t.Tag + } + t = &TaggedName{Name: name, Tag: tag} + if r := fn(t); r != nil { + return r + } + return t +} + +func (t *TaggedName) GoString() string { + return t.goString(0, "") +} + +func (t *TaggedName) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sTaggedName:\n%s\n%s", indent, "", field, + t.Name.goString(indent+2, "Name: "), + t.Tag.goString(indent+2, "Tag: ")) +} + +// PackExpansion is a pack expansion. The Pack field may be nil. +type PackExpansion struct { + Base AST + Pack *ArgumentPack +} + +func (pe *PackExpansion) print(ps *printState) { + // We normally only get here if the simplify function was + // unable to locate and expand the pack. + if pe.Pack == nil { + if ps.llvmStyle { + ps.print(pe.Base) + } else { + parenthesize(ps, pe.Base) + ps.writeString("...") + } + } else { + ps.print(pe.Base) + } +} + +func (pe *PackExpansion) Traverse(fn func(AST) bool) { + if fn(pe) { + pe.Base.Traverse(fn) + // Don't traverse Template--it points elsewhere in the AST. + } +} + +func (pe *PackExpansion) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(pe) { + return nil + } + base := pe.Base.Copy(fn, skip) + if base == nil { + return fn(pe) + } + pe = &PackExpansion{Base: base, Pack: pe.Pack} + if r := fn(pe); r != nil { + return r + } + return pe +} + +func (pe *PackExpansion) GoString() string { + return pe.goString(0, "") +} + +func (pe *PackExpansion) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sPackExpansion: Pack: %p\n%s", indent, "", field, + pe.Pack, pe.Base.goString(indent+2, "Base: ")) +} + +// ArgumentPack is an argument pack. +type ArgumentPack struct { + Args []AST +} + +func (ap *ArgumentPack) print(ps *printState) { + for i, a := range ap.Args { + if i > 0 { + ps.writeString(", ") + } + ps.print(a) + } +} + +func (ap *ArgumentPack) Traverse(fn func(AST) bool) { + if fn(ap) { + for _, a := range ap.Args { + a.Traverse(fn) + } + } +} + +func (ap *ArgumentPack) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(ap) { + return nil + } + args := make([]AST, len(ap.Args)) + changed := false + for i, a := range ap.Args { + ac := a.Copy(fn, skip) + if ac == nil { + args[i] = a + } else { + args[i] = ac + changed = true + } + } + if !changed { + return fn(ap) + } + ap = &ArgumentPack{Args: args} + if r := fn(ap); r != nil { + return r + } + return ap +} + +func (ap *ArgumentPack) GoString() string { + return ap.goString(0, "") +} + +func (ap *ArgumentPack) goString(indent int, field string) string { + if len(ap.Args) == 0 { + return fmt.Sprintf("%*s%sArgumentPack: nil", indent, "", field) + } + s := fmt.Sprintf("%*s%sArgumentPack:", indent, "", field) + for i, a := range ap.Args { + s += "\n" + s += a.goString(indent+2, fmt.Sprintf("%d: ", i)) + } + return s +} + +// SizeofPack is the sizeof operator applied to an argument pack. +type SizeofPack struct { + Pack *ArgumentPack +} + +func (sp *SizeofPack) print(ps *printState) { + if ps.llvmStyle { + ps.writeString("sizeof...") + ps.startScope('(') + ps.print(sp.Pack) + ps.endScope(')') + } else { + ps.writeString(fmt.Sprintf("%d", len(sp.Pack.Args))) + } +} + +func (sp *SizeofPack) Traverse(fn func(AST) bool) { + fn(sp) + // Don't traverse the pack--it points elsewhere in the AST. +} + +func (sp *SizeofPack) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(sp) { + return nil + } + sp = &SizeofPack{Pack: sp.Pack} + if r := fn(sp); r != nil { + return r + } + return sp +} + +func (sp *SizeofPack) GoString() string { + return sp.goString(0, "") +} + +func (sp *SizeofPack) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sSizeofPack: Pack: %p", indent, "", field, sp.Pack) +} + +// SizeofArgs is the size of a captured template parameter pack from +// an alias template. +type SizeofArgs struct { + Args []AST +} + +func (sa *SizeofArgs) print(ps *printState) { + c := 0 + for _, a := range sa.Args { + if ap, ok := a.(*ArgumentPack); ok { + c += len(ap.Args) + } else if el, ok := a.(*ExprList); ok { + c += len(el.Exprs) + } else { + c++ + } + } + ps.writeString(fmt.Sprintf("%d", c)) +} + +func (sa *SizeofArgs) Traverse(fn func(AST) bool) { + if fn(sa) { + for _, a := range sa.Args { + a.Traverse(fn) + } + } +} + +func (sa *SizeofArgs) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(sa) { + return nil + } + changed := false + args := make([]AST, len(sa.Args)) + for i, a := range sa.Args { + ac := a.Copy(fn, skip) + if ac == nil { + args[i] = a + } else { + args[i] = ac + changed = true + } + } + if !changed { + return fn(sa) + } + sa = &SizeofArgs{Args: args} + if r := fn(sa); r != nil { + return r + } + return sa +} + +func (sa *SizeofArgs) GoString() string { + return sa.goString(0, "") +} + +func (sa *SizeofArgs) goString(indent int, field string) string { + var args string + if len(sa.Args) == 0 { + args = fmt.Sprintf("%*sArgs: nil", indent+2, "") + } else { + args = fmt.Sprintf("%*sArgs:", indent+2, "") + for i, a := range sa.Args { + args += "\n" + args += a.goString(indent+4, fmt.Sprintf("%d: ", i)) + } + } + return fmt.Sprintf("%*s%sSizeofArgs:\n%s", indent, "", field, args) +} + +// TemplateParamName is the name of a template parameter that the +// demangler introduced for a lambda that has explicit template +// parameters. This is a prefix with an index. +type TemplateParamName struct { + Prefix string + Index int +} + +func (tpn *TemplateParamName) print(ps *printState) { + ps.writeString(tpn.Prefix) + if tpn.Index > 0 { + ps.writeString(fmt.Sprintf("%d", tpn.Index-1)) + } +} + +func (tpn *TemplateParamName) Traverse(fn func(AST) bool) { + fn(tpn) +} + +func (tpn *TemplateParamName) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(tpn) { + return nil + } + return fn(tpn) +} + +func (tpn *TemplateParamName) GoString() string { + return tpn.goString(0, "") +} + +func (tpn *TemplateParamName) goString(indent int, field string) string { + name := tpn.Prefix + if tpn.Index > 0 { + name += fmt.Sprintf("%d", tpn.Index-1) + } + return fmt.Sprintf("%*s%sTemplateParamName: %s", indent, "", field, name) +} + +// TypeTemplateParam is a type template parameter that appears in a +// lambda with explicit template parameters. +type TypeTemplateParam struct { + Name AST +} + +func (ttp *TypeTemplateParam) print(ps *printState) { + ps.writeString("typename ") + ps.printInner(false) + ps.print(ttp.Name) +} + +func (ttp *TypeTemplateParam) Traverse(fn func(AST) bool) { + if fn(ttp) { + ttp.Name.Traverse(fn) + } +} + +func (ttp *TypeTemplateParam) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(ttp) { + return nil + } + name := ttp.Name.Copy(fn, skip) + if name == nil { + return fn(ttp) + } + ttp = &TypeTemplateParam{Name: name} + if r := fn(ttp); r != nil { + return r + } + return ttp +} + +func (ttp *TypeTemplateParam) GoString() string { + return ttp.goString(0, "") +} + +func (ttp *TypeTemplateParam) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sTypeTemplateParam:\n%s", indent, "", field, + ttp.Name.goString(indent+2, "Name")) +} + +// NonTypeTemplateParam is a non-type template parameter that appears +// in a lambda with explicit template parameters. +type NonTypeTemplateParam struct { + Name AST + Type AST +} + +func (nttp *NonTypeTemplateParam) print(ps *printState) { + ps.inner = append(ps.inner, nttp) + ps.print(nttp.Type) + if len(ps.inner) > 0 { + ps.writeByte(' ') + ps.print(nttp.Name) + ps.inner = ps.inner[:len(ps.inner)-1] + } +} + +func (nttp *NonTypeTemplateParam) printInner(ps *printState) { + ps.print(nttp.Name) +} + +func (nttp *NonTypeTemplateParam) Traverse(fn func(AST) bool) { + if fn(nttp) { + nttp.Name.Traverse(fn) + nttp.Type.Traverse(fn) + } +} + +func (nttp *NonTypeTemplateParam) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(nttp) { + return nil + } + name := nttp.Name.Copy(fn, skip) + typ := nttp.Type.Copy(fn, skip) + if name == nil && typ == nil { + return fn(nttp) + } + if name == nil { + name = nttp.Name + } + if typ == nil { + typ = nttp.Type + } + nttp = &NonTypeTemplateParam{Name: name, Type: typ} + if r := fn(nttp); r != nil { + return r + } + return nttp +} + +func (nttp *NonTypeTemplateParam) GoString() string { + return nttp.goString(0, "") +} + +func (nttp *NonTypeTemplateParam) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sNonTypeTemplateParam:\n%s\n%s", indent, "", field, + nttp.Name.goString(indent+2, "Name: "), + nttp.Type.goString(indent+2, "Type: ")) +} + +// TemplateTemplateParam is a template template parameter that appears +// in a lambda with explicit template parameters. +type TemplateTemplateParam struct { + Name AST + Params []AST + Constraint AST +} + +func (ttp *TemplateTemplateParam) print(ps *printState) { + scopes := ps.scopes + ps.scopes = 0 + + ps.writeString("template<") + ps.printList(ttp.Params, nil) + ps.writeString("> typename ") + + ps.scopes = scopes + + ps.print(ttp.Name) + + if ttp.Constraint != nil { + ps.writeString(" requires ") + ps.print(ttp.Constraint) + } +} + +func (ttp *TemplateTemplateParam) Traverse(fn func(AST) bool) { + if fn(ttp) { + ttp.Name.Traverse(fn) + for _, param := range ttp.Params { + param.Traverse(fn) + } + if ttp.Constraint != nil { + ttp.Constraint.Traverse(fn) + } + } +} + +func (ttp *TemplateTemplateParam) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(ttp) { + return nil + } + + changed := false + + name := ttp.Name.Copy(fn, skip) + if name == nil { + name = ttp.Name + } else { + changed = true + } + + params := make([]AST, len(ttp.Params)) + for i, p := range ttp.Params { + pc := p.Copy(fn, skip) + if pc == nil { + params[i] = p + } else { + params[i] = pc + changed = true + } + } + + var constraint AST + if ttp.Constraint != nil { + constraint = ttp.Constraint.Copy(fn, skip) + if constraint == nil { + constraint = ttp.Constraint + } else { + changed = true + } + } + + if !changed { + return fn(ttp) + } + + ttp = &TemplateTemplateParam{ + Name: name, + Params: params, + Constraint: constraint, + } + if r := fn(ttp); r != nil { + return r + } + return ttp +} + +func (ttp *TemplateTemplateParam) GoString() string { + return ttp.goString(0, "") +} + +func (ttp *TemplateTemplateParam) goString(indent int, field string) string { + var params strings.Builder + fmt.Fprintf(¶ms, "%*sParams:", indent+2, "") + for i, p := range ttp.Params { + params.WriteByte('\n') + params.WriteString(p.goString(indent+4, fmt.Sprintf("%d: ", i))) + } + var constraint string + if ttp.Constraint == nil { + constraint = fmt.Sprintf("%*sConstraint: nil", indent+2, "") + } else { + constraint = ttp.Constraint.goString(indent+2, "Constraint: ") + } + return fmt.Sprintf("%*s%sTemplateTemplateParam:\n%s\n%s\n%s", indent, "", field, + ttp.Name.goString(indent+2, "Name: "), + params.String(), + constraint) +} + +// ConstrainedTypeTemplateParam is a constrained template type +// parameter declaration. +type ConstrainedTypeTemplateParam struct { + Name AST + Constraint AST +} + +func (cttp *ConstrainedTypeTemplateParam) print(ps *printState) { + ps.inner = append(ps.inner, cttp) + ps.print(cttp.Constraint) + if len(ps.inner) > 0 { + ps.writeByte(' ') + ps.print(cttp.Name) + ps.inner = ps.inner[:len(ps.inner)-1] + } +} + +func (cttp *ConstrainedTypeTemplateParam) printInner(ps *printState) { + ps.print(cttp.Name) +} + +func (cttp *ConstrainedTypeTemplateParam) Traverse(fn func(AST) bool) { + if fn(cttp) { + cttp.Name.Traverse(fn) + cttp.Constraint.Traverse(fn) + } +} + +func (cttp *ConstrainedTypeTemplateParam) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(cttp) { + return nil + } + name := cttp.Name.Copy(fn, skip) + constraint := cttp.Constraint.Copy(fn, skip) + if name == nil && constraint == nil { + return fn(cttp) + } + if name == nil { + name = cttp.Name + } + if constraint == nil { + constraint = cttp.Constraint + } + cttp = &ConstrainedTypeTemplateParam{Name: name, Constraint: constraint} + if r := fn(cttp); r != nil { + return r + } + return cttp +} + +func (cttp *ConstrainedTypeTemplateParam) GoString() string { + return cttp.goString(0, "") +} + +func (cttp *ConstrainedTypeTemplateParam) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sConstrainedTypeTemplateParam\n%s\n%s", indent, "", field, + cttp.Name.goString(indent+2, "Name: "), + cttp.Constraint.goString(indent+2, "Constraint: ")) +} + +// TemplateParamPack is a template parameter pack that appears in a +// lambda with explicit template parameters. +type TemplateParamPack struct { + Param AST +} + +func (tpp *TemplateParamPack) print(ps *printState) { + holdInner := ps.inner + defer func() { ps.inner = holdInner }() + + ps.inner = []AST{tpp} + if nttp, ok := tpp.Param.(*NonTypeTemplateParam); ok { + ps.print(nttp.Type) + } else { + ps.print(tpp.Param) + } + if len(ps.inner) > 0 { + ps.writeString("...") + } +} + +func (tpp *TemplateParamPack) printInner(ps *printState) { + ps.writeString("...") + if nttp, ok := tpp.Param.(*NonTypeTemplateParam); ok { + ps.print(nttp.Name) + } +} + +func (tpp *TemplateParamPack) Traverse(fn func(AST) bool) { + if fn(tpp) { + tpp.Param.Traverse(fn) + } +} + +func (tpp *TemplateParamPack) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(tpp) { + return nil + } + param := tpp.Param.Copy(fn, skip) + if param == nil { + return fn(tpp) + } + tpp = &TemplateParamPack{Param: param} + if r := fn(tpp); r != nil { + return r + } + return tpp +} + +func (tpp *TemplateParamPack) GoString() string { + return tpp.goString(0, "") +} + +func (tpp *TemplateParamPack) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sTemplateParamPack:\n%s", indent, "", field, + tpp.Param.goString(indent+2, "Param: ")) +} + +// Cast is a type cast. +type Cast struct { + To AST +} + +func (c *Cast) print(ps *printState) { + ps.writeString("operator ") + ps.print(c.To) +} + +func (c *Cast) Traverse(fn func(AST) bool) { + if fn(c) { + c.To.Traverse(fn) + } +} + +func (c *Cast) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(c) { + return nil + } + to := c.To.Copy(fn, skip) + if to == nil { + return fn(c) + } + c = &Cast{To: to} + if r := fn(c); r != nil { + return r + } + return c +} + +func (c *Cast) GoString() string { + return c.goString(0, "") +} + +func (c *Cast) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sCast\n%s", indent, "", field, + c.To.goString(indent+2, "To: ")) +} + +func (c *Cast) prec() precedence { + return precCast +} + +// The parenthesize function prints the string for val, wrapped in +// parentheses if necessary. +func parenthesize(ps *printState, val AST) { + paren := false + switch v := val.(type) { + case *Name, *InitializerList: + case *FunctionParam: + if ps.llvmStyle { + paren = true + } + case *Qualified: + if v.LocalName { + paren = true + } + default: + paren = true + } + if paren { + ps.startScope('(') + } + ps.print(val) + if paren { + ps.endScope(')') + } +} + +// Nullary is an operator in an expression with no arguments, such as +// throw. +type Nullary struct { + Op AST +} + +func (n *Nullary) print(ps *printState) { + if op, ok := n.Op.(*Operator); ok { + ps.writeString(op.Name) + } else { + ps.print(n.Op) + } +} + +func (n *Nullary) Traverse(fn func(AST) bool) { + if fn(n) { + n.Op.Traverse(fn) + } +} + +func (n *Nullary) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(n) { + return nil + } + op := n.Op.Copy(fn, skip) + if op == nil { + return fn(n) + } + n = &Nullary{Op: op} + if r := fn(n); r != nil { + return r + } + return n +} + +func (n *Nullary) GoString() string { + return n.goString(0, "") +} + +func (n *Nullary) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sNullary:\n%s", indent, "", field, + n.Op.goString(indent+2, "Op: ")) +} + +// Unary is a unary operation in an expression. +type Unary struct { + Op AST + Expr AST + Suffix bool // true for ++ -- when used as postfix + SizeofType bool // true for sizeof (type) +} + +func (u *Unary) print(ps *printState) { + op, _ := u.Op.(*Operator) + expr := u.Expr + + // Don't print the argument list when taking the address of a + // function. + if !ps.llvmStyle { + if op != nil && op.Name == "&" { + if t, ok := expr.(*Typed); ok { + if _, ok := t.Type.(*FunctionType); ok { + expr = t.Name + } + } + } + } + + if u.Suffix { + if ps.llvmStyle { + wantParens := true + opPrec := precUnary + if op != nil { + opPrec = op.precedence + } + if p, ok := expr.(hasPrec); ok { + if p.prec() < opPrec { + wantParens = false + } + } + if wantParens { + ps.startScope('(') + } + ps.print(expr) + if wantParens { + ps.endScope(')') + } + } else { + parenthesize(ps, expr) + } + } + + if op != nil { + ps.writeString(op.Name) + if ps.llvmStyle && op.Name == "noexcept" { + ps.writeByte(' ') + } + } else if c, ok := u.Op.(*Cast); ok { + ps.startScope('(') + ps.print(c.To) + ps.endScope(')') + } else { + ps.print(u.Op) + } + + if !u.Suffix { + isDelete := op != nil && (op.Name == "delete " || op.Name == "delete[] ") + if op != nil && op.Name == "::" { + // Don't use parentheses after ::. + ps.print(expr) + } else if u.SizeofType { + // Always use parentheses for sizeof argument. + ps.startScope('(') + ps.print(expr) + ps.endScope(')') + } else if op != nil && op.Name == "__alignof__" { + // Always use parentheses for __alignof__ argument. + ps.startScope('(') + ps.print(expr) + ps.endScope(')') + } else if ps.llvmStyle { + var wantParens bool + switch { + case op == nil: + wantParens = true + case op.Name == `operator"" `: + wantParens = false + case op.Name == "&": + wantParens = false + case isDelete: + wantParens = false + case op.Name == "alignof ": + wantParens = true + case op.Name == "sizeof ": + wantParens = true + case op.Name == "typeid ": + wantParens = true + default: + wantParens = true + if p, ok := expr.(hasPrec); ok { + if p.prec() < op.precedence { + wantParens = false + } + } + } + if wantParens { + ps.startScope('(') + } + ps.print(expr) + if wantParens { + ps.endScope(')') + } + } else { + parenthesize(ps, expr) + } + } +} + +func (u *Unary) Traverse(fn func(AST) bool) { + if fn(u) { + u.Op.Traverse(fn) + u.Expr.Traverse(fn) + } +} + +func (u *Unary) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(u) { + return nil + } + op := u.Op.Copy(fn, skip) + expr := u.Expr.Copy(fn, skip) + if op == nil && expr == nil { + return fn(u) + } + if op == nil { + op = u.Op + } + if expr == nil { + expr = u.Expr + } + u = &Unary{Op: op, Expr: expr, Suffix: u.Suffix, SizeofType: u.SizeofType} + if r := fn(u); r != nil { + return r + } + return u +} + +func (u *Unary) GoString() string { + return u.goString(0, "") +} + +func (u *Unary) goString(indent int, field string) string { + var s string + if u.Suffix { + s = " Suffix: true" + } + if u.SizeofType { + s += " SizeofType: true" + } + return fmt.Sprintf("%*s%sUnary:%s\n%s\n%s", indent, "", field, + s, u.Op.goString(indent+2, "Op: "), + u.Expr.goString(indent+2, "Expr: ")) +} + +func (u *Unary) prec() precedence { + if p, ok := u.Op.(hasPrec); ok { + return p.prec() + } + return precDefault +} + +// isDesignatedInitializer reports whether x is a designated +// initializer. +func isDesignatedInitializer(x AST) bool { + switch x := x.(type) { + case *Binary: + if op, ok := x.Op.(*Operator); ok { + if op.Name == "]=" { + return true + } + if op.Name != "=" { + return false + } + if _, ok := x.Left.(*Literal); ok { + return false + } + return true + } + case *Trinary: + if op, ok := x.Op.(*Operator); ok { + return op.Name == "[...]=" + } + } + return false +} + +// Binary is a binary operation in an expression. +type Binary struct { + Op AST + Left AST + Right AST +} + +func (b *Binary) print(ps *printState) { + op, _ := b.Op.(*Operator) + + if op != nil && strings.Contains(op.Name, "cast") { + ps.writeString(op.Name) + + scopes := ps.scopes + ps.scopes = 0 + + ps.writeByte('<') + ps.print(b.Left) + ps.writeString(">") + + ps.scopes = scopes + + ps.startScope('(') + ps.print(b.Right) + ps.endScope(')') + return + } + + if isDesignatedInitializer(b) { + if op.Name == "=" { + ps.writeByte('.') + } else { + ps.writeByte('[') + } + ps.print(b.Left) + if op.Name == "]=" { + ps.writeByte(']') + } + if isDesignatedInitializer(b.Right) { + // Don't add anything between designated + // initializer chains. + ps.print(b.Right) + } else { + if ps.llvmStyle { + ps.writeString(" = ") + ps.print(b.Right) + } else { + ps.writeByte('=') + parenthesize(ps, b.Right) + } + } + return + } + + // Use an extra set of parentheses around an expression that + // uses the greater-than operator, so that it does not get + // confused with the '>' that ends template parameters. + needsOuterParen := op != nil && (op.Name == ">" || op.Name == ">>") + if ps.llvmStyle && ps.scopes > 0 { + needsOuterParen = false + } + if needsOuterParen { + ps.startScope('(') + } + + left := b.Left + + skipParens := false + addSpaces := ps.llvmStyle + if ps.llvmStyle && op != nil { + switch op.Name { + case ".", "->", "->*": + addSpaces = false + } + } + + // For a function call in an expression, don't print the types + // of the arguments unless there is a return type. + if op != nil && op.Name == "()" { + if ty, ok := b.Left.(*Typed); ok { + if ft, ok := ty.Type.(*FunctionType); ok { + if ft.Return == nil { + left = ty.Name + } else { + skipParens = true + } + } else { + left = ty.Name + } + } + if ps.llvmStyle { + skipParens = true + } + } + + if skipParens { + ps.print(left) + } else if ps.llvmStyle { + prec := precPrimary + if p, ok := left.(hasPrec); ok { + prec = p.prec() + } + needsParen := false + if prec > b.prec() { + needsParen = true + } + if needsParen { + ps.startScope('(') + } + + ps.print(left) + + if needsParen { + ps.endScope(')') + } + } else { + parenthesize(ps, left) + } + + if op != nil && op.Name == "[]" { + ps.writeByte('[') + ps.print(b.Right) + ps.writeByte(']') + return + } + + if op != nil { + if op.Name != "()" { + if addSpaces && op.Name != "," { + ps.writeByte(' ') + } + ps.writeString(op.Name) + if addSpaces { + ps.writeByte(' ') + } + } + } else { + ps.print(b.Op) + } + + if ps.llvmStyle { + prec := precPrimary + if p, ok := b.Right.(hasPrec); ok { + prec = p.prec() + } + needsParen := false + if prec >= b.prec() { + needsParen = true + } + if needsParen { + ps.startScope('(') + } + + ps.print(b.Right) + + if needsParen { + ps.endScope(')') + } + } else { + parenthesize(ps, b.Right) + } + + if needsOuterParen { + ps.endScope(')') + } +} + +func (b *Binary) Traverse(fn func(AST) bool) { + if fn(b) { + b.Op.Traverse(fn) + b.Left.Traverse(fn) + b.Right.Traverse(fn) + } +} + +func (b *Binary) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(b) { + return nil + } + op := b.Op.Copy(fn, skip) + left := b.Left.Copy(fn, skip) + right := b.Right.Copy(fn, skip) + if op == nil && left == nil && right == nil { + return fn(b) + } + if op == nil { + op = b.Op + } + if left == nil { + left = b.Left + } + if right == nil { + right = b.Right + } + b = &Binary{Op: op, Left: left, Right: right} + if r := fn(b); r != nil { + return r + } + return b +} + +func (b *Binary) GoString() string { + return b.goString(0, "") +} + +func (b *Binary) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sBinary:\n%s\n%s\n%s", indent, "", field, + b.Op.goString(indent+2, "Op: "), + b.Left.goString(indent+2, "Left: "), + b.Right.goString(indent+2, "Right: ")) +} + +func (b *Binary) prec() precedence { + if p, ok := b.Op.(hasPrec); ok { + return p.prec() + } + return precDefault +} + +// Trinary is the ?: trinary operation in an expression. +type Trinary struct { + Op AST + First AST + Second AST + Third AST +} + +func (t *Trinary) print(ps *printState) { + if isDesignatedInitializer(t) { + ps.writeByte('[') + ps.print(t.First) + ps.writeString(" ... ") + ps.print(t.Second) + ps.writeByte(']') + if isDesignatedInitializer(t.Third) { + // Don't add anything between designated + // initializer chains. + ps.print(t.Third) + } else { + if ps.llvmStyle { + ps.writeString(" = ") + ps.print(t.Third) + } else { + ps.writeByte('=') + parenthesize(ps, t.Third) + } + } + return + } + + if ps.llvmStyle { + wantParens := true + opPrec := precPrimary + if op, ok := t.Op.(*Operator); ok { + opPrec = op.precedence + } + if p, ok := t.First.(hasPrec); ok { + if p.prec() < opPrec { + wantParens = false + } + } + if wantParens { + ps.startScope('(') + } + ps.print(t.First) + if wantParens { + ps.endScope(')') + } + } else { + parenthesize(ps, t.First) + } + + if ps.llvmStyle { + ps.writeString(" ? ") + } else { + ps.writeByte('?') + } + + if ps.llvmStyle { + wantParens := true + if p, ok := t.Second.(hasPrec); ok { + if p.prec() < precDefault { + wantParens = false + } + } + if wantParens { + ps.startScope('(') + } + ps.print(t.Second) + if wantParens { + ps.endScope(')') + } + } else { + parenthesize(ps, t.Second) + } + + ps.writeString(" : ") + + if ps.llvmStyle { + wantParens := true + if p, ok := t.Third.(hasPrec); ok { + if p.prec() < precAssign { + wantParens = false + } + } + if wantParens { + ps.startScope('(') + } + ps.print(t.Third) + if wantParens { + ps.endScope(')') + } + } else { + parenthesize(ps, t.Third) + } +} + +func (t *Trinary) Traverse(fn func(AST) bool) { + if fn(t) { + t.Op.Traverse(fn) + t.First.Traverse(fn) + t.Second.Traverse(fn) + t.Third.Traverse(fn) + } +} + +func (t *Trinary) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(t) { + return nil + } + op := t.Op.Copy(fn, skip) + first := t.First.Copy(fn, skip) + second := t.Second.Copy(fn, skip) + third := t.Third.Copy(fn, skip) + if op == nil && first == nil && second == nil && third == nil { + return fn(t) + } + if op == nil { + op = t.Op + } + if first == nil { + first = t.First + } + if second == nil { + second = t.Second + } + if third == nil { + third = t.Third + } + t = &Trinary{Op: op, First: first, Second: second, Third: third} + if r := fn(t); r != nil { + return r + } + return t +} + +func (t *Trinary) GoString() string { + return t.goString(0, "") +} + +func (t *Trinary) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sTrinary:\n%s\n%s\n%s\n%s", indent, "", field, + t.Op.goString(indent+2, "Op: "), + t.First.goString(indent+2, "First: "), + t.Second.goString(indent+2, "Second: "), + t.Third.goString(indent+2, "Third: ")) +} + +// Fold is a C++17 fold-expression. Arg2 is nil for a unary operator. +type Fold struct { + Left bool + Op AST + Arg1 AST + Arg2 AST +} + +func (f *Fold) print(ps *printState) { + op, _ := f.Op.(*Operator) + printOp := func() { + if op != nil { + if ps.llvmStyle { + ps.writeByte(' ') + } + ps.writeString(op.Name) + if ps.llvmStyle { + ps.writeByte(' ') + } + } else { + ps.print(f.Op) + } + } + foldParenthesize := func(a AST) { + if ps.llvmStyle { + prec := precDefault + if p, ok := a.(hasPrec); ok { + prec = p.prec() + } + needsParen := false + if prec > precCast { + needsParen = true + } + if needsParen { + ps.startScope('(') + } + ps.print(a) + if needsParen { + ps.endScope(')') + } + } else { + parenthesize(ps, a) + } + } + + if f.Arg2 == nil { + if f.Left { + ps.startScope('(') + ps.writeString("...") + printOp() + foldParenthesize(f.Arg1) + ps.endScope(')') + } else { + ps.startScope('(') + foldParenthesize(f.Arg1) + printOp() + ps.writeString("...") + ps.endScope(')') + } + } else { + ps.startScope('(') + foldParenthesize(f.Arg1) + printOp() + ps.writeString("...") + printOp() + foldParenthesize(f.Arg2) + ps.endScope(')') + } +} + +func (f *Fold) Traverse(fn func(AST) bool) { + if fn(f) { + f.Op.Traverse(fn) + f.Arg1.Traverse(fn) + if f.Arg2 != nil { + f.Arg2.Traverse(fn) + } + } +} + +func (f *Fold) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(f) { + return nil + } + op := f.Op.Copy(fn, skip) + arg1 := f.Arg1.Copy(fn, skip) + var arg2 AST + if f.Arg2 != nil { + arg2 = f.Arg2.Copy(fn, skip) + } + if op == nil && arg1 == nil && arg2 == nil { + return fn(f) + } + if op == nil { + op = f.Op + } + if arg1 == nil { + arg1 = f.Arg1 + } + if arg2 == nil { + arg2 = f.Arg2 + } + f = &Fold{Left: f.Left, Op: op, Arg1: arg1, Arg2: arg2} + if r := fn(f); r != nil { + return r + } + return f +} + +func (f *Fold) GoString() string { + return f.goString(0, "") +} + +func (f *Fold) goString(indent int, field string) string { + if f.Arg2 == nil { + return fmt.Sprintf("%*s%sFold: Left: %t\n%s\n%s", indent, "", field, + f.Left, f.Op.goString(indent+2, "Op: "), + f.Arg1.goString(indent+2, "Arg1: ")) + } else { + return fmt.Sprintf("%*s%sFold: Left: %t\n%s\n%s\n%s", indent, "", field, + f.Left, f.Op.goString(indent+2, "Op: "), + f.Arg1.goString(indent+2, "Arg1: "), + f.Arg2.goString(indent+2, "Arg2: ")) + } +} + +// Subobject is a a reference to an offset in an expression. This is +// used for C++20 manglings of class types used as the type of +// non-type template arguments. +// +// See https://github.com/itanium-cxx-abi/cxx-abi/issues/47. +type Subobject struct { + Type AST + SubExpr AST + Offset int + Selectors []int + PastEnd bool +} + +func (so *Subobject) print(ps *printState) { + ps.print(so.SubExpr) + ps.writeString(".<") + ps.print(so.Type) + ps.writeString(fmt.Sprintf(" at offset %d>", so.Offset)) +} + +func (so *Subobject) Traverse(fn func(AST) bool) { + if fn(so) { + so.Type.Traverse(fn) + so.SubExpr.Traverse(fn) + } +} + +func (so *Subobject) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(so) { + return nil + } + typ := so.Type.Copy(fn, skip) + subExpr := so.SubExpr.Copy(fn, skip) + if typ == nil && subExpr == nil { + return nil + } + if typ == nil { + typ = so.Type + } + if subExpr == nil { + subExpr = so.SubExpr + } + so = &Subobject{ + Type: typ, + SubExpr: subExpr, + Offset: so.Offset, + Selectors: so.Selectors, + PastEnd: so.PastEnd, + } + if r := fn(so); r != nil { + return r + } + return so +} + +func (so *Subobject) GoString() string { + return so.goString(0, "") +} + +func (so *Subobject) goString(indent int, field string) string { + var selectors string + for _, s := range so.Selectors { + selectors += fmt.Sprintf(" %d", s) + } + return fmt.Sprintf("%*s%sSubobject:\n%s\n%s\n%*sOffset: %d\n%*sSelectors:%s\n%*sPastEnd: %t", + indent, "", field, + so.Type.goString(indent+2, "Type: "), + so.SubExpr.goString(indent+2, "SubExpr: "), + indent+2, "", so.Offset, + indent+2, "", selectors, + indent+2, "", so.PastEnd) +} + +// PtrMemCast is a conversion of an expression to a pointer-to-member +// type. This is used for C++20 manglings of class types used as the +// type of non-type template arguments. +// +// See https://github.com/itanium-cxx-abi/cxx-abi/issues/47. +type PtrMemCast struct { + Type AST + Expr AST + Offset int +} + +func (pmc *PtrMemCast) print(ps *printState) { + ps.startScope('(') + ps.print(pmc.Type) + ps.writeString(")(") + ps.print(pmc.Expr) + ps.endScope(')') +} + +func (pmc *PtrMemCast) Traverse(fn func(AST) bool) { + if fn(pmc) { + pmc.Type.Traverse(fn) + pmc.Expr.Traverse(fn) + } +} + +func (pmc *PtrMemCast) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(pmc) { + return nil + } + typ := pmc.Type.Copy(fn, skip) + expr := pmc.Expr.Copy(fn, skip) + if typ == nil && expr == nil { + return nil + } + if typ == nil { + typ = pmc.Type + } + if expr == nil { + expr = pmc.Expr + } + pmc = &PtrMemCast{ + Type: typ, + Expr: expr, + Offset: pmc.Offset, + } + if r := fn(pmc); r != nil { + return r + } + return pmc +} + +func (pmc *PtrMemCast) GoString() string { + return pmc.goString(0, "") +} + +func (pmc *PtrMemCast) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sPtrMemCast:\n%s\n%s\n%*sOffset: %d", + indent, "", field, + pmc.Type.goString(indent+2, "Type: "), + pmc.Expr.goString(indent+2, "Expr: "), + indent+2, "", pmc.Offset) +} + +// New is a use of operator new in an expression. +type New struct { + Op AST + Place AST + Type AST + Init AST +} + +func (n *New) print(ps *printState) { + if !ps.llvmStyle { + // Op doesn't really matter for printing--we always print "new". + ps.writeString("new ") + } else { + op, _ := n.Op.(*Operator) + if op != nil { + ps.writeString(op.Name) + if n.Place == nil { + ps.writeByte(' ') + } + } else { + ps.print(n.Op) + } + } + if n.Place != nil { + parenthesize(ps, n.Place) + ps.writeByte(' ') + } + ps.print(n.Type) + if n.Init != nil { + parenthesize(ps, n.Init) + } +} + +func (n *New) Traverse(fn func(AST) bool) { + if fn(n) { + n.Op.Traverse(fn) + if n.Place != nil { + n.Place.Traverse(fn) + } + n.Type.Traverse(fn) + if n.Init != nil { + n.Init.Traverse(fn) + } + } +} + +func (n *New) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(n) { + return nil + } + op := n.Op.Copy(fn, skip) + var place AST + if n.Place != nil { + place = n.Place.Copy(fn, skip) + } + typ := n.Type.Copy(fn, skip) + var ini AST + if n.Init != nil { + ini = n.Init.Copy(fn, skip) + } + if op == nil && place == nil && typ == nil && ini == nil { + return fn(n) + } + if op == nil { + op = n.Op + } + if place == nil { + place = n.Place + } + if typ == nil { + typ = n.Type + } + if ini == nil { + ini = n.Init + } + n = &New{Op: op, Place: place, Type: typ, Init: ini} + if r := fn(n); r != nil { + return r + } + return n +} + +func (n *New) GoString() string { + return n.goString(0, "") +} + +func (n *New) goString(indent int, field string) string { + var place string + if n.Place == nil { + place = fmt.Sprintf("%*sPlace: nil", indent, "") + } else { + place = n.Place.goString(indent+2, "Place: ") + } + var ini string + if n.Init == nil { + ini = fmt.Sprintf("%*sInit: nil", indent, "") + } else { + ini = n.Init.goString(indent+2, "Init: ") + } + return fmt.Sprintf("%*s%sNew:\n%s\n%s\n%s\n%s", indent, "", field, + n.Op.goString(indent+2, "Op: "), place, + n.Type.goString(indent+2, "Type: "), ini) +} + +// Literal is a literal in an expression. +type Literal struct { + Type AST + Val string + Neg bool +} + +// Suffixes to use for constants of the given integer type. +var builtinTypeSuffix = map[string]string{ + "int": "", + "unsigned int": "u", + "long": "l", + "unsigned long": "ul", + "long long": "ll", + "unsigned long long": "ull", +} + +// Builtin float types. +var builtinTypeFloat = map[string]bool{ + "double": true, + "long double": true, + "float": true, + "__float128": true, + "half": true, +} + +func (l *Literal) print(ps *printState) { + isFloat := false + if b, ok := l.Type.(*BuiltinType); ok { + if suffix, ok := builtinTypeSuffix[b.Name]; ok { + if l.Neg { + ps.writeByte('-') + } + ps.writeString(l.Val) + ps.writeString(suffix) + return + } else if b.Name == "bool" && !l.Neg { + switch l.Val { + case "0": + ps.writeString("false") + return + case "1": + ps.writeString("true") + return + } + } else if b.Name == "decltype(nullptr)" && (l.Val == "" || l.Val == "0") { + if ps.llvmStyle { + ps.writeString("nullptr") + } else { + ps.print(l.Type) + } + return + } else { + isFloat = builtinTypeFloat[b.Name] + } + } + + ps.startScope('(') + ps.print(l.Type) + ps.endScope(')') + + if isFloat { + ps.writeByte('[') + } + if l.Neg { + ps.writeByte('-') + } + ps.writeString(l.Val) + if isFloat { + ps.writeByte(']') + } +} + +func (l *Literal) Traverse(fn func(AST) bool) { + if fn(l) { + l.Type.Traverse(fn) + } +} + +func (l *Literal) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(l) { + return nil + } + typ := l.Type.Copy(fn, skip) + if typ == nil { + return fn(l) + } + l = &Literal{Type: typ, Val: l.Val, Neg: l.Neg} + if r := fn(l); r != nil { + return r + } + return l +} + +func (l *Literal) GoString() string { + return l.goString(0, "") +} + +func (l *Literal) goString(indent int, field string) string { + var neg string + if l.Neg { + neg = " Neg: true" + } + return fmt.Sprintf("%*s%sLiteral:%s\n%s\n%*sVal: %s", indent, "", field, + neg, l.Type.goString(indent+2, "Type: "), + indent+2, "", l.Val) +} + +func (l *Literal) prec() precedence { + return precPrimary +} + +// StringLiteral is a string literal. +type StringLiteral struct { + Type AST +} + +func (sl *StringLiteral) print(ps *printState) { + ps.writeString(`"<`) + sl.Type.print(ps) + ps.writeString(`>"`) +} + +func (sl *StringLiteral) Traverse(fn func(AST) bool) { + if fn(sl) { + sl.Type.Traverse(fn) + } +} + +func (sl *StringLiteral) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(sl) { + return nil + } + typ := sl.Type.Copy(fn, skip) + if typ == nil { + return fn(sl) + } + sl = &StringLiteral{Type: typ} + if r := fn(sl); r != nil { + return r + } + return sl +} + +func (sl *StringLiteral) GoString() string { + return sl.goString(0, "") +} + +func (sl *StringLiteral) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sStringLiteral:\n%s", indent, "", field, + sl.Type.goString(indent+2, "")) +} + +// LambdaExpr is a literal that is a lambda expression. +type LambdaExpr struct { + Type AST +} + +func (le *LambdaExpr) print(ps *printState) { + ps.writeString("[]") + if cl, ok := le.Type.(*Closure); ok { + cl.printTypes(ps) + } + ps.writeString("{...}") +} + +func (le *LambdaExpr) Traverse(fn func(AST) bool) { + if fn(le) { + le.Type.Traverse(fn) + } +} + +func (le *LambdaExpr) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(le) { + return nil + } + typ := le.Type.Copy(fn, skip) + if typ == nil { + return fn(le) + } + le = &LambdaExpr{Type: typ} + if r := fn(le); r != nil { + return r + } + return le +} + +func (le *LambdaExpr) GoString() string { + return le.goString(0, "") +} + +func (le *LambdaExpr) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sLambdaExpr:\n%s", indent, "", field, + le.Type.goString(indent+2, "")) +} + +// ExprList is a list of expressions, typically arguments to a +// function call in an expression. +type ExprList struct { + Exprs []AST +} + +func (el *ExprList) print(ps *printState) { + ps.printList(el.Exprs, nil) +} + +func (el *ExprList) Traverse(fn func(AST) bool) { + if fn(el) { + for _, e := range el.Exprs { + e.Traverse(fn) + } + } +} + +func (el *ExprList) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(el) { + return nil + } + exprs := make([]AST, len(el.Exprs)) + changed := false + for i, e := range el.Exprs { + ec := e.Copy(fn, skip) + if ec == nil { + exprs[i] = e + } else { + exprs[i] = ec + changed = true + } + } + if !changed { + return fn(el) + } + el = &ExprList{Exprs: exprs} + if r := fn(el); r != nil { + return r + } + return el +} + +func (el *ExprList) GoString() string { + return el.goString(0, "") +} + +func (el *ExprList) goString(indent int, field string) string { + if len(el.Exprs) == 0 { + return fmt.Sprintf("%*s%sExprList: nil", indent, "", field) + } + s := fmt.Sprintf("%*s%sExprList:", indent, "", field) + for i, e := range el.Exprs { + s += "\n" + s += e.goString(indent+2, fmt.Sprintf("%d: ", i)) + } + return s +} + +func (el *ExprList) prec() precedence { + return precComma +} + +// InitializerList is an initializer list: an optional type with a +// list of expressions. +type InitializerList struct { + Type AST + Exprs AST +} + +func (il *InitializerList) print(ps *printState) { + if il.Type != nil { + ps.print(il.Type) + } + ps.writeByte('{') + ps.print(il.Exprs) + ps.writeByte('}') +} + +func (il *InitializerList) Traverse(fn func(AST) bool) { + if fn(il) { + if il.Type != nil { + il.Type.Traverse(fn) + } + il.Exprs.Traverse(fn) + } +} + +func (il *InitializerList) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(il) { + return nil + } + var typ AST + if il.Type != nil { + typ = il.Type.Copy(fn, skip) + } + exprs := il.Exprs.Copy(fn, skip) + if typ == nil && exprs == nil { + return fn(il) + } + if typ == nil { + typ = il.Type + } + if exprs == nil { + exprs = il.Exprs + } + il = &InitializerList{Type: typ, Exprs: exprs} + if r := fn(il); r != nil { + return r + } + return il +} + +func (il *InitializerList) GoString() string { + return il.goString(0, "") +} + +func (il *InitializerList) goString(indent int, field string) string { + var t string + if il.Type == nil { + t = fmt.Sprintf("%*sType: nil", indent+2, "") + } else { + t = il.Type.goString(indent+2, "Type: ") + } + return fmt.Sprintf("%*s%sInitializerList:\n%s\n%s", indent, "", field, + t, il.Exprs.goString(indent+2, "Exprs: ")) +} + +// DefaultArg holds a default argument for a local name. +type DefaultArg struct { + Num int + Arg AST +} + +func (da *DefaultArg) print(ps *printState) { + if !ps.llvmStyle { + fmt.Fprintf(&ps.buf, "{default arg#%d}::", da.Num+1) + } + ps.print(da.Arg) +} + +func (da *DefaultArg) Traverse(fn func(AST) bool) { + if fn(da) { + da.Arg.Traverse(fn) + } +} + +func (da *DefaultArg) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(da) { + return nil + } + arg := da.Arg.Copy(fn, skip) + if arg == nil { + return fn(da) + } + da = &DefaultArg{Num: da.Num, Arg: arg} + if r := fn(da); r != nil { + return r + } + return da +} + +func (da *DefaultArg) GoString() string { + return da.goString(0, "") +} + +func (da *DefaultArg) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sDefaultArg: Num: %d\n%s", indent, "", field, da.Num, + da.Arg.goString(indent+2, "Arg: ")) +} + +// Closure is a closure, or lambda expression. +type Closure struct { + TemplateArgs []AST + TemplateArgsConstraint AST + Types []AST + Num int + CallConstraint AST +} + +func (cl *Closure) print(ps *printState) { + if ps.llvmStyle { + if cl.Num == 0 { + ps.writeString("'lambda'") + } else { + ps.writeString(fmt.Sprintf("'lambda%d'", cl.Num-1)) + } + } else { + ps.writeString("{lambda") + } + cl.printTypes(ps) + if !ps.llvmStyle { + ps.writeString(fmt.Sprintf("#%d}", cl.Num+1)) + } +} + +func (cl *Closure) printTypes(ps *printState) { + if len(cl.TemplateArgs) > 0 { + scopes := ps.scopes + ps.scopes = 0 + + ps.writeString("<") + ps.printList(cl.TemplateArgs, nil) + ps.writeString(">") + + ps.scopes = scopes + } + + if cl.TemplateArgsConstraint != nil { + ps.writeString(" requires ") + ps.print(cl.TemplateArgsConstraint) + ps.writeByte(' ') + } + + ps.startScope('(') + ps.printList(cl.Types, nil) + ps.endScope(')') + + if cl.CallConstraint != nil { + ps.writeString(" requires ") + ps.print(cl.CallConstraint) + } +} + +func (cl *Closure) Traverse(fn func(AST) bool) { + if fn(cl) { + for _, a := range cl.TemplateArgs { + a.Traverse(fn) + } + if cl.TemplateArgsConstraint != nil { + cl.TemplateArgsConstraint.Traverse(fn) + } + for _, t := range cl.Types { + t.Traverse(fn) + } + if cl.CallConstraint != nil { + cl.CallConstraint.Traverse(fn) + } + } +} + +func (cl *Closure) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(cl) { + return nil + } + changed := false + + args := make([]AST, len(cl.TemplateArgs)) + for i, a := range cl.TemplateArgs { + ac := a.Copy(fn, skip) + if ac == nil { + args[i] = a + } else { + args[i] = ac + changed = true + } + } + + var templateArgsConstraint AST + if cl.TemplateArgsConstraint != nil { + templateArgsConstraint = cl.TemplateArgsConstraint.Copy(fn, skip) + if templateArgsConstraint == nil { + templateArgsConstraint = cl.TemplateArgsConstraint + } else { + changed = true + } + } + + types := make([]AST, len(cl.Types)) + for i, t := range cl.Types { + tc := t.Copy(fn, skip) + if tc == nil { + types[i] = t + } else { + types[i] = tc + changed = true + } + } + + var callConstraint AST + if cl.CallConstraint != nil { + callConstraint = cl.CallConstraint.Copy(fn, skip) + if callConstraint == nil { + callConstraint = cl.CallConstraint + } else { + changed = true + } + } + + if !changed { + return fn(cl) + } + cl = &Closure{ + TemplateArgs: args, + TemplateArgsConstraint: templateArgsConstraint, + Types: types, + Num: cl.Num, + CallConstraint: callConstraint, + } + if r := fn(cl); r != nil { + return r + } + return cl +} + +func (cl *Closure) GoString() string { + return cl.goString(0, "") +} + +func (cl *Closure) goString(indent int, field string) string { + var args strings.Builder + if len(cl.TemplateArgs) == 0 { + fmt.Fprintf(&args, "%*sTemplateArgs: nil", indent+2, "") + } else { + fmt.Fprintf(&args, "%*sTemplateArgs:", indent+2, "") + for i, a := range cl.TemplateArgs { + args.WriteByte('\n') + args.WriteString(a.goString(indent+4, fmt.Sprintf("%d: ", i))) + } + } + + var templateArgsConstraint string + if cl.TemplateArgsConstraint != nil { + templateArgsConstraint = "\n" + cl.TemplateArgsConstraint.goString(indent+2, "TemplateArgsConstraint: ") + } + + var types strings.Builder + if len(cl.Types) == 0 { + fmt.Fprintf(&types, "%*sTypes: nil", indent+2, "") + } else { + fmt.Fprintf(&types, "%*sTypes:", indent+2, "") + for i, t := range cl.Types { + types.WriteByte('\n') + types.WriteString(t.goString(indent+4, fmt.Sprintf("%d: ", i))) + } + } + + var callConstraint string + if cl.CallConstraint != nil { + callConstraint = "\n" + cl.CallConstraint.goString(indent+2, "CallConstraint: ") + } + + return fmt.Sprintf("%*s%sClosure: Num: %d\n%s\n%s%s%s", indent, "", field, + cl.Num, args.String(), templateArgsConstraint, types.String(), + callConstraint) +} + +// StructuredBindings is a structured binding declaration. +type StructuredBindings struct { + Bindings []AST +} + +func (sb *StructuredBindings) print(ps *printState) { + ps.writeString("[") + ps.printList(sb.Bindings, nil) + ps.writeString("]") +} + +func (sb *StructuredBindings) Traverse(fn func(AST) bool) { + if fn(sb) { + for _, b := range sb.Bindings { + b.Traverse(fn) + } + } +} + +func (sb *StructuredBindings) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(sb) { + return nil + } + changed := false + bindings := make([]AST, len(sb.Bindings)) + for i, b := range sb.Bindings { + bc := b.Copy(fn, skip) + if bc == nil { + bindings[i] = b + } else { + bindings[i] = bc + changed = true + } + } + if !changed { + return fn(sb) + } + sb = &StructuredBindings{Bindings: bindings} + if r := fn(sb); r != nil { + return r + } + return sb +} + +func (sb *StructuredBindings) GoString() string { + return sb.goString(0, "") +} + +func (sb *StructuredBindings) goString(indent int, field string) string { + var strb strings.Builder + fmt.Fprintf(&strb, "%*s%sStructuredBinding:", indent, "", field) + for _, b := range sb.Bindings { + strb.WriteByte('\n') + strb.WriteString(b.goString(indent+2, "")) + } + return strb.String() +} + +// UnnamedType is an unnamed type, that just has an index. +type UnnamedType struct { + Num int +} + +func (ut *UnnamedType) print(ps *printState) { + if ps.llvmStyle { + if ut.Num == 0 { + ps.writeString("'unnamed'") + } else { + ps.writeString(fmt.Sprintf("'unnamed%d'", ut.Num-1)) + } + } else { + ps.writeString(fmt.Sprintf("{unnamed type#%d}", ut.Num+1)) + } +} + +func (ut *UnnamedType) Traverse(fn func(AST) bool) { + fn(ut) +} + +func (ut *UnnamedType) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(ut) { + return nil + } + return fn(ut) +} + +func (ut *UnnamedType) GoString() string { + return ut.goString(0, "") +} + +func (ut *UnnamedType) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sUnnamedType: Num: %d", indent, "", field, ut.Num) +} + +// Clone is a clone of a function, with a distinguishing suffix. +type Clone struct { + Base AST + Suffix string +} + +func (c *Clone) print(ps *printState) { + ps.print(c.Base) + if ps.llvmStyle { + ps.writeByte(' ') + ps.startScope('(') + ps.writeString(c.Suffix) + ps.endScope(')') + } else { + ps.writeString(fmt.Sprintf(" [clone %s]", c.Suffix)) + } +} + +func (c *Clone) Traverse(fn func(AST) bool) { + if fn(c) { + c.Base.Traverse(fn) + } +} + +func (c *Clone) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(c) { + return nil + } + base := c.Base.Copy(fn, skip) + if base == nil { + return fn(c) + } + c = &Clone{Base: base, Suffix: c.Suffix} + if r := fn(c); r != nil { + return r + } + return c +} + +func (c *Clone) GoString() string { + return c.goString(0, "") +} + +func (c *Clone) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sClone: Suffix: %s\n%s", indent, "", field, + c.Suffix, c.Base.goString(indent+2, "Base: ")) +} + +// Special is a special symbol, printed as a prefix plus another +// value. +type Special struct { + Prefix string + Val AST +} + +func (s *Special) print(ps *printState) { + prefix := s.Prefix + if ps.llvmStyle { + switch prefix { + case "TLS wrapper function for ": + prefix = "thread-local wrapper routine for " + case "TLS init function for ": + prefix = "thread-local initialization routine for " + } + } + ps.writeString(prefix) + ps.print(s.Val) +} + +func (s *Special) Traverse(fn func(AST) bool) { + if fn(s) { + s.Val.Traverse(fn) + } +} + +func (s *Special) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(s) { + return nil + } + val := s.Val.Copy(fn, skip) + if val == nil { + return fn(s) + } + s = &Special{Prefix: s.Prefix, Val: val} + if r := fn(s); r != nil { + return r + } + return s +} + +func (s *Special) GoString() string { + return s.goString(0, "") +} + +func (s *Special) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sSpecial: Prefix: %s\n%s", indent, "", field, + s.Prefix, s.Val.goString(indent+2, "Val: ")) +} + +// Special2 is like special, but uses two values. +type Special2 struct { + Prefix string + Val1 AST + Middle string + Val2 AST +} + +func (s *Special2) print(ps *printState) { + ps.writeString(s.Prefix) + ps.print(s.Val1) + ps.writeString(s.Middle) + ps.print(s.Val2) +} + +func (s *Special2) Traverse(fn func(AST) bool) { + if fn(s) { + s.Val1.Traverse(fn) + s.Val2.Traverse(fn) + } +} + +func (s *Special2) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(s) { + return nil + } + val1 := s.Val1.Copy(fn, skip) + val2 := s.Val2.Copy(fn, skip) + if val1 == nil && val2 == nil { + return fn(s) + } + if val1 == nil { + val1 = s.Val1 + } + if val2 == nil { + val2 = s.Val2 + } + s = &Special2{Prefix: s.Prefix, Val1: val1, Middle: s.Middle, Val2: val2} + if r := fn(s); r != nil { + return r + } + return s +} + +func (s *Special2) GoString() string { + return s.goString(0, "") +} + +func (s *Special2) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sSpecial2: Prefix: %s\n%s\n%*sMiddle: %s\n%s", indent, "", field, + s.Prefix, s.Val1.goString(indent+2, "Val1: "), + indent+2, "", s.Middle, s.Val2.goString(indent+2, "Val2: ")) +} + +// EnableIf is used by clang for an enable_if attribute. +type EnableIf struct { + Type AST + Args []AST +} + +func (ei *EnableIf) print(ps *printState) { + ps.print(ei.Type) + ps.writeString(" [enable_if:") + ps.printList(ei.Args, nil) + ps.writeString("]") +} + +func (ei *EnableIf) Traverse(fn func(AST) bool) { + if fn(ei) { + ei.Type.Traverse(fn) + for _, a := range ei.Args { + a.Traverse(fn) + } + } +} + +func (ei *EnableIf) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(ei) { + return nil + } + typ := ei.Type.Copy(fn, skip) + argsChanged := false + args := make([]AST, len(ei.Args)) + for i, a := range ei.Args { + ac := a.Copy(fn, skip) + if ac == nil { + args[i] = a + } else { + args[i] = ac + argsChanged = true + } + } + if typ == nil && !argsChanged { + return fn(ei) + } + if typ == nil { + typ = ei.Type + } + ei = &EnableIf{Type: typ, Args: args} + if r := fn(ei); r != nil { + return r + } + return ei +} + +func (ei *EnableIf) GoString() string { + return ei.goString(0, "") +} + +func (ei *EnableIf) goString(indent int, field string) string { + var args string + if len(ei.Args) == 0 { + args = fmt.Sprintf("%*sArgs: nil", indent+2, "") + } else { + args = fmt.Sprintf("%*sArgs:", indent+2, "") + for i, a := range ei.Args { + args += "\n" + args += a.goString(indent+4, fmt.Sprintf("%d: ", i)) + } + } + return fmt.Sprintf("%*s%sEnableIf:\n%s\n%s", indent, "", field, + ei.Type.goString(indent+2, "Type: "), args) +} + +// ModuleName is a C++20 module. +type ModuleName struct { + Parent AST + Name AST + IsPartition bool +} + +func (mn *ModuleName) print(ps *printState) { + if mn.Parent != nil { + ps.print(mn.Parent) + } + if mn.IsPartition { + ps.writeByte(':') + } else if mn.Parent != nil { + ps.writeByte('.') + } + ps.print(mn.Name) +} + +func (mn *ModuleName) Traverse(fn func(AST) bool) { + if fn(mn) { + mn.Parent.Traverse(fn) + mn.Name.Traverse(fn) + } +} + +func (mn *ModuleName) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(mn) { + return nil + } + var parent AST + if mn.Parent != nil { + parent = mn.Parent.Copy(fn, skip) + } + name := mn.Name.Copy(fn, skip) + if parent == nil && name == nil { + return fn(mn) + } + if parent == nil { + parent = mn.Parent + } + if name == nil { + name = mn.Name + } + mn = &ModuleName{Parent: parent, Name: name, IsPartition: mn.IsPartition} + if r := fn(mn); r != nil { + return r + } + return mn +} + +func (mn *ModuleName) GoString() string { + return mn.goString(0, "") +} + +func (mn *ModuleName) goString(indent int, field string) string { + var parent string + if mn.Parent == nil { + parent = fmt.Sprintf("%*sParent: nil", indent+2, "") + } else { + parent = mn.Parent.goString(indent+2, "Parent: ") + } + return fmt.Sprintf("%*s%sModuleName: IsPartition: %t\n%s\n%s", indent, "", field, + mn.IsPartition, parent, + mn.Name.goString(indent+2, "Name: ")) +} + +// ModuleEntity is a name inside a module. +type ModuleEntity struct { + Module AST + Name AST +} + +func (me *ModuleEntity) print(ps *printState) { + ps.print(me.Name) + ps.writeByte('@') + ps.print(me.Module) +} + +func (me *ModuleEntity) Traverse(fn func(AST) bool) { + if fn(me) { + me.Module.Traverse(fn) + me.Name.Traverse(fn) + } +} + +func (me *ModuleEntity) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(me) { + return nil + } + module := me.Module.Copy(fn, skip) + name := me.Name.Copy(fn, skip) + if module == nil && name == nil { + return fn(me) + } + if module == nil { + module = me.Module + } + if name == nil { + name = me.Name + } + me = &ModuleEntity{Module: module, Name: name} + if r := fn(me); r != nil { + return r + } + return me +} + +func (me *ModuleEntity) GoString() string { + return me.goString(0, "") +} + +func (me *ModuleEntity) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sModuleEntity:\n%s\n%s", indent, "", field, + me.Module.goString(indent+2, "Module: "), + me.Name.goString(indent+2, "Name: ")) +} + +// Friend is a member like friend name. +type Friend struct { + Name AST +} + +func (f *Friend) print(ps *printState) { + ps.writeString("friend ") + ps.print(f.Name) +} + +func (f *Friend) Traverse(fn func(AST) bool) { + if fn(f) { + f.Name.Traverse(fn) + } +} + +func (f *Friend) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(f) { + return nil + } + name := f.Name.Copy(fn, skip) + if name == nil { + return fn(f) + } + f = &Friend{Name: name} + if r := fn(f); r != nil { + return r + } + return f +} + +func (f *Friend) GoString() string { + return f.goString(0, "") +} + +func (f *Friend) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sFriend:\n%s", indent, "", field, + f.Name.goString(indent+2, "Name: ")) +} + +// Constraint represents an AST with a constraint. +type Constraint struct { + Name AST + Requires AST +} + +func (c *Constraint) print(ps *printState) { + ps.print(c.Name) + ps.writeString(" requires ") + ps.print(c.Requires) +} + +func (c *Constraint) Traverse(fn func(AST) bool) { + if fn(c) { + c.Name.Traverse(fn) + c.Requires.Traverse(fn) + } +} + +func (c *Constraint) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(c) { + return nil + } + name := c.Name.Copy(fn, skip) + requires := c.Requires.Copy(fn, skip) + if name == nil && requires == nil { + return fn(c) + } + if name == nil { + name = c.Name + } + if requires == nil { + requires = c.Requires + } + c = &Constraint{Name: name, Requires: requires} + if r := fn(c); r != nil { + return r + } + return c +} + +func (c *Constraint) GoString() string { + return c.goString(0, "") +} + +func (c *Constraint) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sConstraint:\n%s\n%s", indent, "", field, + c.Name.goString(indent+2, "Name: "), + c.Requires.goString(indent+2, "Requires: ")) +} + +// RequiresExpr is a C++20 requires expression. +type RequiresExpr struct { + Params []AST + Requirements []AST +} + +func (re *RequiresExpr) print(ps *printState) { + ps.writeString("requires") + if len(re.Params) > 0 { + ps.writeByte(' ') + ps.startScope('(') + ps.printList(re.Params, nil) + ps.endScope(')') + } + ps.writeByte(' ') + ps.startScope('{') + for _, req := range re.Requirements { + ps.print(req) + } + ps.writeByte(' ') + ps.endScope('}') +} + +func (re *RequiresExpr) Traverse(fn func(AST) bool) { + if fn(re) { + for _, p := range re.Params { + p.Traverse(fn) + } + for _, r := range re.Requirements { + r.Traverse(fn) + } + } +} + +func (re *RequiresExpr) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(re) { + return nil + } + + changed := false + + var params []AST + if len(re.Params) > 0 { + params = make([]AST, len(re.Params)) + for i, p := range re.Params { + pc := p.Copy(fn, skip) + if pc == nil { + params[i] = p + } else { + params[i] = pc + changed = true + } + } + } + + requirements := make([]AST, len(re.Requirements)) + for i, r := range re.Requirements { + rc := r.Copy(fn, skip) + if rc == nil { + requirements[i] = r + } else { + requirements[i] = rc + changed = true + } + } + + if !changed { + return fn(re) + } + + re = &RequiresExpr{Params: params, Requirements: requirements} + if r := fn(re); r != nil { + return r + } + return re +} + +func (re *RequiresExpr) GoString() string { + return re.goString(0, "") +} + +func (re *RequiresExpr) goString(indent int, field string) string { + var params strings.Builder + if len(re.Params) == 0 { + fmt.Fprintf(¶ms, "%*sParams: nil", indent+2, "") + } else { + fmt.Fprintf(¶ms, "%*sParams:", indent+2, "") + for i, p := range re.Params { + params.WriteByte('\n') + params.WriteString(p.goString(indent+4, fmt.Sprintf("%d: ", i))) + } + } + + var requirements strings.Builder + fmt.Fprintf(&requirements, "%*sRequirements:", indent+2, "") + for i, r := range re.Requirements { + requirements.WriteByte('\n') + requirements.WriteString(r.goString(indent+4, fmt.Sprintf("%d: ", i))) + } + + return fmt.Sprintf("%*s%sRequirements:\n%s\n%s", indent, "", field, + params.String(), requirements.String()) +} + +// ExprRequirement is a simple requirement in a requires expression. +// This is an arbitrary expression. +type ExprRequirement struct { + Expr AST + Noexcept bool + TypeReq AST +} + +func (er *ExprRequirement) print(ps *printState) { + ps.writeByte(' ') + if er.Noexcept || er.TypeReq != nil { + ps.startScope('{') + } + ps.print(er.Expr) + if er.Noexcept || er.TypeReq != nil { + ps.endScope('}') + } + if er.Noexcept { + ps.writeString(" noexcept") + } + if er.TypeReq != nil { + ps.writeString(" -> ") + ps.print(er.TypeReq) + } + ps.writeByte(';') +} + +func (er *ExprRequirement) Traverse(fn func(AST) bool) { + if fn(er) { + er.Expr.Traverse(fn) + if er.TypeReq != nil { + er.TypeReq.Traverse(fn) + } + } +} + +func (er *ExprRequirement) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(er) { + return nil + } + expr := er.Expr.Copy(fn, skip) + var typeReq AST + if er.TypeReq != nil { + typeReq = er.TypeReq.Copy(fn, skip) + } + if expr == nil && typeReq == nil { + return fn(er) + } + if expr == nil { + expr = er.Expr + } + if typeReq == nil { + typeReq = er.TypeReq + } + er = &ExprRequirement{Expr: expr, TypeReq: typeReq} + if r := fn(er); r != nil { + return r + } + return er +} + +func (er *ExprRequirement) GoString() string { + return er.goString(0, "") +} + +func (er *ExprRequirement) goString(indent int, field string) string { + var typeReq string + if er.TypeReq != nil { + typeReq = "\n" + er.TypeReq.goString(indent+2, "TypeReq: ") + } + + return fmt.Sprintf("%*s%sExprRequirement: Noexcept: %t\n%s%s", indent, "", field, + er.Noexcept, + er.Expr.goString(indent+2, "Expr: "), + typeReq) +} + +// TypeRequirement is a type requirement in a requires expression. +type TypeRequirement struct { + Type AST +} + +func (tr *TypeRequirement) print(ps *printState) { + ps.writeString(" typename ") + ps.print(tr.Type) + ps.writeByte(';') +} + +func (tr *TypeRequirement) Traverse(fn func(AST) bool) { + if fn(tr) { + tr.Type.Traverse(fn) + } +} + +func (tr *TypeRequirement) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(tr) { + return nil + } + typ := tr.Type.Copy(fn, skip) + if typ == nil { + return fn(tr) + } + tr = &TypeRequirement{Type: typ} + if r := fn(tr); r != nil { + return r + } + return tr +} + +func (tr *TypeRequirement) GoString() string { + return tr.goString(0, "") +} + +func (tr *TypeRequirement) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sTypeRequirement:\n%s", indent, "", field, + tr.Type.goString(indent+2, "")) +} + +// NestedRequirement is a nested requirement in a requires expression. +type NestedRequirement struct { + Constraint AST +} + +func (nr *NestedRequirement) print(ps *printState) { + ps.writeString(" requires ") + ps.print(nr.Constraint) + ps.writeByte(';') +} + +func (nr *NestedRequirement) Traverse(fn func(AST) bool) { + if fn(nr) { + nr.Constraint.Traverse(fn) + } +} + +func (nr *NestedRequirement) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(nr) { + return nil + } + constraint := nr.Constraint.Copy(fn, skip) + if constraint == nil { + return fn(nr) + } + nr = &NestedRequirement{Constraint: constraint} + if r := fn(nr); r != nil { + return r + } + return nr +} + +func (nr *NestedRequirement) GoString() string { + return nr.goString(0, "") +} + +func (nr *NestedRequirement) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sNestedRequirement:\n%s", indent, "", field, + nr.Constraint.goString(indent+2, "")) +} + +// ExplicitObjectParameter represents a C++23 explicit object parameter. +type ExplicitObjectParameter struct { + Base AST +} + +func (eop *ExplicitObjectParameter) print(ps *printState) { + ps.writeString("this ") + ps.print(eop.Base) +} + +func (eop *ExplicitObjectParameter) Traverse(fn func(AST) bool) { + if fn(eop) { + eop.Base.Traverse(fn) + } +} + +func (eop *ExplicitObjectParameter) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(eop) { + return nil + } + base := eop.Base.Copy(fn, skip) + if base == nil { + return fn(eop) + } + eop = &ExplicitObjectParameter{Base: base} + if r := fn(eop); r != nil { + return r + } + return eop +} + +func (eop *ExplicitObjectParameter) GoString() string { + return eop.goString(0, "") +} + +func (eop *ExplicitObjectParameter) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sExplicitObjectParameter:\n%s", indent, "", field, + eop.Base.goString(indent+2, "")) +} + +// Print the inner types. +func (ps *printState) printInner(prefixOnly bool) []AST { + var save []AST + var psave *[]AST + if prefixOnly { + psave = &save + } + for len(ps.inner) > 0 { + ps.printOneInner(psave) + } + return save +} + +// innerPrinter is an interface for types that can print themselves as +// inner types. +type innerPrinter interface { + printInner(*printState) +} + +// Print the most recent inner type. If save is not nil, only print +// prefixes. +func (ps *printState) printOneInner(save *[]AST) { + if len(ps.inner) == 0 { + panic("printOneInner called with no inner types") + } + ln := len(ps.inner) + a := ps.inner[ln-1] + ps.inner = ps.inner[:ln-1] + + if save != nil { + if _, ok := a.(*MethodWithQualifiers); ok { + *save = append(*save, a) + return + } + } + + if ip, ok := a.(innerPrinter); ok { + ip.printInner(ps) + } else { + ps.print(a) + } +} + +// isEmpty returns whether printing a will not print anything. +func (ps *printState) isEmpty(a AST) bool { + switch a := a.(type) { + case *ArgumentPack: + for _, a := range a.Args { + if !ps.isEmpty(a) { + return false + } + } + return true + case *ExprList: + return len(a.Exprs) == 0 + case *PackExpansion: + return a.Pack != nil && ps.isEmpty(a.Base) + default: + return false + } +} diff --git a/vendor/github.com/ianlancetaylor/demangle/demangle.go b/vendor/github.com/ianlancetaylor/demangle/demangle.go new file mode 100644 index 00000000000..90b339d6828 --- /dev/null +++ b/vendor/github.com/ianlancetaylor/demangle/demangle.go @@ -0,0 +1,3692 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package demangle defines functions that demangle GCC/LLVM +// C++ and Rust symbol names. +// This package recognizes names that were mangled according to the C++ ABI +// defined at http://codesourcery.com/cxx-abi/ and the Rust ABI +// defined at +// https://rust-lang.github.io/rfcs/2603-rust-symbol-name-mangling-v0.html +// +// Most programs will want to call Filter or ToString. +package demangle + +import ( + "errors" + "fmt" + "strings" +) + +// ErrNotMangledName is returned by CheckedDemangle if the string does +// not appear to be a C++ symbol name. +var ErrNotMangledName = errors.New("not a C++ or Rust mangled name") + +// Option is the type of demangler options. +type Option int + +const ( + // The NoParams option disables demangling of function parameters. + // It only omits the parameters of the function name being demangled, + // not the parameter types of other functions that may be mentioned. + // Using the option will speed up the demangler and cause it to + // use less memory. + NoParams Option = iota + + // The NoTemplateParams option disables demangling of template parameters. + // This applies to both C++ and Rust. + NoTemplateParams + + // The NoEnclosingParams option disables demangling of the function + // parameter types of the enclosing function when demangling a + // local name defined within a function. + NoEnclosingParams + + // The NoClones option disables inclusion of clone suffixes. + // NoParams implies NoClones. + NoClones + + // The NoRust option disables demangling of old-style Rust + // mangled names, which can be confused with C++ style mangled + // names. New style Rust mangled names are still recognized. + NoRust + + // The Verbose option turns on more verbose demangling. + Verbose + + // LLVMStyle tries to translate an AST to a string in the + // style of the LLVM demangler. This does not affect + // the parsing of the AST, only the conversion of the AST + // to a string. + LLVMStyle +) + +// maxLengthShift is how we shift the MaxLength value. +const maxLengthShift = 16 + +// maxLengthMask is a mask for the maxLength value. +const maxLengthMask = 0x1f << maxLengthShift + +// MaxLength returns an Option that limits the maximum length of a +// demangled string. The maximum length is expressed as a power of 2, +// so a value of 1 limits the returned string to 2 characters, and +// a value of 16 limits the returned string to 65,536 characters. +// The value must be between 1 and 30. +func MaxLength(pow int) Option { + if pow <= 0 || pow > 30 { + panic("demangle: invalid MaxLength value") + } + return Option(pow << maxLengthShift) +} + +// isMaxLength reports whether an Option holds a maximum length. +func isMaxLength(opt Option) bool { + return opt&maxLengthMask != 0 +} + +// maxLength returns the maximum length stored in an Option. +func maxLength(opt Option) int { + return 1 << ((opt & maxLengthMask) >> maxLengthShift) +} + +// Filter demangles a C++ or Rust symbol name, +// returning the human-readable C++ or Rust name. +// If any error occurs during demangling, the input string is returned. +func Filter(name string, options ...Option) string { + ret, err := ToString(name, options...) + if err != nil { + return name + } + return ret +} + +// ToString demangles a C++ or Rust symbol name, +// returning a human-readable C++ or Rust name or an error. +// If the name does not appear to be a C++ or Rust symbol name at all, +// the error will be ErrNotMangledName. +func ToString(name string, options ...Option) (string, error) { + if strings.HasPrefix(name, "_R") { + return rustToString(name, options) + } + + // Check for an old-style Rust mangled name. + // It starts with _ZN and ends with "17h" followed by 16 hex digits + // followed by "E" followed by an optional suffix starting with "." + // (which we ignore). + if strings.HasPrefix(name, "_ZN") { + rname := name + if pos := strings.LastIndex(rname, "E."); pos > 0 { + rname = rname[:pos+1] + } + if strings.HasSuffix(rname, "E") && len(rname) > 23 && rname[len(rname)-20:len(rname)-17] == "17h" { + noRust := false + for _, o := range options { + if o == NoRust { + noRust = true + break + } + } + if !noRust { + s, ok := oldRustToString(rname, options) + if ok { + return s, nil + } + } + } + } + + a, err := ToAST(name, options...) + if err != nil { + return "", err + } + return ASTToString(a, options...), nil +} + +// ToAST demangles a C++ symbol name into an abstract syntax tree +// representing the symbol. +// If the NoParams option is passed, and the name has a function type, +// the parameter types are not demangled. +// If the name does not appear to be a C++ symbol name at all, the +// error will be ErrNotMangledName. +// This function does not currently support Rust symbol names. +func ToAST(name string, options ...Option) (AST, error) { + if strings.HasPrefix(name, "_Z") { + a, err := doDemangle(name[2:], options...) + return a, adjustErr(err, 2) + } + + if strings.HasPrefix(name, "___Z") { + // clang extensions + block := strings.LastIndex(name, "_block_invoke") + if block == -1 { + return nil, ErrNotMangledName + } + a, err := doDemangle(name[4:block], options...) + if err != nil { + return a, adjustErr(err, 4) + } + name = strings.TrimPrefix(name[block:], "_block_invoke") + if len(name) > 0 && name[0] == '_' { + name = name[1:] + } + for len(name) > 0 && isDigit(name[0]) { + name = name[1:] + } + if len(name) > 0 && name[0] != '.' { + return nil, errors.New("unparsed characters at end of mangled name") + } + a = &Special{Prefix: "invocation function for block in ", Val: a} + return a, nil + } + + const prefix = "_GLOBAL_" + if strings.HasPrefix(name, prefix) { + // The standard demangler ignores NoParams for global + // constructors. We are compatible. + i := 0 + for i < len(options) { + if options[i] == NoParams { + options = append(options[:i], options[i+1:]...) + } else { + i++ + } + } + a, err := globalCDtorName(name[len(prefix):], options...) + return a, adjustErr(err, len(prefix)) + } + + return nil, ErrNotMangledName +} + +// globalCDtorName demangles a global constructor/destructor symbol name. +// The parameter is the string following the "_GLOBAL_" prefix. +func globalCDtorName(name string, options ...Option) (AST, error) { + if len(name) < 4 { + return nil, ErrNotMangledName + } + switch name[0] { + case '.', '_', '$': + default: + return nil, ErrNotMangledName + } + + var ctor bool + switch name[1] { + case 'I': + ctor = true + case 'D': + ctor = false + default: + return nil, ErrNotMangledName + } + + if name[2] != '_' { + return nil, ErrNotMangledName + } + + if !strings.HasPrefix(name[3:], "_Z") { + return &GlobalCDtor{Ctor: ctor, Key: &Name{Name: name}}, nil + } else { + a, err := doDemangle(name[5:], options...) + if err != nil { + return nil, adjustErr(err, 5) + } + return &GlobalCDtor{Ctor: ctor, Key: a}, nil + } +} + +// The doDemangle function is the entry point into the demangler proper. +func doDemangle(name string, options ...Option) (ret AST, err error) { + // When the demangling routines encounter an error, they panic + // with a value of type demangleErr. + defer func() { + if r := recover(); r != nil { + if de, ok := r.(demangleErr); ok { + ret = nil + err = de + return + } + panic(r) + } + }() + + params := true + clones := true + verbose := false + for _, o := range options { + switch { + case o == NoParams: + params = false + clones = false + case o == NoClones: + clones = false + case o == Verbose: + verbose = true + case o == NoTemplateParams || o == NoEnclosingParams || o == LLVMStyle || isMaxLength(o): + // These are valid options but only affect + // printing of the AST. + case o == NoRust: + // Unimportant here. + default: + return nil, fmt.Errorf("unrecognized demangler option %v", o) + } + } + + st := &state{str: name, verbose: verbose} + a := st.encoding(params, notForLocalName) + + // Accept a clone suffix. + if clones { + for len(st.str) > 1 && st.str[0] == '.' && (isLower(st.str[1]) || st.str[1] == '_' || isDigit(st.str[1])) { + a = st.cloneSuffix(a) + } + } + + if clones && len(st.str) > 0 { + st.fail("unparsed characters at end of mangled name") + } + + return a, nil +} + +// A state holds the current state of demangling a string. +type state struct { + str string // remainder of string to demangle + verbose bool // whether to use verbose demangling + off int // offset of str within original string + subs substitutions // substitutions + templates []*Template // templates being processed + + // The number of entries in templates when we started parsing + // a lambda, plus 1 so that 0 means not parsing a lambda. + lambdaTemplateLevel int + + parsingConstraint bool // whether parsing a constraint expression + + // Counts of template parameters without template arguments, + // for lambdas. + typeTemplateParamCount int + nonTypeTemplateParamCount int + templateTemplateParamCount int +} + +// copy returns a copy of the current state. +func (st *state) copy() *state { + n := new(state) + *n = *st + return n +} + +// fail panics with demangleErr, to be caught in doDemangle. +func (st *state) fail(err string) { + panic(demangleErr{err: err, off: st.off}) +} + +// failEarlier is like fail, but decrements the offset to indicate +// that the point of failure occurred earlier in the string. +func (st *state) failEarlier(err string, dec int) { + if st.off < dec { + panic("internal error") + } + panic(demangleErr{err: err, off: st.off - dec}) +} + +// advance advances the current string offset. +func (st *state) advance(add int) { + if len(st.str) < add { + panic("internal error") + } + st.str = st.str[add:] + st.off += add +} + +// checkChar requires that the next character in the string be c, and +// advances past it. +func (st *state) checkChar(c byte) { + if len(st.str) == 0 || st.str[0] != c { + panic("internal error") + } + st.advance(1) +} + +// A demangleErr is an error at a specific offset in the mangled +// string. +type demangleErr struct { + err string + off int +} + +// Error implements the builtin error interface for demangleErr. +func (de demangleErr) Error() string { + return fmt.Sprintf("%s at %d", de.err, de.off) +} + +// adjustErr adjusts the position of err, if it is a demangleErr, +// and returns err. +func adjustErr(err error, adj int) error { + if err == nil { + return nil + } + if de, ok := err.(demangleErr); ok { + de.off += adj + return de + } + return err +} + +type forLocalNameType int + +const ( + forLocalName forLocalNameType = iota + notForLocalName +) + +// encoding parses: +// +// encoding ::= <(function) name> +// <(data) name> +// +func (st *state) encoding(params bool, local forLocalNameType) AST { + if len(st.str) < 1 { + st.fail("expected encoding") + } + + if st.str[0] == 'G' || st.str[0] == 'T' { + return st.specialName() + } + + a, explicitObjectParameter := st.name() + a = simplify(a) + + if !params { + // Don't demangle the parameters. + + // Strip CV-qualifiers, as they apply to the 'this' + // parameter, and are not output by the standard + // demangler without parameters. + if mwq, ok := a.(*MethodWithQualifiers); ok { + a = mwq.Method + } + + // If this is a local name, there may be CV-qualifiers + // on the name that really apply to the top level, and + // therefore must be discarded when discarding + // parameters. This can happen when parsing a class + // that is local to a function. + if q, ok := a.(*Qualified); ok && q.LocalName { + p := &q.Name + if da, ok := (*p).(*DefaultArg); ok { + p = &da.Arg + } + if mwq, ok := (*p).(*MethodWithQualifiers); ok { + *p = mwq.Method + } + } + + return a + } + + if len(st.str) == 0 || st.str[0] == 'E' { + // There are no parameters--this is a data symbol, not + // a function symbol. + return a + } + + mwq, _ := a.(*MethodWithQualifiers) + + var findTemplate func(AST) *Template + findTemplate = func(check AST) *Template { + switch check := check.(type) { + case *Template: + return check + case *Qualified: + if check.LocalName { + return findTemplate(check.Name) + } else if _, ok := check.Name.(*Constructor); ok { + return findTemplate(check.Name) + } + case *MethodWithQualifiers: + return findTemplate(check.Method) + case *Constructor: + if check.Base != nil { + return findTemplate(check.Base) + } + } + return nil + } + + template := findTemplate(a) + var oldLambdaTemplateLevel int + if template != nil { + st.templates = append(st.templates, template) + oldLambdaTemplateLevel = st.lambdaTemplateLevel + st.lambdaTemplateLevel = 0 + } + + // Checking for the enable_if attribute here is what the LLVM + // demangler does. This is not very general but perhaps it is + // sufficient. + const enableIfPrefix = "Ua9enable_ifI" + var enableIfArgs []AST + if strings.HasPrefix(st.str, enableIfPrefix) { + st.advance(len(enableIfPrefix) - 1) + enableIfArgs = st.templateArgs() + } + + ft := st.bareFunctionType(hasReturnType(a), explicitObjectParameter) + + var constraint AST + if len(st.str) > 0 && st.str[0] == 'Q' { + constraint = st.constraintExpr() + } + + if template != nil { + st.templates = st.templates[:len(st.templates)-1] + st.lambdaTemplateLevel = oldLambdaTemplateLevel + } + + ft = simplify(ft) + + // For a local name, discard the return type, so that it + // doesn't get confused with the top level return type. + if local == forLocalName { + if functype, ok := ft.(*FunctionType); ok { + functype.ForLocalName = true + } + } + + // Any top-level qualifiers belong to the function type. + if mwq != nil { + a = mwq.Method + mwq.Method = ft + ft = mwq + } + if q, ok := a.(*Qualified); ok && q.LocalName { + p := &q.Name + if da, ok := (*p).(*DefaultArg); ok { + p = &da.Arg + } + if mwq, ok := (*p).(*MethodWithQualifiers); ok { + *p = mwq.Method + mwq.Method = ft + ft = mwq + } + } + + r := AST(&Typed{Name: a, Type: ft}) + + if len(enableIfArgs) > 0 { + r = &EnableIf{Type: r, Args: enableIfArgs} + } + + if constraint != nil { + r = &Constraint{Name: r, Requires: constraint} + } + + return r +} + +// hasReturnType returns whether the mangled form of a will have a +// return type. +func hasReturnType(a AST) bool { + switch a := a.(type) { + case *Qualified: + if a.LocalName { + return hasReturnType(a.Name) + } + return false + case *Template: + return !isCDtorConversion(a.Name) + case *TypeWithQualifiers: + return hasReturnType(a.Base) + case *MethodWithQualifiers: + return hasReturnType(a.Method) + default: + return false + } +} + +// isCDtorConversion returns when an AST is a constructor, a +// destructor, or a conversion operator. +func isCDtorConversion(a AST) bool { + switch a := a.(type) { + case *Qualified: + return isCDtorConversion(a.Name) + case *Constructor, *Destructor, *Cast: + return true + default: + return false + } +} + +// taggedName parses: +// +// ::= B +func (st *state) taggedName(a AST) AST { + for len(st.str) > 0 && st.str[0] == 'B' { + st.advance(1) + tag := st.sourceName() + a = &TaggedName{Name: a, Tag: tag} + } + return a +} + +// name parses: +// +// ::= +// ::= +// ::= +// ::= +// +// ::= +// ::= St +// +// ::= +// ::= +// +// Besides the name, this returns whether it saw the code indicating +// a C++23 explicit object parameter. +func (st *state) name() (AST, bool) { + if len(st.str) < 1 { + st.fail("expected name") + } + + var module AST + switch st.str[0] { + case 'N': + return st.nestedName() + case 'Z': + return st.localName() + case 'U': + a, isCast := st.unqualifiedName(nil) + if isCast { + st.setTemplate(a, nil) + } + return a, false + case 'S': + if len(st.str) < 2 { + st.advance(1) + st.fail("expected substitution index") + } + var a AST + isCast := false + subst := false + if st.str[1] == 't' { + st.advance(2) + a, isCast = st.unqualifiedName(nil) + a = &Qualified{Scope: &Name{Name: "std"}, Name: a, LocalName: false} + } else { + a = st.substitution(false) + if mn, ok := a.(*ModuleName); ok { + module = mn + break + } + subst = true + } + if len(st.str) > 0 && st.str[0] == 'I' { + // This can only happen if we saw + // and are about to see + // . is a + // substitution candidate if it did not come from a + // substitution. + if !subst { + st.subs.add(a) + } + args := st.templateArgs() + tmpl := &Template{Name: a, Args: args} + if isCast { + st.setTemplate(a, tmpl) + st.clearTemplateArgs(args) + isCast = false + } + a = tmpl + } + if isCast { + st.setTemplate(a, nil) + } + return a, false + } + + a, isCast := st.unqualifiedName(module) + if len(st.str) > 0 && st.str[0] == 'I' { + st.subs.add(a) + args := st.templateArgs() + tmpl := &Template{Name: a, Args: args} + if isCast { + st.setTemplate(a, tmpl) + st.clearTemplateArgs(args) + isCast = false + } + a = tmpl + } + if isCast { + st.setTemplate(a, nil) + } + return a, false +} + +// nestedName parses: +// +// ::= N [] [] E +// ::= N [] [] E +// +// Besides the name, this returns whether it saw the code indicating +// a C++23 explicit object parameter. +func (st *state) nestedName() (AST, bool) { + st.checkChar('N') + + var q AST + var r string + + explicitObjectParameter := false + if len(st.str) > 0 && st.str[0] == 'H' { + st.advance(1) + explicitObjectParameter = true + } else { + q = st.cvQualifiers() + r = st.refQualifier() + } + + a := st.prefix() + + if q != nil || r != "" { + a = &MethodWithQualifiers{Method: a, Qualifiers: q, RefQualifier: r} + } + if len(st.str) == 0 || st.str[0] != 'E' { + st.fail("expected E after nested name") + } + st.advance(1) + return a, explicitObjectParameter +} + +// prefix parses: +// +// ::= +// ::= +// ::= +// ::= +// ::= +// ::= +// +// ::= <(template) unqualified-name> +// ::= +// ::= +// +// ::= Dt E +// ::= DT E +func (st *state) prefix() AST { + var a AST + + // The last name seen, for a constructor/destructor. + var last AST + + var module AST + + getLast := func(a AST) AST { + for { + if t, ok := a.(*Template); ok { + a = t.Name + } else if q, ok := a.(*Qualified); ok { + a = q.Name + } else if t, ok := a.(*TaggedName); ok { + a = t.Name + } else { + return a + } + } + } + + var cast *Cast + for { + if len(st.str) == 0 { + st.fail("expected prefix") + } + var next AST + + c := st.str[0] + if isDigit(c) || isLower(c) || c == 'U' || c == 'L' || c == 'F' || c == 'W' || (c == 'D' && len(st.str) > 1 && st.str[1] == 'C') { + un, isUnCast := st.unqualifiedName(module) + next = un + module = nil + if isUnCast { + if tn, ok := un.(*TaggedName); ok { + un = tn.Name + } + cast = un.(*Cast) + } + } else { + switch st.str[0] { + case 'C': + inheriting := false + st.advance(1) + if len(st.str) > 0 && st.str[0] == 'I' { + inheriting = true + st.advance(1) + } + if len(st.str) < 1 { + st.fail("expected constructor type") + } + if last == nil { + st.fail("constructor before name is seen") + } + st.advance(1) + var base AST + if inheriting { + base = st.demangleType(false) + } + next = &Constructor{ + Name: getLast(last), + Base: base, + } + if len(st.str) > 0 && st.str[0] == 'B' { + next = st.taggedName(next) + } + case 'D': + if len(st.str) > 1 && (st.str[1] == 'T' || st.str[1] == 't') { + next = st.demangleType(false) + } else { + if len(st.str) < 2 { + st.fail("expected destructor type") + } + if last == nil { + st.fail("destructor before name is seen") + } + st.advance(2) + next = &Destructor{Name: getLast(last)} + if len(st.str) > 0 && st.str[0] == 'B' { + next = st.taggedName(next) + } + } + case 'S': + next = st.substitution(true) + if mn, ok := next.(*ModuleName); ok { + module = mn + next = nil + } + case 'I': + if a == nil { + st.fail("unexpected template arguments") + } + var args []AST + args = st.templateArgs() + tmpl := &Template{Name: a, Args: args} + if cast != nil { + st.setTemplate(cast, tmpl) + st.clearTemplateArgs(args) + cast = nil + } + a = nil + next = tmpl + case 'T': + next = st.templateParam() + case 'E': + if a == nil { + st.fail("expected prefix") + } + if cast != nil { + var toTmpl *Template + if castTempl, ok := cast.To.(*Template); ok { + toTmpl = castTempl + } + st.setTemplate(cast, toTmpl) + } + return a + case 'M': + if a == nil { + st.fail("unexpected lambda initializer") + } + // This is the initializer scope for a + // lambda. We don't need to record + // it. The normal code will treat the + // variable has a type scope, which + // gives appropriate output. + st.advance(1) + continue + case 'J': + // It appears that in some cases clang + // can emit a J for a template arg + // without the expected I. I don't + // know when this happens, but I've + // seen it in some large C++ programs. + if a == nil { + st.fail("unexpected template arguments") + } + var args []AST + for len(st.str) == 0 || st.str[0] != 'E' { + arg := st.templateArg(nil) + args = append(args, arg) + } + st.advance(1) + tmpl := &Template{Name: a, Args: args} + if cast != nil { + st.setTemplate(cast, tmpl) + st.clearTemplateArgs(args) + cast = nil + } + a = nil + next = tmpl + default: + st.fail("unrecognized letter in prefix") + } + } + + if next == nil { + continue + } + + last = next + if a == nil { + a = next + } else { + a = &Qualified{Scope: a, Name: next, LocalName: false} + } + + if c != 'S' && (len(st.str) == 0 || st.str[0] != 'E') { + st.subs.add(a) + } + } +} + +// unqualifiedName parses: +// +// ::= +// ::= +// ::= +// ::= +// +// ::= L +func (st *state) unqualifiedName(module AST) (r AST, isCast bool) { + if len(st.str) < 1 { + st.fail("expected unqualified name") + } + + module = st.moduleName(module) + + friend := false + if len(st.str) > 0 && st.str[0] == 'F' { + st.advance(1) + friend = true + if len(st.str) < 1 { + st.fail("expected unqualified name") + } + } + + var a AST + isCast = false + c := st.str[0] + if isDigit(c) { + a = st.sourceName() + } else if isLower(c) { + a, _ = st.operatorName(false) + if _, ok := a.(*Cast); ok { + isCast = true + } + if op, ok := a.(*Operator); ok && op.Name == `operator"" ` { + n := st.sourceName() + a = &Unary{Op: op, Expr: n, Suffix: false, SizeofType: false} + } + } else if c == 'D' && len(st.str) > 1 && st.str[1] == 'C' { + var bindings []AST + st.advance(2) + for { + binding := st.sourceName() + bindings = append(bindings, binding) + if len(st.str) > 0 && st.str[0] == 'E' { + st.advance(1) + break + } + } + a = &StructuredBindings{Bindings: bindings} + } else { + switch c { + case 'C', 'D': + st.fail("constructor/destructor not in nested name") + case 'L': + st.advance(1) + a = st.sourceName() + a = st.discriminator(a) + case 'U': + if len(st.str) < 2 { + st.advance(1) + st.fail("expected closure or unnamed type") + } + c := st.str[1] + switch c { + case 'b': + st.advance(2) + st.compactNumber() + a = &Name{Name: "'block-literal'"} + case 'l': + a = st.closureTypeName() + case 't': + a = st.unnamedTypeName() + default: + st.advance(1) + st.fail("expected closure or unnamed type") + } + default: + st.fail("expected unqualified name") + } + } + + if module != nil { + a = &ModuleEntity{Module: module, Name: a} + } + + if len(st.str) > 0 && st.str[0] == 'B' { + a = st.taggedName(a) + } + + if friend { + a = &Friend{Name: a} + } + + return a, isCast +} + +// sourceName parses: +// +// ::= <(positive length) number> +// identifier ::= <(unqualified source code identifier)> +func (st *state) sourceName() AST { + val := st.number() + if val <= 0 { + st.fail("expected positive number") + } + if len(st.str) < val { + st.fail("not enough characters for identifier") + } + id := st.str[:val] + st.advance(val) + + // Look for GCC encoding of anonymous namespace, and make it + // more friendly. + const anonPrefix = "_GLOBAL_" + if strings.HasPrefix(id, anonPrefix) && len(id) > len(anonPrefix)+2 { + c1 := id[len(anonPrefix)] + c2 := id[len(anonPrefix)+1] + if (c1 == '.' || c1 == '_' || c1 == '$') && c2 == 'N' { + id = "(anonymous namespace)" + } + } + + n := &Name{Name: id} + return n +} + +// moduleName parses: +// +// ::= +// ::= +// ::= # passed in by caller +// ::= W +// ::= W P +// +// The module name is optional. If it is not present, this returns the parent. +func (st *state) moduleName(parent AST) AST { + ret := parent + for len(st.str) > 0 && st.str[0] == 'W' { + st.advance(1) + isPartition := false + if len(st.str) > 0 && st.str[0] == 'P' { + st.advance(1) + isPartition = true + } + name := st.sourceName() + ret = &ModuleName{ + Parent: ret, + Name: name, + IsPartition: isPartition, + } + st.subs.add(ret) + } + return ret +} + +// number parses: +// +// number ::= [n] <(non-negative decimal integer)> +func (st *state) number() int { + neg := false + if len(st.str) > 0 && st.str[0] == 'n' { + neg = true + st.advance(1) + } + if len(st.str) == 0 || !isDigit(st.str[0]) { + st.fail("missing number") + } + val := 0 + for len(st.str) > 0 && isDigit(st.str[0]) { + // Number picked to ensure we can't overflow with 32-bit int. + // Any very large number here is bogus. + if val >= 0x80000000/10-10 { + st.fail("numeric overflow") + } + val = val*10 + int(st.str[0]-'0') + st.advance(1) + } + if neg { + val = -val + } + return val +} + +// seqID parses: +// +// ::= <0-9A-Z>+ +// +// We expect this to be followed by an underscore. +func (st *state) seqID(eofOK bool) int { + if len(st.str) > 0 && st.str[0] == '_' { + st.advance(1) + return 0 + } + id := 0 + for { + if len(st.str) == 0 { + if eofOK { + return id + 1 + } + st.fail("missing end to sequence ID") + } + // Don't overflow a 32-bit int. + if id >= 0x80000000/36-36 { + st.fail("sequence ID overflow") + } + c := st.str[0] + if c == '_' { + st.advance(1) + return id + 1 + } + if isDigit(c) { + id = id*36 + int(c-'0') + } else if isUpper(c) { + id = id*36 + int(c-'A') + 10 + } else { + st.fail("invalid character in sequence ID") + } + st.advance(1) + } +} + +// An operator is the demangled name, and the number of arguments it +// takes in an expression. +type operator struct { + name string + args int + prec precedence +} + +// The operators map maps the mangled operator names to information +// about them. +var operators = map[string]operator{ + "aN": {"&=", 2, precAssign}, + "aS": {"=", 2, precAssign}, + "aa": {"&&", 2, precLogicalAnd}, + "ad": {"&", 1, precUnary}, + "an": {"&", 2, precAnd}, + "at": {"alignof ", 1, precUnary}, + "aw": {"co_await ", 1, precPrimary}, + "az": {"alignof ", 1, precUnary}, + "cc": {"const_cast", 2, precPostfix}, + "cl": {"()", 2, precPostfix}, + // cp is not in the ABI but is used by clang "when the call + // would use ADL except for being parenthesized." + "cp": {"()", 2, precPostfix}, + "cm": {",", 2, precComma}, + "co": {"~", 1, precUnary}, + "dV": {"/=", 2, precAssign}, + "dX": {"[...]=", 3, precAssign}, + "da": {"delete[] ", 1, precUnary}, + "dc": {"dynamic_cast", 2, precPostfix}, + "de": {"*", 1, precUnary}, + "di": {"=", 2, precAssign}, + "dl": {"delete ", 1, precUnary}, + "ds": {".*", 2, precPtrMem}, + "dt": {".", 2, precPostfix}, + "dv": {"/", 2, precAssign}, + "dx": {"]=", 2, precAssign}, + "eO": {"^=", 2, precAssign}, + "eo": {"^", 2, precXor}, + "eq": {"==", 2, precEqual}, + "fl": {"...", 2, precPrimary}, + "fr": {"...", 2, precPrimary}, + "fL": {"...", 3, precPrimary}, + "fR": {"...", 3, precPrimary}, + "ge": {">=", 2, precRel}, + "gs": {"::", 1, precUnary}, + "gt": {">", 2, precRel}, + "ix": {"[]", 2, precPostfix}, + "lS": {"<<=", 2, precAssign}, + "le": {"<=", 2, precRel}, + "li": {`operator"" `, 1, precUnary}, + "ls": {"<<", 2, precShift}, + "lt": {"<", 2, precRel}, + "mI": {"-=", 2, precAssign}, + "mL": {"*=", 2, precAssign}, + "mi": {"-", 2, precAdd}, + "ml": {"*", 2, precMul}, + "mm": {"--", 1, precPostfix}, + "na": {"new[]", 3, precUnary}, + "ne": {"!=", 2, precEqual}, + "ng": {"-", 1, precUnary}, + "nt": {"!", 1, precUnary}, + "nw": {"new", 3, precUnary}, + "nx": {"noexcept", 1, precUnary}, + "oR": {"|=", 2, precAssign}, + "oo": {"||", 2, precLogicalOr}, + "or": {"|", 2, precOr}, + "pL": {"+=", 2, precAssign}, + "pl": {"+", 2, precAdd}, + "pm": {"->*", 2, precPtrMem}, + "pp": {"++", 1, precPostfix}, + "ps": {"+", 1, precUnary}, + "pt": {"->", 2, precPostfix}, + "qu": {"?", 3, precCond}, + "rM": {"%=", 2, precAssign}, + "rS": {">>=", 2, precAssign}, + "rc": {"reinterpret_cast", 2, precPostfix}, + "rm": {"%", 2, precMul}, + "rs": {">>", 2, precShift}, + "sP": {"sizeof...", 1, precUnary}, + "sZ": {"sizeof...", 1, precUnary}, + "sc": {"static_cast", 2, precPostfix}, + "ss": {"<=>", 2, precSpaceship}, + "st": {"sizeof ", 1, precUnary}, + "sz": {"sizeof ", 1, precUnary}, + "te": {"typeid ", 1, precPostfix}, + "ti": {"typeid ", 1, precPostfix}, + "tr": {"throw", 0, precPrimary}, + "tw": {"throw ", 1, precUnary}, +} + +// operatorName parses: +// +// operator_name ::= many different two character encodings. +// ::= cv +// ::= v +// +// We need to know whether we are in an expression because it affects +// how we handle template parameters in the type of a cast operator. +func (st *state) operatorName(inExpression bool) (AST, int) { + if len(st.str) < 2 { + st.fail("missing operator code") + } + code := st.str[:2] + st.advance(2) + if code[0] == 'v' && isDigit(code[1]) { + name := st.sourceName() + return &Operator{Name: name.(*Name).Name}, int(code[1] - '0') + } else if code == "cv" { + // Push a nil on templates to indicate that template + // parameters will have their template filled in + // later. + if !inExpression { + st.templates = append(st.templates, nil) + } + + t := st.demangleType(!inExpression) + + if !inExpression { + st.templates = st.templates[:len(st.templates)-1] + } + + return &Cast{To: t}, 1 + } else if op, ok := operators[code]; ok { + return &Operator{Name: op.name, precedence: op.prec}, op.args + } else { + st.failEarlier("unrecognized operator code", 2) + panic("not reached") + } +} + +// localName parses: +// +// ::= Z <(function) encoding> E <(entity) name> [] +// ::= Z <(function) encoding> E s [] +// ::= Z <(function) encoding> E d [ number>] _ +// +// Besides the name, this returns whether it saw the code indicating +// a C++23 explicit object parameter. +func (st *state) localName() (AST, bool) { + st.checkChar('Z') + fn := st.encoding(true, forLocalName) + if len(st.str) == 0 || st.str[0] != 'E' { + st.fail("expected E after local name") + } + st.advance(1) + if len(st.str) > 0 && st.str[0] == 's' { + st.advance(1) + var n AST = &Name{Name: "string literal"} + n = st.discriminator(n) + return &Qualified{Scope: fn, Name: n, LocalName: true}, false + } else { + num := -1 + if len(st.str) > 0 && st.str[0] == 'd' { + // Default argument scope. + st.advance(1) + num = st.compactNumber() + } + n, explicitObjectParameter := st.name() + n = st.discriminator(n) + if num >= 0 { + n = &DefaultArg{Num: num, Arg: n} + } + return &Qualified{Scope: fn, Name: n, LocalName: true}, explicitObjectParameter + } +} + +// Parse a Java resource special-name. +func (st *state) javaResource() AST { + off := st.off + ln := st.number() + if ln <= 1 { + st.failEarlier("java resource length less than 1", st.off-off) + } + if len(st.str) == 0 || st.str[0] != '_' { + st.fail("expected _ after number") + } + st.advance(1) + ln-- + if len(st.str) < ln { + st.fail("not enough characters for java resource length") + } + str := st.str[:ln] + final := "" + st.advance(ln) + for i := 0; i < len(str); i++ { + if str[i] != '$' { + final += string(str[i]) + } else { + if len(str) <= i+1 { + st.failEarlier("java resource escape at end of string", 1) + } + i++ + r, ok := map[byte]string{ + 'S': "/", + '_': ".", + '$': "$", + }[str[i]] + if !ok { + st.failEarlier("unrecognized java resource escape", ln-i-1) + } + final += r + } + } + return &Special{Prefix: "java resource ", Val: &Name{Name: final}} +} + +// specialName parses: +// +// ::= TV +// ::= TT +// ::= TI +// ::= TS +// ::= TA +// ::= GV <(object) name> +// ::= T <(base) encoding> +// ::= Tc <(base) encoding> +// g++ extensions: +// ::= TC <(offset) number> _ <(base) type> +// ::= TF +// ::= TJ +// ::= GR +// ::= GA +// ::= Gr +// ::= GTt +// ::= GTn +// ::= GI +func (st *state) specialName() AST { + if st.str[0] == 'T' { + st.advance(1) + if len(st.str) == 0 { + st.fail("expected special name code") + } + c := st.str[0] + st.advance(1) + switch c { + case 'V': + t := st.demangleType(false) + return &Special{Prefix: "vtable for ", Val: t} + case 'T': + t := st.demangleType(false) + return &Special{Prefix: "VTT for ", Val: t} + case 'I': + t := st.demangleType(false) + return &Special{Prefix: "typeinfo for ", Val: t} + case 'S': + t := st.demangleType(false) + return &Special{Prefix: "typeinfo name for ", Val: t} + case 'A': + t := st.templateArg(nil) + return &Special{Prefix: "template parameter object for ", Val: t} + case 'h': + st.callOffset('h') + v := st.encoding(true, notForLocalName) + return &Special{Prefix: "non-virtual thunk to ", Val: v} + case 'v': + st.callOffset('v') + v := st.encoding(true, notForLocalName) + return &Special{Prefix: "virtual thunk to ", Val: v} + case 'c': + st.callOffset(0) + st.callOffset(0) + v := st.encoding(true, notForLocalName) + return &Special{Prefix: "covariant return thunk to ", Val: v} + case 'C': + derived := st.demangleType(false) + off := st.off + offset := st.number() + if offset < 0 { + st.failEarlier("expected positive offset", st.off-off) + } + if len(st.str) == 0 || st.str[0] != '_' { + st.fail("expected _ after number") + } + st.advance(1) + base := st.demangleType(false) + return &Special2{Prefix: "construction vtable for ", Val1: base, Middle: "-in-", Val2: derived} + case 'F': + t := st.demangleType(false) + return &Special{Prefix: "typeinfo fn for ", Val: t} + case 'J': + t := st.demangleType(false) + return &Special{Prefix: "java Class for ", Val: t} + case 'H': + n, _ := st.name() + return &Special{Prefix: "TLS init function for ", Val: n} + case 'W': + n, _ := st.name() + return &Special{Prefix: "TLS wrapper function for ", Val: n} + default: + st.fail("unrecognized special T name code") + panic("not reached") + } + } else { + st.checkChar('G') + if len(st.str) == 0 { + st.fail("expected special name code") + } + c := st.str[0] + st.advance(1) + switch c { + case 'V': + n, _ := st.name() + return &Special{Prefix: "guard variable for ", Val: n} + case 'R': + n, _ := st.name() + st.seqID(true) + return &Special{Prefix: "reference temporary for ", Val: n} + case 'A': + v := st.encoding(true, notForLocalName) + return &Special{Prefix: "hidden alias for ", Val: v} + case 'T': + if len(st.str) == 0 { + st.fail("expected special GT name code") + } + c := st.str[0] + st.advance(1) + v := st.encoding(true, notForLocalName) + switch c { + case 'n': + return &Special{Prefix: "non-transaction clone for ", Val: v} + default: + // The proposal is that different + // letters stand for different types + // of transactional cloning. Treat + // them all the same for now. + fallthrough + case 't': + return &Special{Prefix: "transaction clone for ", Val: v} + } + case 'r': + return st.javaResource() + case 'I': + module := st.moduleName(nil) + if module == nil { + st.fail("expected module after GI") + } + return &Special{Prefix: "initializer for module ", Val: module} + default: + st.fail("unrecognized special G name code") + panic("not reached") + } + } +} + +// callOffset parses: +// +// ::= h _ +// ::= v _ +// +// ::= <(offset) number> +// +// ::= <(offset) number> _ <(virtual offset) number> +// +// The c parameter, if not 0, is a character we just read which is the +// start of the . +// +// We don't display the offset information anywhere. +func (st *state) callOffset(c byte) { + if c == 0 { + if len(st.str) == 0 { + st.fail("missing call offset") + } + c = st.str[0] + st.advance(1) + } + switch c { + case 'h': + st.number() + case 'v': + st.number() + if len(st.str) == 0 || st.str[0] != '_' { + st.fail("expected _ after number") + } + st.advance(1) + st.number() + default: + st.failEarlier("unrecognized call offset code", 1) + } + if len(st.str) == 0 || st.str[0] != '_' { + st.fail("expected _ after call offset") + } + st.advance(1) +} + +// builtinTypes maps the type letter to the type name. +var builtinTypes = map[byte]string{ + 'a': "signed char", + 'b': "bool", + 'c': "char", + 'd': "double", + 'e': "long double", + 'f': "float", + 'g': "__float128", + 'h': "unsigned char", + 'i': "int", + 'j': "unsigned int", + 'l': "long", + 'm': "unsigned long", + 'n': "__int128", + 'o': "unsigned __int128", + 's': "short", + 't': "unsigned short", + 'v': "void", + 'w': "wchar_t", + 'x': "long long", + 'y': "unsigned long long", + 'z': "...", +} + +// demangleType parses: +// +// ::= +// ::= +// ::= +// ::= +// ::= +// ::= +// ::= +// ::= +// ::= +// ::= P +// ::= R +// ::= O (C++0x) +// ::= C +// ::= G +// ::= U +// +// ::= various one letter codes +// ::= u +func (st *state) demangleType(isCast bool) AST { + if len(st.str) == 0 { + st.fail("expected type") + } + + addSubst := true + + q := st.cvQualifiers() + if q != nil { + if len(st.str) == 0 { + st.fail("expected type") + } + + // CV-qualifiers before a function type apply to + // 'this', so avoid adding the unqualified function + // type to the substitution list. + if st.str[0] == 'F' { + addSubst = false + } + } + + var ret AST + + // Use correct substitution for a template parameter. + var sub AST + + if btype, ok := builtinTypes[st.str[0]]; ok { + ret = &BuiltinType{Name: btype} + st.advance(1) + if q != nil { + ret = &TypeWithQualifiers{Base: ret, Qualifiers: q} + st.subs.add(ret) + } + return ret + } + c := st.str[0] + switch c { + case 'u': + st.advance(1) + ret = st.sourceName() + if len(st.str) > 0 && st.str[0] == 'I' { + st.advance(1) + base := st.demangleType(false) + if len(st.str) == 0 || st.str[0] != 'E' { + st.fail("expected E after transformed type") + } + st.advance(1) + ret = &TransformedType{Name: ret.(*Name).Name, Base: base} + } + case 'F': + ret = st.functionType() + case 'N', 'W', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + ret, _ = st.name() + case 'A': + ret = st.arrayType(isCast) + case 'M': + ret = st.pointerToMemberType(isCast) + case 'T': + if len(st.str) > 1 && (st.str[1] == 's' || st.str[1] == 'u' || st.str[1] == 'e') { + c = st.str[1] + st.advance(2) + ret, _ = st.name() + var kind string + switch c { + case 's': + kind = "struct" + case 'u': + kind = "union" + case 'e': + kind = "enum" + } + ret = &ElaboratedType{Kind: kind, Type: ret} + break + } + + ret = st.templateParam() + if len(st.str) > 0 && st.str[0] == 'I' { + // See the function comment to explain this. + if !isCast { + st.subs.add(ret) + args := st.templateArgs() + ret = &Template{Name: ret, Args: args} + } else { + ret = st.demangleCastTemplateArgs(ret, true) + } + } + case 'S': + // If this is a special substitution, then it + // is the start of . + var c2 byte + if len(st.str) > 1 { + c2 = st.str[1] + } + if isDigit(c2) || c2 == '_' || isUpper(c2) { + ret = st.substitution(false) + if _, ok := ret.(*ModuleName); ok { + ret, _ = st.unqualifiedName(ret) + st.subs.add(ret) + } + if len(st.str) == 0 || st.str[0] != 'I' { + addSubst = false + } else { + // See the function comment to explain this. + if _, ok := ret.(*TemplateParam); !ok || !isCast { + args := st.templateArgs() + ret = &Template{Name: ret, Args: args} + } else { + next := st.demangleCastTemplateArgs(ret, false) + if next == ret { + addSubst = false + } + ret = next + } + } + } else { + ret, _ = st.name() + // This substitution is not itself a + // substitution candidate, unless template + // arguments were added. + if ret == subAST[c2] || ret == verboseAST[c2] { + addSubst = false + } + } + case 'O', 'P', 'R', 'C', 'G': + st.advance(1) + t := st.demangleType(isCast) + switch c { + case 'O': + ret = &RvalueReferenceType{Base: t} + case 'P': + ret = &PointerType{Base: t} + case 'R': + ret = &ReferenceType{Base: t} + case 'C': + ret = &ComplexType{Base: t} + case 'G': + ret = &ImaginaryType{Base: t} + } + case 'U': + if len(st.str) < 2 { + st.fail("expected source name or unnamed type") + } + switch st.str[1] { + case 'l': + ret = st.closureTypeName() + addSubst = false + case 't': + ret = st.unnamedTypeName() + addSubst = false + default: + st.advance(1) + n := st.sourceName() + if len(st.str) > 0 && st.str[0] == 'I' { + args := st.templateArgs() + n = &Template{Name: n, Args: args} + } + t := st.demangleType(isCast) + ret = &VendorQualifier{Qualifier: n, Type: t} + } + case 'D': + st.advance(1) + if len(st.str) == 0 { + st.fail("expected D code for type") + } + addSubst = false + c2 := st.str[0] + st.advance(1) + switch c2 { + case 'T', 't': + // decltype(expression) + ret = st.expression() + if len(st.str) == 0 || st.str[0] != 'E' { + st.fail("expected E after expression in type") + } + st.advance(1) + ret = &Decltype{Expr: ret} + addSubst = true + + case 'p': + t := st.demangleType(isCast) + pack := st.findArgumentPack(t) + ret = &PackExpansion{Base: t, Pack: pack} + addSubst = true + + case 'a': + ret = &Name{Name: "auto"} + case 'c': + ret = &Name{Name: "decltype(auto)"} + + case 'f': + ret = &BuiltinType{Name: "decimal32"} + case 'd': + ret = &BuiltinType{Name: "decimal64"} + case 'e': + ret = &BuiltinType{Name: "decimal128"} + case 'h': + ret = &BuiltinType{Name: "half"} + case 'u': + ret = &BuiltinType{Name: "char8_t"} + case 's': + ret = &BuiltinType{Name: "char16_t"} + case 'i': + ret = &BuiltinType{Name: "char32_t"} + case 'n': + ret = &BuiltinType{Name: "decltype(nullptr)"} + + case 'F': + accum := false + bits := 0 + if len(st.str) > 0 && isDigit(st.str[0]) { + accum = true + bits = st.number() + } + if len(st.str) > 0 && st.str[0] == '_' { + if bits == 0 { + st.fail("expected non-zero number of bits") + } + st.advance(1) + ret = &BinaryFP{Bits: bits} + } else { + base := st.demangleType(isCast) + if len(st.str) > 0 && isDigit(st.str[0]) { + // We don't care about the bits. + st.number() + } + sat := false + if len(st.str) > 0 { + if st.str[0] == 's' { + sat = true + } + st.advance(1) + } + ret = &FixedType{Base: base, Accum: accum, Sat: sat} + } + + case 'v': + ret = st.vectorType(isCast) + addSubst = true + + case 'B', 'U': + signed := c2 == 'B' + var size AST + if len(st.str) > 0 && isDigit(st.str[0]) { + bits := st.number() + size = &Name{Name: fmt.Sprintf("%d", bits)} + } else { + size = st.expression() + } + if len(st.str) == 0 || st.str[0] != '_' { + st.fail("expected _ after _BitInt size") + } + st.advance(1) + ret = &BitIntType{Size: size, Signed: signed} + + case 'k': + constraint, _ := st.name() + ret = &SuffixType{ + Base: constraint, + Suffix: "auto", + } + + case 'K': + constraint, _ := st.name() + ret = &SuffixType{ + Base: constraint, + Suffix: "decltype(auto)", + } + + default: + st.fail("unrecognized D code in type") + } + + default: + st.fail("unrecognized type code") + } + + if addSubst { + if sub != nil { + st.subs.add(sub) + } else { + st.subs.add(ret) + } + } + + if q != nil { + if _, ok := ret.(*FunctionType); ok { + ret = &MethodWithQualifiers{Method: ret, Qualifiers: q, RefQualifier: ""} + } else if mwq, ok := ret.(*MethodWithQualifiers); ok { + // Merge adjacent qualifiers. This case + // happens with a function with a trailing + // ref-qualifier. + mwq.Qualifiers = mergeQualifiers(q, mwq.Qualifiers) + } else { + // Merge adjacent qualifiers. This case + // happens with multi-dimensional array types. + if qsub, ok := ret.(*TypeWithQualifiers); ok { + q = mergeQualifiers(q, qsub.Qualifiers) + ret = qsub.Base + } + ret = &TypeWithQualifiers{Base: ret, Qualifiers: q} + } + st.subs.add(ret) + } + + return ret +} + +// demangleCastTemplateArgs is for a rather hideous parse. When we +// see a template-param followed by a template-args, we need to decide +// whether we have a template-param or a template-template-param. +// Normally it is template-template-param, meaning that we pick up the +// template arguments here. But, if we are parsing the type for a +// cast operator, then the only way this can be template-template-param +// is if there is another set of template-args immediately after this +// set. That would look like this: +// +// +// -> +// -> +// -> +// -> +// -> +// -> cv +// -> cv +// +// Otherwise, we have this derivation: +// +// +// -> +// -> +// -> +// -> +// -> +// -> cv +// -> cv +// +// in which the template-args are actually part of the prefix. For +// the special case where this arises, demangleType is called with +// isCast as true. This function is then responsible for checking +// whether we see but there is not +// another following . In that case, we reset the +// parse and just return the . +func (st *state) demangleCastTemplateArgs(tp AST, addSubst bool) AST { + save := st.copy() + + var args []AST + failed := false + func() { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(demangleErr); ok { + failed = true + } else { + panic(r) + } + } + }() + + args = st.templateArgs() + }() + + if !failed && len(st.str) > 0 && st.str[0] == 'I' { + if addSubst { + st.subs.add(tp) + } + return &Template{Name: tp, Args: args} + } + // Reset back to before we started reading the template arguments. + // They will be read again by st.prefix. + *st = *save + return tp +} + +// mergeQualifiers merges two qualifier lists into one. +func mergeQualifiers(q1AST, q2AST AST) AST { + if q1AST == nil { + return q2AST + } + if q2AST == nil { + return q1AST + } + q1 := q1AST.(*Qualifiers) + m := make(map[string]bool) + for _, qualAST := range q1.Qualifiers { + qual := qualAST.(*Qualifier) + if len(qual.Exprs) == 0 { + m[qual.Name] = true + } + } + rq := q1.Qualifiers + for _, qualAST := range q2AST.(*Qualifiers).Qualifiers { + qual := qualAST.(*Qualifier) + if len(qual.Exprs) > 0 { + rq = append(rq, qualAST) + } else if !m[qual.Name] { + rq = append(rq, qualAST) + m[qual.Name] = true + } + } + q1.Qualifiers = rq + return q1 +} + +// qualifiers maps from the character used in the mangled name to the +// string to print. +var qualifiers = map[byte]string{ + 'r': "restrict", + 'V': "volatile", + 'K': "const", +} + +// cvQualifiers parses: +// +// ::= [r] [V] [K] +func (st *state) cvQualifiers() AST { + var q []AST +qualLoop: + for len(st.str) > 0 { + if qv, ok := qualifiers[st.str[0]]; ok { + qual := &Qualifier{Name: qv} + q = append([]AST{qual}, q...) + st.advance(1) + } else if len(st.str) > 1 && st.str[0] == 'D' { + var qual AST + switch st.str[1] { + case 'x': + qual = &Qualifier{Name: "transaction_safe"} + st.advance(2) + case 'o': + qual = &Qualifier{Name: "noexcept"} + st.advance(2) + case 'O': + st.advance(2) + expr := st.expression() + if len(st.str) == 0 || st.str[0] != 'E' { + st.fail("expected E after computed noexcept expression") + } + st.advance(1) + qual = &Qualifier{Name: "noexcept", Exprs: []AST{expr}} + case 'w': + st.advance(2) + parmlist := st.parmlist(false) + if len(st.str) == 0 || st.str[0] != 'E' { + st.fail("expected E after throw parameter list") + } + st.advance(1) + qual = &Qualifier{Name: "throw", Exprs: parmlist} + default: + break qualLoop + } + q = append([]AST{qual}, q...) + } else { + break + } + } + if len(q) == 0 { + return nil + } + return &Qualifiers{Qualifiers: q} +} + +// refQualifier parses: +// +// ::= R +// ::= O +func (st *state) refQualifier() string { + if len(st.str) > 0 { + switch st.str[0] { + case 'R': + st.advance(1) + return "&" + case 'O': + st.advance(1) + return "&&" + } + } + return "" +} + +// parmlist parses: +// +// + +func (st *state) parmlist(explicitObjectParameter bool) []AST { + var ret []AST + for { + if len(st.str) < 1 { + break + } + if st.str[0] == 'E' || st.str[0] == '.' { + break + } + if (st.str[0] == 'R' || st.str[0] == 'O') && len(st.str) > 1 && st.str[1] == 'E' { + // This is a function ref-qualifier. + break + } + if st.str[0] == 'Q' { + // This is a requires clause. + break + } + ptype := st.demangleType(false) + + if len(ret) == 0 && explicitObjectParameter { + ptype = &ExplicitObjectParameter{Base: ptype} + } + + ret = append(ret, ptype) + } + + // There should always be at least one type. A function that + // takes no arguments will have a single parameter type + // "void". + if len(ret) == 0 { + st.fail("expected at least one type in type list") + } + + // Omit a single parameter type void. + if len(ret) == 1 { + if bt, ok := ret[0].(*BuiltinType); ok && bt.Name == "void" { + ret = nil + } + } + + return ret +} + +// functionType parses: +// +// ::= F [Y] [] E +func (st *state) functionType() AST { + st.checkChar('F') + if len(st.str) > 0 && st.str[0] == 'Y' { + // Function has C linkage. We don't print this. + st.advance(1) + } + ret := st.bareFunctionType(true, false) + r := st.refQualifier() + if r != "" { + ret = &MethodWithQualifiers{Method: ret, Qualifiers: nil, RefQualifier: r} + } + if len(st.str) == 0 || st.str[0] != 'E' { + st.fail("expected E after function type") + } + st.advance(1) + return ret +} + +// bareFunctionType parses: +// +// ::= [J]+ +func (st *state) bareFunctionType(hasReturnType, explicitObjectParameter bool) AST { + if len(st.str) > 0 && st.str[0] == 'J' { + hasReturnType = true + st.advance(1) + } + var returnType AST + if hasReturnType { + returnType = st.demangleType(false) + } + types := st.parmlist(explicitObjectParameter) + return &FunctionType{ + Return: returnType, + Args: types, + ForLocalName: false, // may be set later in encoding + } +} + +// arrayType parses: +// +// ::= A <(positive dimension) number> _ <(element) type> +// ::= A [<(dimension) expression>] _ <(element) type> +func (st *state) arrayType(isCast bool) AST { + st.checkChar('A') + + if len(st.str) == 0 { + st.fail("missing array dimension") + } + + var dim AST + if st.str[0] == '_' { + dim = &Name{Name: ""} + } else if isDigit(st.str[0]) { + i := 1 + for len(st.str) > i && isDigit(st.str[i]) { + i++ + } + dim = &Name{Name: st.str[:i]} + st.advance(i) + } else { + dim = st.expression() + } + + if len(st.str) == 0 || st.str[0] != '_' { + st.fail("expected _ after dimension") + } + st.advance(1) + + t := st.demangleType(isCast) + + arr := &ArrayType{Dimension: dim, Element: t} + + // Qualifiers on the element of an array type go on the whole + // array type. + if q, ok := arr.Element.(*TypeWithQualifiers); ok { + return &TypeWithQualifiers{Base: &ArrayType{Dimension: dim, Element: q.Base}, Qualifiers: q.Qualifiers} + } + + return arr +} + +// vectorType parses: +// +// ::= Dv _ +// ::= Dv _ _ +func (st *state) vectorType(isCast bool) AST { + if len(st.str) == 0 { + st.fail("expected vector dimension") + } + + var dim AST + if st.str[0] == '_' { + st.advance(1) + dim = st.expression() + } else { + num := st.number() + dim = &Name{Name: fmt.Sprintf("%d", num)} + } + + if len(st.str) == 0 || st.str[0] != '_' { + st.fail("expected _ after vector dimension") + } + st.advance(1) + + t := st.demangleType(isCast) + + return &VectorType{Dimension: dim, Base: t} +} + +// pointerToMemberType parses: +// +// ::= M <(class) type> <(member) type> +func (st *state) pointerToMemberType(isCast bool) AST { + st.checkChar('M') + cl := st.demangleType(false) + + // The ABI says, "The type of a non-static member function is + // considered to be different, for the purposes of + // substitution, from the type of a namespace-scope or static + // member function whose type appears similar. The types of + // two non-static member functions are considered to be + // different, for the purposes of substitution, if the + // functions are members of different classes. In other words, + // for the purposes of substitution, the class of which the + // function is a member is considered part of the type of + // function." + // + // For a pointer to member function, this call to demangleType + // will end up adding a (possibly qualified) non-member + // function type to the substitution table, which is not + // correct; however, the member function type will never be + // used in a substitution, so putting the wrong type in the + // substitution table is harmless. + mem := st.demangleType(isCast) + return &PtrMem{Class: cl, Member: mem} +} + +// compactNumber parses: +// +// _ +func (st *state) compactNumber() int { + if len(st.str) == 0 { + st.fail("missing index") + } + if st.str[0] == '_' { + st.advance(1) + return 0 + } else if st.str[0] == 'n' { + st.fail("unexpected negative number") + } + n := st.number() + if len(st.str) == 0 || st.str[0] != '_' { + st.fail("missing underscore after number") + } + st.advance(1) + return n + 1 +} + +// templateParam parses: +// +// ::= T_ +// ::= T <(parameter-2 non-negative) number> _ +// ::= TL __ +// ::= TL _ _ +// +// When a template parameter is a substitution candidate, any +// reference to that substitution refers to the template parameter +// with the same index in the currently active template, not to +// whatever the template parameter would be expanded to here. We sort +// this out in substitution and simplify. +func (st *state) templateParam() AST { + off := st.off + str := st.str + st.checkChar('T') + + level := 0 + if len(st.str) > 0 && st.str[0] == 'L' { + st.advance(1) + level = st.compactNumber() + } + + n := st.compactNumber() + + // We don't try to substitute template parameters in a + // constraint expression. + if st.parsingConstraint { + return &Name{Name: str[:st.off-1-off]} + } + + if level >= len(st.templates) { + if st.lambdaTemplateLevel > 0 && level == st.lambdaTemplateLevel-1 { + // Lambda auto params are mangled as template params. + // See https://gcc.gnu.org/PR78252. + return &LambdaAuto{Index: n} + } + st.failEarlier(fmt.Sprintf("template parameter is not in scope of template (level %d >= %d)", level, len(st.templates)), st.off-off) + } + + template := st.templates[level] + + if template == nil { + // We are parsing a cast operator. If the cast is + // itself a template, then this is a forward + // reference. Fill it in later. + return &TemplateParam{Index: n, Template: nil} + } + + if n >= len(template.Args) { + if st.lambdaTemplateLevel > 0 && level == st.lambdaTemplateLevel-1 { + // Lambda auto params are mangled as template params. + // See https://gcc.gnu.org/PR78252. + return &LambdaAuto{Index: n} + } + st.failEarlier(fmt.Sprintf("template index out of range (%d >= %d)", n, len(template.Args)), st.off-off) + } + + return &TemplateParam{Index: n, Template: template} +} + +// setTemplate sets the Template field of any TemplateParam's in a. +// This handles the forward referencing template parameters found in +// cast operators. +func (st *state) setTemplate(a AST, tmpl *Template) { + seen := make(map[AST]bool) + a.Traverse(func(a AST) bool { + switch a := a.(type) { + case *TemplateParam: + if a.Template != nil { + if tmpl != nil { + st.fail("duplicate template parameters") + } + return false + } + if tmpl == nil { + st.fail("cast template parameter not in scope of template") + } + if a.Index >= len(tmpl.Args) { + st.fail(fmt.Sprintf("cast template index out of range (%d >= %d)", a.Index, len(tmpl.Args))) + } + a.Template = tmpl + return false + case *Closure: + // There are no template params in closure types. + // https://gcc.gnu.org/PR78252. + return false + default: + if seen[a] { + return false + } + seen[a] = true + return true + } + }) +} + +// clearTemplateArgs gives an error for any unset Template field in +// args. This handles erroneous cases where a cast operator with a +// forward referenced template is in the scope of another cast +// operator. +func (st *state) clearTemplateArgs(args []AST) { + for _, a := range args { + st.setTemplate(a, nil) + } +} + +// templateArgs parses: +// +// ::= I + E +func (st *state) templateArgs() []AST { + if len(st.str) == 0 || (st.str[0] != 'I' && st.str[0] != 'J') { + panic("internal error") + } + st.advance(1) + + var ret []AST + for len(st.str) == 0 || st.str[0] != 'E' { + arg := st.templateArg(ret) + ret = append(ret, arg) + + if len(st.str) > 0 && st.str[0] == 'Q' { + // A list of template arguments can have a + // constraint, but we don't demangle it. + st.constraintExpr() + if len(st.str) == 0 || st.str[0] != 'E' { + st.fail("expected end of template arguments after constraint") + } + } + } + st.advance(1) + return ret +} + +// templateArg parses: +// +// ::= +// ::= X E +// ::= +// ::= J * E +// ::= LZ E +// ::= +func (st *state) templateArg(prev []AST) AST { + if len(st.str) == 0 { + st.fail("missing template argument") + } + switch st.str[0] { + case 'X': + st.advance(1) + expr := st.expression() + if len(st.str) == 0 || st.str[0] != 'E' { + st.fail("missing end of expression") + } + st.advance(1) + return expr + + case 'L': + return st.exprPrimary() + + case 'I', 'J': + args := st.templateArgs() + return &ArgumentPack{Args: args} + + case 'T': + var arg byte + if len(st.str) > 1 { + arg = st.str[1] + } + switch arg { + case 'y', 'n', 't', 'p', 'k': + off := st.off + + // Apparently template references in the + // template parameter refer to previous + // arguments in the same template. + template := &Template{Args: prev} + st.templates = append(st.templates, template) + + param, _ := st.templateParamDecl() + + st.templates = st.templates[:len(st.templates)-1] + + if param == nil { + st.failEarlier("expected template parameter as template argument", st.off-off) + } + arg := st.templateArg(nil) + return &TemplateParamQualifiedArg{Param: param, Arg: arg} + } + return st.demangleType(false) + + default: + return st.demangleType(false) + } +} + +// exprList parses a sequence of expressions up to a terminating character. +func (st *state) exprList(stop byte) AST { + if len(st.str) > 0 && st.str[0] == stop { + st.advance(1) + return &ExprList{Exprs: nil} + } + + var exprs []AST + for { + e := st.expression() + exprs = append(exprs, e) + if len(st.str) > 0 && st.str[0] == stop { + st.advance(1) + break + } + } + return &ExprList{Exprs: exprs} +} + +// expression parses: +// +// ::= <(unary) operator-name> +// ::= <(binary) operator-name> +// ::= <(trinary) operator-name> +// ::= pp_ +// ::= mm_ +// ::= cl + E +// ::= cl + E +// ::= cv +// ::= cv _ * E +// ::= tl * E +// ::= il * E +// ::= [gs] nw * _ E +// ::= [gs] nw * _ +// ::= [gs] na * _ E +// ::= [gs] na * _ +// ::= [gs] dl +// ::= [gs] da +// ::= dc +// ::= sc +// ::= cc +// ::= mc [] E +// ::= rc +// ::= ti +// ::= te +// ::= so [] * [p] E +// ::= st +// ::= sz +// ::= at +// ::= az +// ::= nx +// ::= +// ::= +// ::= dt +// ::= pt +// ::= ds +// ::= sZ +// ::= sZ +// ::= sP * E +// ::= sp +// ::= fl +// ::= fr +// ::= fL +// ::= fR +// ::= tw +// ::= tr +// ::= u * E +// ::= +// ::= +// +// ::= fp _ +// ::= fp +// ::= fL p _ +// ::= fL p +// ::= fpT +// +// ::= +// ::= di +// ::= dx +// ::= dX +func (st *state) expression() AST { + if len(st.str) == 0 { + st.fail("expected expression") + } + if st.str[0] == 'L' { + return st.exprPrimary() + } else if st.str[0] == 'T' { + return st.templateParam() + } else if st.str[0] == 's' && len(st.str) > 1 && st.str[1] == 'o' { + st.advance(2) + return st.subobject() + } else if st.str[0] == 's' && len(st.str) > 1 && st.str[1] == 'r' { + return st.unresolvedName() + } else if st.str[0] == 's' && len(st.str) > 1 && st.str[1] == 'p' { + st.advance(2) + e := st.expression() + pack := st.findArgumentPack(e) + return &PackExpansion{Base: e, Pack: pack} + } else if st.str[0] == 's' && len(st.str) > 1 && st.str[1] == 'Z' { + st.advance(2) + off := st.off + e := st.expression() + ap := st.findArgumentPack(e) + if ap == nil { + st.failEarlier("missing argument pack", st.off-off) + } + return &SizeofPack{Pack: ap} + } else if st.str[0] == 's' && len(st.str) > 1 && st.str[1] == 'P' { + st.advance(2) + var args []AST + for len(st.str) == 0 || st.str[0] != 'E' { + arg := st.templateArg(nil) + args = append(args, arg) + } + st.advance(1) + return &SizeofArgs{Args: args} + } else if st.str[0] == 'f' && len(st.str) > 1 && st.str[1] == 'p' { + st.advance(2) + if len(st.str) > 0 && st.str[0] == 'T' { + st.advance(1) + return &FunctionParam{Index: 0} + } else { + // We can see qualifiers here, but we don't + // include them in the demangled string. + st.cvQualifiers() + index := st.compactNumber() + return &FunctionParam{Index: index + 1} + } + } else if st.str[0] == 'f' && len(st.str) > 2 && st.str[1] == 'L' && isDigit(st.str[2]) { + st.advance(2) + // We don't include the scope count in the demangled string. + st.number() + if len(st.str) == 0 || st.str[0] != 'p' { + st.fail("expected p after function parameter scope count") + } + st.advance(1) + // We can see qualifiers here, but we don't include them + // in the demangled string. + st.cvQualifiers() + index := st.compactNumber() + return &FunctionParam{Index: index + 1} + } else if st.str[0] == 'm' && len(st.str) > 1 && st.str[1] == 'c' { + st.advance(2) + typ := st.demangleType(false) + expr := st.expression() + offset := 0 + if len(st.str) > 0 && (st.str[0] == 'n' || isDigit(st.str[0])) { + offset = st.number() + } + if len(st.str) == 0 || st.str[0] != 'E' { + st.fail("expected E after pointer-to-member conversion") + } + st.advance(1) + return &PtrMemCast{ + Type: typ, + Expr: expr, + Offset: offset, + } + } else if isDigit(st.str[0]) || (st.str[0] == 'o' && len(st.str) > 1 && st.str[1] == 'n') { + if st.str[0] == 'o' { + // Skip operator function ID. + st.advance(2) + } + n, _ := st.unqualifiedName(nil) + if len(st.str) > 0 && st.str[0] == 'I' { + args := st.templateArgs() + n = &Template{Name: n, Args: args} + } + return n + } else if (st.str[0] == 'i' || st.str[0] == 't') && len(st.str) > 1 && st.str[1] == 'l' { + // Brace-enclosed initializer list. + c := st.str[0] + st.advance(2) + var t AST + if c == 't' { + t = st.demangleType(false) + } + exprs := st.exprList('E') + return &InitializerList{Type: t, Exprs: exprs} + } else if st.str[0] == 's' && len(st.str) > 1 && st.str[1] == 't' { + o, _ := st.operatorName(true) + t := st.demangleType(false) + return &Unary{Op: o, Expr: t, Suffix: false, SizeofType: true} + } else if st.str[0] == 'u' { + st.advance(1) + name := st.sourceName() + // Special case __uuidof followed by type or + // expression, as used by LLVM. + if n, ok := name.(*Name); ok && n.Name == "__uuidof" { + if len(st.str) < 2 { + st.fail("missing uuidof argument") + } + var operand AST + if st.str[0] == 't' { + st.advance(1) + operand = st.demangleType(false) + } else if st.str[0] == 'z' { + st.advance(1) + operand = st.expression() + } + if operand != nil { + return &Binary{ + Op: &Operator{Name: "()"}, + Left: name, + Right: &ExprList{ + Exprs: []AST{operand}, + }, + } + } + } + var args []AST + for { + if len(st.str) == 0 { + st.fail("missing argument in vendor extended expressoin") + } + if st.str[0] == 'E' { + st.advance(1) + break + } + arg := st.templateArg(nil) + args = append(args, arg) + } + return &Binary{ + Op: &Operator{Name: "()"}, + Left: name, + Right: &ExprList{Exprs: args}, + } + } else if st.str[0] == 'r' && len(st.str) > 1 && (st.str[1] == 'q' || st.str[1] == 'Q') { + return st.requiresExpr() + } else { + if len(st.str) < 2 { + st.fail("missing operator code") + } + code := st.str[:2] + o, args := st.operatorName(true) + switch args { + case 0: + return &Nullary{Op: o} + + case 1: + suffix := false + if code == "pp" || code == "mm" { + if len(st.str) > 0 && st.str[0] == '_' { + st.advance(1) + } else { + suffix = true + } + } + var operand AST + if _, ok := o.(*Cast); ok && len(st.str) > 0 && st.str[0] == '_' { + st.advance(1) + operand = st.exprList('E') + } else { + operand = st.expression() + } + return &Unary{Op: o, Expr: operand, Suffix: suffix, SizeofType: false} + + case 2: + var left, right AST + if code == "sc" || code == "dc" || code == "cc" || code == "rc" { + left = st.demangleType(false) + } else if code[0] == 'f' { + left, _ = st.operatorName(true) + right = st.expression() + return &Fold{Left: code[1] == 'l', Op: left, Arg1: right, Arg2: nil} + } else if code == "di" { + left, _ = st.unqualifiedName(nil) + } else { + left = st.expression() + } + if code == "cl" || code == "cp" { + right = st.exprList('E') + } else if code == "dt" || code == "pt" { + if len(st.str) > 0 && st.str[0] == 'L' { + right = st.exprPrimary() + } else { + right = st.unresolvedName() + if len(st.str) > 0 && st.str[0] == 'I' { + args := st.templateArgs() + right = &Template{Name: right, Args: args} + } + } + } else { + right = st.expression() + } + return &Binary{Op: o, Left: left, Right: right} + + case 3: + if code[0] == 'n' { + if code[1] != 'w' && code[1] != 'a' { + panic("internal error") + } + place := st.exprList('_') + if place.(*ExprList).Exprs == nil { + place = nil + } + t := st.demangleType(false) + var ini AST + if len(st.str) > 0 && st.str[0] == 'E' { + st.advance(1) + } else if len(st.str) > 1 && st.str[0] == 'p' && st.str[1] == 'i' { + // Parenthesized initializer. + st.advance(2) + ini = st.exprList('E') + } else if len(st.str) > 1 && st.str[0] == 'i' && st.str[1] == 'l' { + // Initializer list. + ini = st.expression() + } else { + st.fail("unrecognized new initializer") + } + return &New{Op: o, Place: place, Type: t, Init: ini} + } else if code[0] == 'f' { + first, _ := st.operatorName(true) + second := st.expression() + third := st.expression() + return &Fold{Left: code[1] == 'L', Op: first, Arg1: second, Arg2: third} + } else { + first := st.expression() + second := st.expression() + third := st.expression() + return &Trinary{Op: o, First: first, Second: second, Third: third} + } + + default: + st.fail(fmt.Sprintf("unsupported number of operator arguments: %d", args)) + panic("not reached") + } + } +} + +// subobject parses: +// +// ::= so [] * [p] E +// ::= _ [] +func (st *state) subobject() AST { + typ := st.demangleType(false) + expr := st.expression() + offset := 0 + if len(st.str) > 0 && (st.str[0] == 'n' || isDigit(st.str[0])) { + offset = st.number() + } + var selectors []int + for len(st.str) > 0 && st.str[0] == '_' { + st.advance(1) + selector := 0 + if len(st.str) > 0 && (st.str[0] == 'n' || isDigit(st.str[0])) { + selector = st.number() + } + selectors = append(selectors, selector) + } + pastEnd := false + if len(st.str) > 0 && st.str[0] == 'p' { + st.advance(1) + pastEnd = true + } + if len(st.str) == 0 || st.str[0] != 'E' { + st.fail("expected E after subobject") + } + st.advance(1) + return &Subobject{ + Type: typ, + SubExpr: expr, + Offset: offset, + Selectors: selectors, + PastEnd: pastEnd, + } +} + +// unresolvedName parses: +// +// ::= [gs] +// ::= sr +// ::= srN + E +// ::= [gs] sr + E +func (st *state) unresolvedName() AST { + if len(st.str) >= 2 && st.str[:2] == "gs" { + st.advance(2) + n := st.unresolvedName() + return &Unary{ + Op: &Operator{Name: "::"}, + Expr: n, + Suffix: false, + SizeofType: false, + } + } else if len(st.str) >= 2 && st.str[:2] == "sr" { + st.advance(2) + if len(st.str) == 0 { + st.fail("expected unresolved type") + } + switch st.str[0] { + case 'T', 'D', 'S': + t := st.demangleType(false) + n := st.baseUnresolvedName() + n = &Qualified{Scope: t, Name: n, LocalName: false} + if len(st.str) > 0 && st.str[0] == 'I' { + args := st.templateArgs() + n = &Template{Name: n, Args: args} + st.subs.add(n) + } + return n + default: + var s AST + if st.str[0] == 'N' { + st.advance(1) + s = st.demangleType(false) + } + for len(st.str) == 0 || st.str[0] != 'E' { + // GCC does not seem to follow the ABI here. + // It can emit type/name without an 'E'. + if s != nil && len(st.str) > 0 && !isDigit(st.str[0]) { + if q, ok := s.(*Qualified); ok { + a := q.Scope + if t, ok := a.(*Template); ok { + st.subs.add(t.Name) + st.subs.add(t) + } else { + st.subs.add(a) + } + return s + } + } + n := st.sourceName() + if len(st.str) > 0 && st.str[0] == 'I' { + st.subs.add(n) + args := st.templateArgs() + n = &Template{Name: n, Args: args} + } + if s == nil { + s = n + } else { + s = &Qualified{Scope: s, Name: n, LocalName: false} + } + } + if s == nil { + st.fail("missing scope in unresolved name") + } + st.advance(1) + n := st.baseUnresolvedName() + return &Qualified{Scope: s, Name: n, LocalName: false} + } + } else { + return st.baseUnresolvedName() + } +} + +// baseUnresolvedName parses: +// +// ::= +// ::= on +// ::= on +// ::= dn +// +// ::= [ ] +func (st *state) baseUnresolvedName() AST { + var n AST + if len(st.str) >= 2 && st.str[:2] == "on" { + st.advance(2) + n, _ = st.operatorName(true) + } else if len(st.str) >= 2 && st.str[:2] == "dn" { + st.advance(2) + if len(st.str) > 0 && isDigit(st.str[0]) { + n = st.sourceName() + } else { + n = st.demangleType(false) + } + n = &Destructor{Name: n} + } else if len(st.str) > 0 && isDigit(st.str[0]) { + n = st.sourceName() + } else { + // GCC seems to not follow the ABI here: it can have + // an operator name without on. + // See https://gcc.gnu.org/PR70182. + n, _ = st.operatorName(true) + } + if len(st.str) > 0 && st.str[0] == 'I' { + args := st.templateArgs() + n = &Template{Name: n, Args: args} + } + return n +} + +// requiresExpr parses: +// +// ::= rQ _ + E +// ::= rq + E +// ::= X [N] [R ] +// ::= T +// ::= Q +func (st *state) requiresExpr() AST { + st.checkChar('r') + if len(st.str) == 0 || (st.str[0] != 'q' && st.str[0] != 'Q') { + st.fail("expected q or Q in requires clause in expression") + } + kind := st.str[0] + st.advance(1) + + var params []AST + if kind == 'Q' { + for len(st.str) > 0 && st.str[0] != '_' { + typ := st.demangleType(false) + params = append(params, typ) + } + st.advance(1) + } + + var requirements []AST + for len(st.str) > 0 && st.str[0] != 'E' { + var req AST + switch st.str[0] { + case 'X': + st.advance(1) + expr := st.expression() + var noexcept bool + if len(st.str) > 0 && st.str[0] == 'N' { + st.advance(1) + noexcept = true + } + var typeReq AST + if len(st.str) > 0 && st.str[0] == 'R' { + st.advance(1) + typeReq, _ = st.name() + } + req = &ExprRequirement{ + Expr: expr, + Noexcept: noexcept, + TypeReq: typeReq, + } + + case 'T': + st.advance(1) + typ := st.demangleType(false) + req = &TypeRequirement{Type: typ} + + case 'Q': + st.advance(1) + // We parse a regular expression rather than a + // constraint expression. + expr := st.expression() + req = &NestedRequirement{Constraint: expr} + + default: + st.fail("unrecognized requirement code") + } + + requirements = append(requirements, req) + } + + if len(st.str) == 0 || st.str[0] != 'E' { + st.fail("expected E after requirements") + } + st.advance(1) + + return &RequiresExpr{ + Params: params, + Requirements: requirements, + } +} + +// exprPrimary parses: +// +// ::= L <(value) number> E +// ::= L <(value) float> E +// ::= L E +func (st *state) exprPrimary() AST { + st.checkChar('L') + if len(st.str) == 0 { + st.fail("expected primary expression") + + } + + // Check for 'Z' here because g++ incorrectly omitted the + // underscore until -fabi-version=3. + var ret AST + if st.str[0] == '_' || st.str[0] == 'Z' { + if st.str[0] == '_' { + st.advance(1) + } + if len(st.str) == 0 || st.str[0] != 'Z' { + st.fail("expected mangled name") + } + st.advance(1) + ret = st.encoding(true, notForLocalName) + } else { + t := st.demangleType(false) + + isArrayType := func(typ AST) bool { + if twq, ok := typ.(*TypeWithQualifiers); ok { + typ = twq.Base + } + _, ok := typ.(*ArrayType) + return ok + } + + neg := false + if len(st.str) > 0 && st.str[0] == 'n' { + neg = true + st.advance(1) + } + if len(st.str) > 0 && st.str[0] == 'E' { + if bt, ok := t.(*BuiltinType); ok && bt.Name == "decltype(nullptr)" { + // A nullptr should not have a value. + // We accept one if present because GCC + // used to generate one. + // https://gcc.gnu.org/PR91979. + } else if cl, ok := t.(*Closure); ok { + // A closure doesn't have a value. + st.advance(1) + return &LambdaExpr{Type: cl} + } else if isArrayType(t) { + st.advance(1) + return &StringLiteral{Type: t} + } else { + st.fail("missing literal value") + } + } + i := 0 + for len(st.str) > i && st.str[i] != 'E' { + i++ + } + val := st.str[:i] + st.advance(i) + ret = &Literal{Type: t, Val: val, Neg: neg} + } + if len(st.str) == 0 || st.str[0] != 'E' { + st.fail("expected E after literal") + } + st.advance(1) + return ret +} + +// discriminator parses: +// +// ::= _ <(non-negative) number> (when number < 10) +// __ <(non-negative) number> _ (when number >= 10) +func (st *state) discriminator(a AST) AST { + if len(st.str) == 0 || st.str[0] != '_' { + // clang can generate a discriminator at the end of + // the string with no underscore. + for i := 0; i < len(st.str); i++ { + if !isDigit(st.str[i]) { + return a + } + } + // Skip the trailing digits. + st.advance(len(st.str)) + return a + } + off := st.off + st.advance(1) + trailingUnderscore := false + if len(st.str) > 0 && st.str[0] == '_' { + st.advance(1) + trailingUnderscore = true + } + d := st.number() + if d < 0 { + st.failEarlier("invalid negative discriminator", st.off-off) + } + if trailingUnderscore && d >= 10 { + if len(st.str) == 0 || st.str[0] != '_' { + st.fail("expected _ after discriminator >= 10") + } + st.advance(1) + } + // We don't currently print out the discriminator, so we don't + // save it. + return a +} + +// closureTypeName parses: +// +// ::= Ul E [ ] _ +// ::= + +func (st *state) closureTypeName() AST { + st.checkChar('U') + st.checkChar('l') + + oldLambdaTemplateLevel := st.lambdaTemplateLevel + st.lambdaTemplateLevel = len(st.templates) + 1 + + var templateArgs []AST + var template *Template + for len(st.str) > 1 && st.str[0] == 'T' { + arg, templateVal := st.templateParamDecl() + if arg == nil { + break + } + templateArgs = append(templateArgs, arg) + if template == nil { + template = &Template{ + Name: &Name{Name: "lambda"}, + } + st.templates = append(st.templates, template) + } + template.Args = append(template.Args, templateVal) + } + + var templateArgsConstraint AST + if len(st.str) > 0 && st.str[0] == 'Q' { + templateArgsConstraint = st.constraintExpr() + } + + types := st.parmlist(false) + + st.lambdaTemplateLevel = oldLambdaTemplateLevel + + if template != nil { + st.templates = st.templates[:len(st.templates)-1] + } + + var callConstraint AST + if len(st.str) > 0 && st.str[0] == 'Q' { + callConstraint = st.constraintExpr() + } + + if len(st.str) == 0 || st.str[0] != 'E' { + st.fail("expected E after closure type name") + } + st.advance(1) + num := st.compactNumber() + return &Closure{ + TemplateArgs: templateArgs, + TemplateArgsConstraint: templateArgsConstraint, + Types: types, + Num: num, + CallConstraint: callConstraint, + } +} + +// templateParamDecl parses: +// +// ::= Ty # type parameter +// ::= Tn # non-type parameter +// ::= Tt * E # template parameter +// ::= Tp # parameter pack +// +// Returns the new AST to include in the AST we are building and the +// new AST to add to the list of template parameters. +// +// Returns nil, nil if not looking at a template-param-decl. +func (st *state) templateParamDecl() (AST, AST) { + if len(st.str) < 2 || st.str[0] != 'T' { + return nil, nil + } + mk := func(prefix string, p *int) AST { + idx := *p + (*p)++ + return &TemplateParamName{ + Prefix: prefix, + Index: idx, + } + } + switch st.str[1] { + case 'y': + st.advance(2) + name := mk("$T", &st.typeTemplateParamCount) + tp := &TypeTemplateParam{ + Name: name, + } + return tp, name + case 'k': + st.advance(2) + constraint, _ := st.name() + name := mk("$T", &st.typeTemplateParamCount) + tp := &ConstrainedTypeTemplateParam{ + Name: name, + Constraint: constraint, + } + return tp, name + case 'n': + st.advance(2) + name := mk("$N", &st.nonTypeTemplateParamCount) + typ := st.demangleType(false) + tp := &NonTypeTemplateParam{ + Name: name, + Type: typ, + } + return tp, name + case 't': + st.advance(2) + name := mk("$TT", &st.templateTemplateParamCount) + var params []AST + var template *Template + var constraint AST + for { + if len(st.str) == 0 { + st.fail("expected closure template parameter") + } + if st.str[0] == 'E' { + st.advance(1) + break + } + off := st.off + param, templateVal := st.templateParamDecl() + if param == nil { + st.failEarlier("expected closure template parameter", st.off-off) + } + params = append(params, param) + if template == nil { + template = &Template{ + Name: &Name{Name: "template_template"}, + } + st.templates = append(st.templates, template) + } + template.Args = append(template.Args, templateVal) + + if len(st.str) > 0 && st.str[0] == 'Q' { + // A list of template template + // parameters can have a constraint. + constraint = st.constraintExpr() + if len(st.str) == 0 || st.str[0] != 'E' { + st.fail("expected end of template template parameters after constraint") + } + } + } + if template != nil { + st.templates = st.templates[:len(st.templates)-1] + } + tp := &TemplateTemplateParam{ + Name: name, + Params: params, + Constraint: constraint, + } + return tp, name + case 'p': + st.advance(2) + off := st.off + param, templateVal := st.templateParamDecl() + if param == nil { + st.failEarlier("expected lambda template parameter", st.off-off) + } + return &TemplateParamPack{Param: param}, templateVal + default: + return nil, nil + } +} + +// unnamedTypeName parses: +// +// ::= Ut [ ] _ +func (st *state) unnamedTypeName() AST { + st.checkChar('U') + st.checkChar('t') + num := st.compactNumber() + ret := &UnnamedType{Num: num} + st.subs.add(ret) + return ret +} + +// constraintExpr parses a constraint expression. This is just a +// regular expression, but template parameters are handled specially. +func (st *state) constraintExpr() AST { + st.checkChar('Q') + + hold := st.parsingConstraint + st.parsingConstraint = true + defer func() { st.parsingConstraint = hold }() + + return st.expression() +} + +// Recognize a clone suffix. These are not part of the mangling API, +// but are added by GCC when cloning functions. +func (st *state) cloneSuffix(a AST) AST { + i := 0 + if len(st.str) > 1 && st.str[0] == '.' && (isLower(st.str[1]) || isDigit(st.str[1]) || st.str[1] == '_') { + i += 2 + for len(st.str) > i && (isLower(st.str[i]) || isDigit(st.str[i]) || st.str[i] == '_') { + i++ + } + } + for len(st.str) > i+1 && st.str[i] == '.' && isDigit(st.str[i+1]) { + i += 2 + for len(st.str) > i && isDigit(st.str[i]) { + i++ + } + } + suffix := st.str[:i] + st.advance(i) + return &Clone{Base: a, Suffix: suffix} +} + +// substitutions is the list of substitution candidates that may +// appear later in the string. +type substitutions []AST + +// add adds a new substitution candidate. +func (subs *substitutions) add(a AST) { + *subs = append(*subs, a) +} + +// subAST maps standard substitution codes to the corresponding AST. +var subAST = map[byte]AST{ + 't': &Name{Name: "std"}, + 'a': &Qualified{Scope: &Name{Name: "std"}, Name: &Name{Name: "allocator"}}, + 'b': &Qualified{Scope: &Name{Name: "std"}, Name: &Name{Name: "basic_string"}}, + 's': &Qualified{Scope: &Name{Name: "std"}, Name: &Name{Name: "string"}}, + 'i': &Qualified{Scope: &Name{Name: "std"}, Name: &Name{Name: "istream"}}, + 'o': &Qualified{Scope: &Name{Name: "std"}, Name: &Name{Name: "ostream"}}, + 'd': &Qualified{Scope: &Name{Name: "std"}, Name: &Name{Name: "iostream"}}, +} + +// verboseAST maps standard substitution codes to the long form of the +// corresponding AST. We use this when the Verbose option is used, to +// match the standard demangler. +var verboseAST = map[byte]AST{ + 't': &Name{Name: "std"}, + 'a': &Qualified{Scope: &Name{Name: "std"}, Name: &Name{Name: "allocator"}}, + 'b': &Qualified{Scope: &Name{Name: "std"}, Name: &Name{Name: "basic_string"}}, + + // std::basic_string, std::allocator > + 's': &Template{ + Name: &Qualified{Scope: &Name{Name: "std"}, Name: &Name{Name: "basic_string"}}, + Args: []AST{ + &BuiltinType{Name: "char"}, + &Template{ + Name: &Qualified{Scope: &Name{Name: "std"}, Name: &Name{Name: "char_traits"}}, + Args: []AST{&BuiltinType{Name: "char"}}}, + &Template{ + Name: &Qualified{Scope: &Name{Name: "std"}, Name: &Name{Name: "allocator"}}, + Args: []AST{&BuiltinType{Name: "char"}}}}}, + // std::basic_istream > + 'i': &Template{ + Name: &Qualified{Scope: &Name{Name: "std"}, Name: &Name{Name: "basic_istream"}}, + Args: []AST{ + &BuiltinType{Name: "char"}, + &Template{ + Name: &Qualified{Scope: &Name{Name: "std"}, Name: &Name{Name: "char_traits"}}, + Args: []AST{&BuiltinType{Name: "char"}}}}}, + // std::basic_ostream > + 'o': &Template{ + Name: &Qualified{Scope: &Name{Name: "std"}, Name: &Name{Name: "basic_ostream"}}, + Args: []AST{ + &BuiltinType{Name: "char"}, + &Template{ + Name: &Qualified{Scope: &Name{Name: "std"}, Name: &Name{Name: "char_traits"}}, + Args: []AST{&BuiltinType{Name: "char"}}}}}, + // std::basic_iostream > + 'd': &Template{ + Name: &Qualified{Scope: &Name{Name: "std"}, Name: &Name{Name: "basic_iostream"}}, + Args: []AST{ + &BuiltinType{Name: "char"}, + &Template{ + Name: &Qualified{Scope: &Name{Name: "std"}, Name: &Name{Name: "char_traits"}}, + Args: []AST{&BuiltinType{Name: "char"}}}}}, +} + +// substitution parses: +// +// ::= S _ +// ::= S_ +// ::= St +// ::= Sa +// ::= Sb +// ::= Ss +// ::= Si +// ::= So +// ::= Sd +func (st *state) substitution(forPrefix bool) AST { + st.checkChar('S') + if len(st.str) == 0 { + st.fail("missing substitution index") + } + c := st.str[0] + off := st.off + if c == '_' || isDigit(c) || isUpper(c) { + id := st.seqID(false) + if id >= len(st.subs) { + st.failEarlier(fmt.Sprintf("substitution index out of range (%d >= %d)", id, len(st.subs)), st.off-off) + } + + ret := st.subs[id] + + // We need to update any references to template + // parameters to refer to the currently active + // template. + + // When copying a Typed we may need to adjust + // the templates. + copyTemplates := st.templates + var oldLambdaTemplateLevel []int + + // pushTemplate is called from skip, popTemplate from copy. + pushTemplate := func(template *Template) { + copyTemplates = append(copyTemplates, template) + oldLambdaTemplateLevel = append(oldLambdaTemplateLevel, st.lambdaTemplateLevel) + st.lambdaTemplateLevel = 0 + } + popTemplate := func() { + copyTemplates = copyTemplates[:len(copyTemplates)-1] + st.lambdaTemplateLevel = oldLambdaTemplateLevel[len(oldLambdaTemplateLevel)-1] + oldLambdaTemplateLevel = oldLambdaTemplateLevel[:len(oldLambdaTemplateLevel)-1] + } + + copy := func(a AST) AST { + var index int + switch a := a.(type) { + case *Typed: + // Remove the template added in skip. + if _, ok := a.Name.(*Template); ok { + popTemplate() + } + return nil + case *Closure: + // Undo the save in skip. + st.lambdaTemplateLevel = oldLambdaTemplateLevel[len(oldLambdaTemplateLevel)-1] + oldLambdaTemplateLevel = oldLambdaTemplateLevel[:len(oldLambdaTemplateLevel)-1] + return nil + case *TemplateParam: + index = a.Index + case *LambdaAuto: + // A lambda auto parameter is represented + // as a template parameter, so we may have + // to change back when substituting. + index = a.Index + default: + return nil + } + if st.parsingConstraint { + // We don't try to substitute template + // parameters in a constraint expression. + return &Name{Name: fmt.Sprintf("T%d", index)} + } + if st.lambdaTemplateLevel > 0 { + if _, ok := a.(*LambdaAuto); ok { + return nil + } + return &LambdaAuto{Index: index} + } + var template *Template + if len(copyTemplates) > 0 { + template = copyTemplates[len(copyTemplates)-1] + } else if rt, ok := ret.(*Template); ok { + // At least with clang we can see a template + // to start, and sometimes we need to refer + // to it. There is probably something wrong + // here. + template = rt + } else { + st.failEarlier("substituted template parameter not in scope of template", st.off-off) + } + if template == nil { + // This template parameter is within + // the scope of a cast operator. + return &TemplateParam{Index: index, Template: nil} + } + + if index >= len(template.Args) { + st.failEarlier(fmt.Sprintf("substituted template index out of range (%d >= %d)", index, len(template.Args)), st.off-off) + } + + return &TemplateParam{Index: index, Template: template} + } + seen := make(map[AST]bool) + skip := func(a AST) bool { + switch a := a.(type) { + case *Typed: + if template, ok := a.Name.(*Template); ok { + // This template is removed in copy. + pushTemplate(template) + } + return false + case *Closure: + // This is undone in copy. + oldLambdaTemplateLevel = append(oldLambdaTemplateLevel, st.lambdaTemplateLevel) + st.lambdaTemplateLevel = len(copyTemplates) + 1 + return false + case *TemplateParam, *LambdaAuto: + return false + } + if seen[a] { + return true + } + seen[a] = true + return false + } + + if c := ret.Copy(copy, skip); c != nil { + return c + } + + return ret + } else { + st.advance(1) + m := subAST + if st.verbose { + m = verboseAST + } + // For compatibility with the standard demangler, use + // a longer name for a constructor or destructor. + if forPrefix && len(st.str) > 0 && (st.str[0] == 'C' || st.str[0] == 'D') { + m = verboseAST + } + a, ok := m[c] + if !ok { + st.failEarlier("unrecognized substitution code", 1) + } + + if len(st.str) > 0 && st.str[0] == 'B' { + a = st.taggedName(a) + st.subs.add(a) + } + + return a + } +} + +// isDigit returns whetner c is a digit for demangling purposes. +func isDigit(c byte) bool { + return c >= '0' && c <= '9' +} + +// isUpper returns whether c is an upper case letter for demangling purposes. +func isUpper(c byte) bool { + return c >= 'A' && c <= 'Z' +} + +// isLower returns whether c is a lower case letter for demangling purposes. +func isLower(c byte) bool { + return c >= 'a' && c <= 'z' +} + +// simplify replaces template parameters with their expansions, and +// merges qualifiers. +func simplify(a AST) AST { + seen := make(map[AST]bool) + skip := func(a AST) bool { + if seen[a] { + return true + } + seen[a] = true + return false + } + if r := a.Copy(simplifyOne, skip); r != nil { + return r + } + return a +} + +// simplifyOne simplifies a single AST. It returns nil if there is +// nothing to do. +func simplifyOne(a AST) AST { + switch a := a.(type) { + case *TemplateParam: + if a.Template != nil && a.Index < len(a.Template.Args) { + return a.Template.Args[a.Index] + } + case *MethodWithQualifiers: + if m, ok := a.Method.(*MethodWithQualifiers); ok { + ref := a.RefQualifier + if ref == "" { + ref = m.RefQualifier + } else if m.RefQualifier != "" { + if ref == "&" || m.RefQualifier == "&" { + ref = "&" + } + } + return &MethodWithQualifiers{Method: m.Method, Qualifiers: mergeQualifiers(a.Qualifiers, m.Qualifiers), RefQualifier: ref} + } + if t, ok := a.Method.(*TypeWithQualifiers); ok { + return &MethodWithQualifiers{Method: t.Base, Qualifiers: mergeQualifiers(a.Qualifiers, t.Qualifiers), RefQualifier: a.RefQualifier} + } + case *TypeWithQualifiers: + if ft, ok := a.Base.(*FunctionType); ok { + return &MethodWithQualifiers{Method: ft, Qualifiers: a.Qualifiers, RefQualifier: ""} + } + if t, ok := a.Base.(*TypeWithQualifiers); ok { + return &TypeWithQualifiers{Base: t.Base, Qualifiers: mergeQualifiers(a.Qualifiers, t.Qualifiers)} + } + if m, ok := a.Base.(*MethodWithQualifiers); ok { + return &MethodWithQualifiers{Method: m.Method, Qualifiers: mergeQualifiers(a.Qualifiers, m.Qualifiers), RefQualifier: m.RefQualifier} + } + case *ReferenceType: + if rt, ok := a.Base.(*ReferenceType); ok { + return rt + } + if rrt, ok := a.Base.(*RvalueReferenceType); ok { + return &ReferenceType{Base: rrt.Base} + } + case *RvalueReferenceType: + if rrt, ok := a.Base.(*RvalueReferenceType); ok { + return rrt + } + if rt, ok := a.Base.(*ReferenceType); ok { + return rt + } + case *ArrayType: + // Qualifiers on the element of an array type + // go on the whole array type. + if q, ok := a.Element.(*TypeWithQualifiers); ok { + return &TypeWithQualifiers{ + Base: &ArrayType{Dimension: a.Dimension, Element: q.Base}, + Qualifiers: q.Qualifiers, + } + } + case *PackExpansion: + // Expand the pack and replace it with a list of + // expressions. + if a.Pack != nil { + exprs := make([]AST, len(a.Pack.Args)) + for i, arg := range a.Pack.Args { + copy := func(sub AST) AST { + // Replace the ArgumentPack + // with a specific argument. + if sub == a.Pack { + return arg + } + // Copy everything else. + return nil + } + + seen := make(map[AST]bool) + skip := func(sub AST) bool { + // Don't traverse into another + // pack expansion. + if _, ok := sub.(*PackExpansion); ok { + return true + } + if seen[sub] { + return true + } + seen[sub] = true + return false + } + + b := a.Base.Copy(copy, skip) + if b == nil { + b = a.Base + } + exprs[i] = simplify(b) + } + return &ExprList{Exprs: exprs} + } + } + return nil +} + +// findArgumentPack walks the AST looking for the argument pack for a +// pack expansion. We find it via a template parameter. +func (st *state) findArgumentPack(a AST) *ArgumentPack { + seen := make(map[AST]bool) + var ret *ArgumentPack + a.Traverse(func(a AST) bool { + if ret != nil { + return false + } + switch a := a.(type) { + case *TemplateParam: + if a.Template == nil || a.Index >= len(a.Template.Args) { + return true + } + if pack, ok := a.Template.Args[a.Index].(*ArgumentPack); ok { + ret = pack + return false + } + case *PackExpansion, *Closure, *Name: + return false + case *TaggedName, *Operator, *BuiltinType, *FunctionParam: + return false + case *UnnamedType, *FixedType, *DefaultArg: + return false + } + if seen[a] { + return false + } + seen[a] = true + return true + }) + return ret +} diff --git a/vendor/github.com/ianlancetaylor/demangle/rust.go b/vendor/github.com/ianlancetaylor/demangle/rust.go new file mode 100644 index 00000000000..f3d2d33bd0b --- /dev/null +++ b/vendor/github.com/ianlancetaylor/demangle/rust.go @@ -0,0 +1,1165 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package demangle + +import ( + "fmt" + "math" + "math/bits" + "strings" + "unicode/utf8" +) + +// rustToString demangles a Rust symbol. +func rustToString(name string, options []Option) (ret string, err error) { + if !strings.HasPrefix(name, "_R") { + return "", ErrNotMangledName + } + + // When the demangling routines encounter an error, they panic + // with a value of type demangleErr. + defer func() { + if r := recover(); r != nil { + if de, ok := r.(demangleErr); ok { + ret = "" + err = de + return + } + panic(r) + } + }() + + suffix := "" + dot := strings.Index(name, ".") + if dot >= 0 { + suffix = name[dot:] + name = name[:dot] + } + + name = name[2:] + rst := &rustState{orig: name, str: name} + + for _, o := range options { + if o == NoTemplateParams { + rst.noGenericArgs = true + } else if isMaxLength(o) { + rst.max = maxLength(o) + } + } + + rst.symbolName() + + if len(rst.str) > 0 { + rst.fail("unparsed characters at end of mangled name") + } + + if suffix != "" { + llvmStyle := false + for _, o := range options { + if o == LLVMStyle { + llvmStyle = true + break + } + } + if llvmStyle { + rst.skip = false + rst.writeString(" (") + rst.writeString(suffix) + rst.writeByte(')') + } + } + + s := rst.buf.String() + if rst.max > 0 && len(s) > rst.max { + s = s[:rst.max] + } + return s, nil +} + +// A rustState holds the current state of demangling a Rust string. +type rustState struct { + orig string // the original string being demangled + str string // remainder of string to demangle + off int // offset of str within original string + buf strings.Builder // demangled string being built + skip bool // don't print, just skip + lifetimes int64 // number of bound lifetimes + last byte // last byte written to buffer + noGenericArgs bool // don't demangle generic arguments + max int // maximum output length +} + +// fail panics with demangleErr, to be caught in rustToString. +func (rst *rustState) fail(err string) { + panic(demangleErr{err: err, off: rst.off}) +} + +// advance advances the current string offset. +func (rst *rustState) advance(add int) { + if len(rst.str) < add { + panic("internal error") + } + rst.str = rst.str[add:] + rst.off += add +} + +// checkChar requires that the next character in the string be c, +// and advances past it. +func (rst *rustState) checkChar(c byte) { + if len(rst.str) == 0 || rst.str[0] != c { + rst.fail("expected " + string(c)) + } + rst.advance(1) +} + +// writeByte writes a byte to the buffer. +func (rst *rustState) writeByte(c byte) { + if rst.skip { + return + } + if rst.max > 0 && rst.buf.Len() > rst.max { + rst.skip = true + return + } + rst.last = c + rst.buf.WriteByte(c) +} + +// writeString writes a string to the buffer. +func (rst *rustState) writeString(s string) { + if rst.skip { + return + } + if rst.max > 0 && rst.buf.Len() > rst.max { + rst.skip = true + return + } + if len(s) > 0 { + rst.last = s[len(s)-1] + rst.buf.WriteString(s) + } +} + +// symbolName parses: +// +// = "_R" [] [] +// = +// +// We've already skipped the "_R". +func (rst *rustState) symbolName() { + if len(rst.str) < 1 { + rst.fail("expected symbol-name") + } + + if isDigit(rst.str[0]) { + rst.fail("unsupported Rust encoding version") + } + + rst.path(true) + + if len(rst.str) > 0 { + rst.skip = true + rst.path(false) + } +} + +// path parses: +// +// = "C" // crate root +// | "M" // (inherent impl) +// | "X" // (trait impl) +// | "Y" // (trait definition) +// | "N" // ...::ident (nested path) +// | "I" {} "E" // ... (generic args) +// | +// = "C" // closure +// | "S" // shim +// | // other special namespaces +// | // internal namespaces +// +// needsSeparator is true if we need to write out :: for a generic; +// it is passed as false if we are in the middle of a type. +func (rst *rustState) path(needsSeparator bool) { + if len(rst.str) < 1 { + rst.fail("expected path") + } + switch c := rst.str[0]; c { + case 'C': + rst.advance(1) + _, ident := rst.identifier() + rst.writeString(ident) + case 'M', 'X': + rst.advance(1) + rst.implPath() + rst.writeByte('<') + rst.demangleType() + if c == 'X' { + rst.writeString(" as ") + rst.path(false) + } + rst.writeByte('>') + case 'Y': + rst.advance(1) + rst.writeByte('<') + rst.demangleType() + rst.writeString(" as ") + rst.path(false) + rst.writeByte('>') + case 'N': + rst.advance(1) + + if len(rst.str) < 1 { + rst.fail("expected namespace") + } + ns := rst.str[0] + switch { + case ns >= 'a' && ns <= 'z': + case ns >= 'A' && ns <= 'Z': + default: + rst.fail("invalid namespace character") + } + rst.advance(1) + + rst.path(needsSeparator) + + dis, ident := rst.identifier() + + if ns >= 'A' && ns <= 'Z' { + rst.writeString("::{") + switch ns { + case 'C': + rst.writeString("closure") + case 'S': + rst.writeString("shim") + default: + rst.writeByte(ns) + } + if len(ident) > 0 { + rst.writeByte(':') + rst.writeString(ident) + } + if !rst.skip { + fmt.Fprintf(&rst.buf, "#%d}", dis) + rst.last = '}' + } + } else { + rst.writeString("::") + rst.writeString(ident) + } + case 'I': + rst.advance(1) + rst.path(needsSeparator) + if needsSeparator { + rst.writeString("::") + } + rst.writeByte('<') + rst.genericArgs() + rst.writeByte('>') + rst.checkChar('E') + case 'B': + rst.backref(func() { rst.path(needsSeparator) }) + default: + rst.fail("unrecognized letter in path") + } +} + +// implPath parses: +// +// = [] +func (rst *rustState) implPath() { + // This path is not part of the demangled string. + hold := rst.skip + rst.skip = true + defer func() { + rst.skip = hold + }() + + rst.disambiguator() + rst.path(false) +} + +// identifier parses: +// +// = [] +// +// It returns the disambiguator and the identifier. +func (rst *rustState) identifier() (int64, string) { + dis := rst.disambiguator() + ident, _ := rst.undisambiguatedIdentifier() + return dis, ident +} + +// disambiguator parses an optional: +// +// = "s" +func (rst *rustState) disambiguator() int64 { + if len(rst.str) == 0 || rst.str[0] != 's' { + return 0 + } + rst.advance(1) + return rst.base62Number() + 1 +} + +// undisambiguatedIdentifier parses: +// +// = ["u"] ["_"] +func (rst *rustState) undisambiguatedIdentifier() (id string, isPunycode bool) { + isPunycode = false + if len(rst.str) > 0 && rst.str[0] == 'u' { + rst.advance(1) + isPunycode = true + } + + val := rst.decimalNumber() + + if len(rst.str) > 0 && rst.str[0] == '_' { + rst.advance(1) + } + + if len(rst.str) < val { + rst.fail("not enough characters for identifier") + } + id = rst.str[:val] + rst.advance(val) + + for i := 0; i < len(id); i++ { + c := id[i] + switch { + case c >= '0' && c <= '9': + case c >= 'A' && c <= 'Z': + case c >= 'a' && c <= 'z': + case c == '_': + default: + rst.fail("invalid character in identifier") + } + } + + if isPunycode { + id = rst.expandPunycode(id) + } + + return id, isPunycode +} + +// expandPunycode decodes the Rust version of punycode. +// This algorithm is taken from RFC 3492 section 6.2. +func (rst *rustState) expandPunycode(s string) string { + const ( + base = 36 + tmin = 1 + tmax = 26 + skew = 38 + damp = 700 + initialBias = 72 + initialN = 128 + ) + + var ( + output []rune + encoding string + ) + idx := strings.LastIndex(s, "_") + if idx >= 0 { + output = []rune(s[:idx]) + encoding = s[idx+1:] + } else { + encoding = s + } + + i := 0 + n := initialN + bias := initialBias + + pos := 0 + for pos < len(encoding) { + oldI := i + w := 1 + for k := base; ; k += base { + if pos == len(encoding) { + rst.fail("unterminated punycode") + } + + var digit byte + d := encoding[pos] + pos++ + switch { + case '0' <= d && d <= '9': + digit = d - '0' + 26 + case 'A' <= d && d <= 'Z': + digit = d - 'A' + case 'a' <= d && d <= 'z': + digit = d - 'a' + default: + rst.fail("invalid punycode digit") + } + + i += int(digit) * w + if i < 0 { + rst.fail("punycode number overflow") + } + + var t int + if k <= bias { + t = tmin + } else if k > bias+tmax { + t = tmax + } else { + t = k - bias + } + + if int(digit) < t { + break + } + + if w >= math.MaxInt32/base { + rst.fail("punycode number overflow") + } + w *= base - t + } + + delta := i - oldI + numPoints := len(output) + 1 + firstTime := oldI == 0 + if firstTime { + delta /= damp + } else { + delta /= 2 + } + delta += delta / numPoints + k := 0 + for delta > ((base-tmin)*tmax)/2 { + delta /= base - tmin + k += base + } + bias = k + ((base-tmin+1)*delta)/(delta+skew) + + n += i / (len(output) + 1) + if n > utf8.MaxRune { + rst.fail("punycode rune overflow") + } else if !utf8.ValidRune(rune(n)) { + rst.fail("punycode invalid code point") + } + i %= len(output) + 1 + output = append(output, 0) + copy(output[i+1:], output[i:]) + output[i] = rune(n) + i++ + } + + return string(output) +} + +// genericArgs prints a list of generic arguments, without angle brackets. +func (rst *rustState) genericArgs() { + if rst.noGenericArgs { + hold := rst.skip + rst.skip = true + defer func() { + rst.skip = hold + }() + } + + first := true + for len(rst.str) > 0 && rst.str[0] != 'E' { + if first { + first = false + } else { + rst.writeString(", ") + } + rst.genericArg() + } +} + +// genericArg parses: +// +// = +// | +// | "K" // forward-compat for const generics +// = "L" +func (rst *rustState) genericArg() { + if len(rst.str) < 1 { + rst.fail("expected generic-arg") + } + if rst.str[0] == 'L' { + rst.advance(1) + rst.writeLifetime(rst.base62Number()) + } else if rst.str[0] == 'K' { + rst.advance(1) + rst.demangleConst() + } else { + rst.demangleType() + } +} + +// binder parses an optional: +// +// = "G" +func (rst *rustState) binder() { + if len(rst.str) < 1 || rst.str[0] != 'G' { + return + } + rst.advance(1) + + binderLifetimes := rst.base62Number() + 1 + + // Every bound lifetime should be referenced later. + if binderLifetimes >= int64(len(rst.str))-rst.lifetimes { + rst.fail("binder lifetimes overflow") + } + + rst.writeString("for<") + for i := int64(0); i < binderLifetimes; i++ { + if i > 0 { + rst.writeString(", ") + } + rst.lifetimes++ + rst.writeLifetime(1) + } + rst.writeString("> ") +} + +// demangleType parses: +// +// = +// | // named type +// | "A" // [T; N] +// | "S" // [T] +// | "T" {} "E" // (T1, T2, T3, ...) +// | "R" [] // &T +// | "Q" [] // &mut T +// | "P" // *const T +// | "O" // *mut T +// | "F" // fn(...) -> ... +// | "D" // dyn Trait + Send + 'a +// | +func (rst *rustState) demangleType() { + if len(rst.str) < 1 { + rst.fail("expected type") + } + c := rst.str[0] + if c >= 'a' && c <= 'z' { + rst.basicType() + return + } + switch c { + case 'C', 'M', 'X', 'Y', 'N', 'I': + rst.path(false) + case 'A', 'S': + rst.advance(1) + rst.writeByte('[') + rst.demangleType() + if c == 'A' { + rst.writeString("; ") + rst.demangleConst() + } + rst.writeByte(']') + case 'T': + rst.advance(1) + rst.writeByte('(') + c := 0 + for len(rst.str) > 0 && rst.str[0] != 'E' { + if c > 0 { + rst.writeString(", ") + } + c++ + rst.demangleType() + } + if c == 1 { + rst.writeByte(',') + } + rst.writeByte(')') + rst.checkChar('E') + case 'R', 'Q': + rst.advance(1) + rst.writeByte('&') + if len(rst.str) > 0 && rst.str[0] == 'L' { + rst.advance(1) + if lifetime := rst.base62Number(); lifetime > 0 { + rst.writeLifetime(lifetime) + rst.writeByte(' ') + } + } + if c == 'Q' { + rst.writeString("mut ") + } + rst.demangleType() + case 'P': + rst.advance(1) + rst.writeString("*const ") + rst.demangleType() + case 'O': + rst.advance(1) + rst.writeString("*mut ") + rst.demangleType() + case 'F': + rst.advance(1) + hold := rst.lifetimes + rst.fnSig() + rst.lifetimes = hold + case 'D': + rst.advance(1) + hold := rst.lifetimes + rst.dynBounds() + rst.lifetimes = hold + if len(rst.str) == 0 || rst.str[0] != 'L' { + rst.fail("expected L") + } + rst.advance(1) + if lifetime := rst.base62Number(); lifetime > 0 { + if rst.last != ' ' { + rst.writeByte(' ') + } + rst.writeString("+ ") + rst.writeLifetime(lifetime) + } + case 'B': + rst.backref(rst.demangleType) + default: + rst.fail("unrecognized character in type") + } +} + +var rustBasicTypes = map[byte]string{ + 'a': "i8", + 'b': "bool", + 'c': "char", + 'd': "f64", + 'e': "str", + 'f': "f32", + 'h': "u8", + 'i': "isize", + 'j': "usize", + 'l': "i32", + 'm': "u32", + 'n': "i128", + 'o': "u128", + 'p': "_", + 's': "i16", + 't': "u16", + 'u': "()", + 'v': "...", + 'x': "i64", + 'y': "u64", + 'z': "!", +} + +// basicType parses: +// +// +func (rst *rustState) basicType() { + if len(rst.str) < 1 { + rst.fail("expected basic type") + } + str, ok := rustBasicTypes[rst.str[0]] + if !ok { + rst.fail("unrecognized basic type character") + } + rst.advance(1) + rst.writeString(str) +} + +// fnSig parses: +// +// = [] ["U"] ["K" ] {} "E" +// = "C" +// | +func (rst *rustState) fnSig() { + rst.binder() + if len(rst.str) > 0 && rst.str[0] == 'U' { + rst.advance(1) + rst.writeString("unsafe ") + } + if len(rst.str) > 0 && rst.str[0] == 'K' { + rst.advance(1) + if len(rst.str) > 0 && rst.str[0] == 'C' { + rst.advance(1) + rst.writeString(`extern "C" `) + } else { + rst.writeString(`extern "`) + id, isPunycode := rst.undisambiguatedIdentifier() + if isPunycode { + rst.fail("punycode used in ABI string") + } + id = strings.ReplaceAll(id, "_", "-") + rst.writeString(id) + rst.writeString(`" `) + } + } + rst.writeString("fn(") + first := true + for len(rst.str) > 0 && rst.str[0] != 'E' { + if first { + first = false + } else { + rst.writeString(", ") + } + rst.demangleType() + } + rst.checkChar('E') + rst.writeByte(')') + if len(rst.str) > 0 && rst.str[0] == 'u' { + rst.advance(1) + } else { + rst.writeString(" -> ") + rst.demangleType() + } +} + +// dynBounds parses: +// +// = [] {} "E" +func (rst *rustState) dynBounds() { + rst.writeString("dyn ") + rst.binder() + first := true + for len(rst.str) > 0 && rst.str[0] != 'E' { + if first { + first = false + } else { + rst.writeString(" + ") + } + rst.dynTrait() + } + rst.checkChar('E') +} + +// dynTrait parses: +// +// = {} +// = "p" +func (rst *rustState) dynTrait() { + started := rst.pathStartGenerics() + for len(rst.str) > 0 && rst.str[0] == 'p' { + rst.advance(1) + if started { + rst.writeString(", ") + } else { + rst.writeByte('<') + started = true + } + id, _ := rst.undisambiguatedIdentifier() + rst.writeString(id) + rst.writeString(" = ") + rst.demangleType() + } + if started { + rst.writeByte('>') + } +} + +// pathStartGenerics is like path but if it sees an I to start generic +// arguments it won't close them. It reports whether it started generics. +func (rst *rustState) pathStartGenerics() bool { + if len(rst.str) < 1 { + rst.fail("expected path") + } + switch rst.str[0] { + case 'I': + rst.advance(1) + rst.path(false) + rst.writeByte('<') + rst.genericArgs() + rst.checkChar('E') + return true + case 'B': + var started bool + rst.backref(func() { started = rst.pathStartGenerics() }) + return started + default: + rst.path(false) + return false + } +} + +// writeLifetime writes out a lifetime binding. +func (rst *rustState) writeLifetime(lifetime int64) { + rst.writeByte('\'') + if lifetime == 0 { + rst.writeByte('_') + return + } + depth := rst.lifetimes - lifetime + if depth < 0 { + rst.fail("invalid lifetime") + } else if depth < 26 { + rst.writeByte('a' + byte(depth)) + } else { + rst.writeByte('z') + if !rst.skip { + fmt.Fprintf(&rst.buf, "%d", depth-26+1) + rst.last = '0' + } + } +} + +// demangleConst parses: +// +// = +// | "p" // placeholder, shown as _ +// | +// = ["n"] {} "_" +func (rst *rustState) demangleConst() { + if len(rst.str) < 1 { + rst.fail("expected constant") + } + + if rst.str[0] == 'B' { + rst.backref(rst.demangleConst) + return + } + + if rst.str[0] == 'p' { + rst.advance(1) + rst.writeByte('_') + return + } + + typ := rst.str[0] + + const ( + invalid = iota + signedInt + unsignedInt + boolean + character + ) + + var kind int + switch typ { + case 'a', 's', 'l', 'x', 'n', 'i': + kind = signedInt + case 'h', 't', 'm', 'y', 'o', 'j': + kind = unsignedInt + case 'b': + kind = boolean + case 'c': + kind = character + default: + rst.fail("unrecognized constant type") + } + + rst.advance(1) + + if kind == signedInt && len(rst.str) > 0 && rst.str[0] == 'n' { + rst.advance(1) + rst.writeByte('-') + } + + start := rst.str + digits := 0 + val := uint64(0) +digitLoop: + for len(rst.str) > 0 { + c := rst.str[0] + var digit uint64 + switch { + case c >= '0' && c <= '9': + digit = uint64(c - '0') + case c >= 'a' && c <= 'f': + digit = uint64(c - 'a' + 10) + case c == '_': + rst.advance(1) + break digitLoop + default: + rst.fail("expected hex digit or _") + } + rst.advance(1) + if val == 0 && digit == 0 && (len(rst.str) == 0 || rst.str[0] != '_') { + rst.fail("invalid leading 0 in constant") + } + val *= 16 + val += digit + digits++ + } + + if digits == 0 { + rst.fail("expected constant") + } + + switch kind { + case signedInt, unsignedInt: + if digits > 16 { + // Value too big, just write out the string. + rst.writeString("0x") + rst.writeString(start[:digits]) + } else { + if !rst.skip { + fmt.Fprintf(&rst.buf, "%d", val) + rst.last = '0' + } + } + case boolean: + if digits > 1 { + rst.fail("boolean value too large") + } else if val == 0 { + rst.writeString("false") + } else if val == 1 { + rst.writeString("true") + } else { + rst.fail("invalid boolean value") + } + case character: + if digits > 6 { + rst.fail("character value too large") + } + rst.writeByte('\'') + if val == '\t' { + rst.writeString(`\t`) + } else if val == '\r' { + rst.writeString(`\r`) + } else if val == '\n' { + rst.writeString(`\n`) + } else if val == '\\' { + rst.writeString(`\\`) + } else if val == '\'' { + rst.writeString(`\'`) + } else if val >= ' ' && val <= '~' { + // printable ASCII character + rst.writeByte(byte(val)) + } else { + if !rst.skip { + fmt.Fprintf(&rst.buf, `\u{%x}`, val) + rst.last = '}' + } + } + rst.writeByte('\'') + default: + panic("internal error") + } +} + +// base62Number parses: +// +// = {<0-9a-zA-Z>} "_" +func (rst *rustState) base62Number() int64 { + if len(rst.str) > 0 && rst.str[0] == '_' { + rst.advance(1) + return 0 + } + val := int64(0) + for len(rst.str) > 0 { + c := rst.str[0] + rst.advance(1) + if c == '_' { + return val + 1 + } + val *= 62 + if c >= '0' && c <= '9' { + val += int64(c - '0') + } else if c >= 'a' && c <= 'z' { + val += int64(c - 'a' + 10) + } else if c >= 'A' && c <= 'Z' { + val += int64(c - 'A' + 36) + } else { + rst.fail("invalid digit in base 62 number") + } + } + rst.fail("expected _ after base 62 number") + return 0 +} + +// backref parses: +// +// = "B" +func (rst *rustState) backref(demangle func()) { + backoff := rst.off + + rst.checkChar('B') + idx64 := rst.base62Number() + + if rst.skip { + return + } + if rst.max > 0 && rst.buf.Len() > rst.max { + return + } + + idx := int(idx64) + if int64(idx) != idx64 { + rst.fail("backref index overflow") + } + if idx < 0 || idx >= backoff { + rst.fail("invalid backref index") + } + + holdStr := rst.str + holdOff := rst.off + rst.str = rst.orig[idx:backoff] + rst.off = idx + defer func() { + rst.str = holdStr + rst.off = holdOff + }() + + demangle() +} + +func (rst *rustState) decimalNumber() int { + if len(rst.str) == 0 { + rst.fail("expected number") + } + + val := 0 + for len(rst.str) > 0 && isDigit(rst.str[0]) { + add := int(rst.str[0] - '0') + if val >= math.MaxInt32/10-add { + rst.fail("decimal number overflow") + } + val *= 10 + val += add + rst.advance(1) + } + return val +} + +// oldRustToString demangles a Rust symbol using the old demangling. +// The second result reports whether this is a valid Rust mangled name. +func oldRustToString(name string, options []Option) (string, bool) { + max := 0 + for _, o := range options { + if isMaxLength(o) { + max = maxLength(o) + } + } + + // We know that the string starts with _ZN. + name = name[3:] + + hexDigit := func(c byte) (byte, bool) { + switch { + case c >= '0' && c <= '9': + return c - '0', true + case c >= 'a' && c <= 'f': + return c - 'a' + 10, true + default: + return 0, false + } + } + + // We know that the strings end with "17h" followed by 16 characters + // followed by "E". We check that the 16 characters are all hex digits. + // Also the hex digits must contain at least 5 distinct digits. + seen := uint16(0) + for i := len(name) - 17; i < len(name)-1; i++ { + digit, ok := hexDigit(name[i]) + if !ok { + return "", false + } + seen |= 1 << digit + } + if bits.OnesCount16(seen) < 5 { + return "", false + } + name = name[:len(name)-20] + + // The name is a sequence of length-preceded identifiers. + var sb strings.Builder + for len(name) > 0 { + if max > 0 && sb.Len() > max { + break + } + + if !isDigit(name[0]) { + return "", false + } + + val := 0 + for len(name) > 0 && isDigit(name[0]) { + add := int(name[0] - '0') + if val >= math.MaxInt32/10-add { + return "", false + } + val *= 10 + val += add + name = name[1:] + } + + // An optional trailing underscore can separate the + // length from the identifier. + if len(name) > 0 && name[0] == '_' { + name = name[1:] + val-- + } + + if len(name) < val { + return "", false + } + + id := name[:val] + name = name[val:] + + if sb.Len() > 0 { + sb.WriteString("::") + } + + // Ignore leading underscores preceding escape sequences. + if strings.HasPrefix(id, "_$") { + id = id[1:] + } + + // The identifier can have escape sequences. + escape: + for len(id) > 0 { + switch c := id[0]; c { + case '$': + codes := map[string]byte{ + "SP": '@', + "BP": '*', + "RF": '&', + "LT": '<', + "GT": '>', + "LP": '(', + "RP": ')', + } + + valid := true + if len(id) > 2 && id[1] == 'C' && id[2] == '$' { + sb.WriteByte(',') + id = id[3:] + } else if len(id) > 4 && id[1] == 'u' && id[4] == '$' { + dig1, ok1 := hexDigit(id[2]) + dig2, ok2 := hexDigit(id[3]) + val := (dig1 << 4) | dig2 + if !ok1 || !ok2 || dig1 > 7 || val < ' ' { + valid = false + } else { + sb.WriteByte(val) + id = id[5:] + } + } else if len(id) > 3 && id[3] == '$' { + if code, ok := codes[id[1:3]]; !ok { + valid = false + } else { + sb.WriteByte(code) + id = id[4:] + } + } else { + valid = false + } + if !valid { + sb.WriteString(id) + break escape + } + case '.': + if strings.HasPrefix(id, "..") { + sb.WriteString("::") + id = id[2:] + } else { + sb.WriteByte(c) + id = id[1:] + } + default: + sb.WriteByte(c) + id = id[1:] + } + } + } + + s := sb.String() + if max > 0 && len(s) > max { + s = s[:max] + } + return s, true +} diff --git a/vendor/github.com/tetratelabs/wabin/LICENSE b/vendor/github.com/tetratelabs/wabin/LICENSE new file mode 100644 index 00000000000..0632df3146c --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020-2022 wazero authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/tetratelabs/wabin/binary/code.go b/vendor/github.com/tetratelabs/wabin/binary/code.go new file mode 100644 index 00000000000..1c5c9fce437 --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/binary/code.go @@ -0,0 +1,118 @@ +package binary + +import ( + "bytes" + "fmt" + "io" + "math" + + "github.com/tetratelabs/wabin/leb128" + "github.com/tetratelabs/wabin/wasm" +) + +func decodeCode(r *bytes.Reader) (*wasm.Code, error) { + ss, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get the size of code: %w", err) + } + remaining := int64(ss) + + // parse locals + ls, bytesRead, err := leb128.DecodeUint32(r) + remaining -= int64(bytesRead) + if err != nil { + return nil, fmt.Errorf("get the size locals: %v", err) + } else if remaining < 0 { + return nil, io.EOF + } + + var nums []uint64 + var types []wasm.ValueType + var sum uint64 + var n uint32 + for i := uint32(0); i < ls; i++ { + n, bytesRead, err = leb128.DecodeUint32(r) + remaining -= int64(bytesRead) + 1 // +1 for the subsequent ReadByte + if err != nil { + return nil, fmt.Errorf("read n of locals: %v", err) + } else if remaining < 0 { + return nil, io.EOF + } + + sum += uint64(n) + nums = append(nums, uint64(n)) + + b, err := r.ReadByte() + if err != nil { + return nil, fmt.Errorf("read type of local: %v", err) + } + switch vt := b; vt { + case wasm.ValueTypeI32, wasm.ValueTypeF32, wasm.ValueTypeI64, wasm.ValueTypeF64, + wasm.ValueTypeFuncref, wasm.ValueTypeExternref, wasm.ValueTypeV128: + types = append(types, vt) + default: + return nil, fmt.Errorf("invalid local type: 0x%x", vt) + } + } + + if sum > math.MaxUint32 { + return nil, fmt.Errorf("too many locals: %d", sum) + } + + var localTypes []wasm.ValueType + for i, num := range nums { + t := types[i] + for j := uint64(0); j < num; j++ { + localTypes = append(localTypes, t) + } + } + + body := make([]byte, remaining) + if _, err = io.ReadFull(r, body); err != nil { + return nil, fmt.Errorf("read body: %w", err) + } + + if endIndex := len(body) - 1; endIndex < 0 || body[endIndex] != wasm.OpcodeEnd { + return nil, fmt.Errorf("expr not end with OpcodeEnd") + } + + return &wasm.Code{Body: body, LocalTypes: localTypes}, nil +} + +// encodeCode returns the wasm.Code encoded in WebAssembly Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-code +func encodeCode(c *wasm.Code) []byte { + // local blocks compress locals while preserving index order by grouping locals of the same type. + // https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#code-section%E2%91%A0 + localBlockCount := uint32(0) // how many blocks of locals with the same type (types can repeat!) + var localBlocks []byte + localTypeLen := len(c.LocalTypes) + if localTypeLen > 0 { + i := localTypeLen - 1 + var runCount uint32 // count of the same type + var lastValueType wasm.ValueType // initialize to an invalid type 0 + + // iterate backwards so it is easier to size prefix + for ; i >= 0; i-- { + vt := c.LocalTypes[i] + if lastValueType != vt { + if runCount != 0 { // Only on the first iteration, this is zero when vt is compared against invalid + localBlocks = append(leb128.EncodeUint32(runCount), localBlocks...) + } + lastValueType = vt + localBlocks = append(leb128.EncodeUint32(uint32(vt)), localBlocks...) // reuse the EncodeUint32 cache + localBlockCount++ + runCount = 1 + } else { + runCount++ + } + } + localBlocks = append(leb128.EncodeUint32(runCount), localBlocks...) + localBlocks = append(leb128.EncodeUint32(localBlockCount), localBlocks...) + } else { + localBlocks = leb128.EncodeUint32(0) + } + code := append(localBlocks, c.Body...) + return append(leb128.EncodeUint32(uint32(len(code))), code...) +} diff --git a/vendor/github.com/tetratelabs/wabin/binary/const_expr.go b/vendor/github.com/tetratelabs/wabin/binary/const_expr.go new file mode 100644 index 00000000000..e2e251f670a --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/binary/const_expr.go @@ -0,0 +1,102 @@ +package binary + +import ( + "bytes" + "fmt" + + "github.com/tetratelabs/wabin/ieee754" + "github.com/tetratelabs/wabin/leb128" + "github.com/tetratelabs/wabin/wasm" +) + +func decodeConstantExpression(r *bytes.Reader, features wasm.CoreFeatures) (*wasm.ConstantExpression, error) { + b, err := r.ReadByte() + if err != nil { + return nil, fmt.Errorf("read opcode: %v", err) + } + + remainingBeforeData := int64(r.Len()) + offsetAtData := r.Size() - remainingBeforeData + + opcode := b + switch opcode { + case wasm.OpcodeI32Const: + // Treat constants as signed as their interpretation is not yet known per /RATIONALE.md + _, _, err = leb128.DecodeInt32(r) + case wasm.OpcodeI64Const: + // Treat constants as signed as their interpretation is not yet known per /RATIONALE.md + _, _, err = leb128.DecodeInt64(r) + case wasm.OpcodeF32Const: + _, err = ieee754.DecodeFloat32(r) + case wasm.OpcodeF64Const: + _, err = ieee754.DecodeFloat64(r) + case wasm.OpcodeGlobalGet: + _, _, err = leb128.DecodeUint32(r) + case wasm.OpcodeRefNull: + if err := features.RequireEnabled(wasm.CoreFeatureBulkMemoryOperations); err != nil { + return nil, fmt.Errorf("ref.null is not supported as %w", err) + } + reftype, err := r.ReadByte() + if err != nil { + return nil, fmt.Errorf("read reference type for ref.null: %w", err) + } else if reftype != wasm.RefTypeFuncref && reftype != wasm.RefTypeExternref { + return nil, fmt.Errorf("invalid type for ref.null: 0x%x", reftype) + } + case wasm.OpcodeRefFunc: + if err := features.RequireEnabled(wasm.CoreFeatureBulkMemoryOperations); err != nil { + return nil, fmt.Errorf("ref.func is not supported as %w", err) + } + // Parsing index. + _, _, err = leb128.DecodeUint32(r) + case wasm.OpcodeVecPrefix: + if err := features.RequireEnabled(wasm.CoreFeatureSIMD); err != nil { + return nil, fmt.Errorf("vector instructions are not supported as %w", err) + } + opcode, err = r.ReadByte() + if err != nil { + return nil, fmt.Errorf("read vector instruction opcode suffix: %w", err) + } + + if opcode != wasm.OpcodeVecV128Const { + return nil, fmt.Errorf("invalid vector opcode for const expression: %#x", opcode) + } + + remainingBeforeData = int64(r.Len()) + offsetAtData = r.Size() - remainingBeforeData + + n, err := r.Read(make([]byte, 16)) + if err != nil { + return nil, fmt.Errorf("read vector const instruction immediates: %w", err) + } else if n != 16 { + return nil, fmt.Errorf("read vector const instruction immediates: needs 16 bytes but was %d bytes", n) + } + default: + return nil, fmt.Errorf("%v for const expression opt code: %#x", ErrInvalidByte, b) + } + + if err != nil { + return nil, fmt.Errorf("read value: %v", err) + } + + if b, err = r.ReadByte(); err != nil { + return nil, fmt.Errorf("look for end opcode: %v", err) + } + + if b != wasm.OpcodeEnd { + return nil, fmt.Errorf("constant expression has been not terminated") + } + + data := make([]byte, remainingBeforeData-int64(r.Len())-1) + if _, err := r.ReadAt(data, offsetAtData); err != nil { + return nil, fmt.Errorf("error re-buffering ConstantExpression.Data") + } + + return &wasm.ConstantExpression{Opcode: opcode, Data: data}, nil +} + +func encodeConstantExpression(expr *wasm.ConstantExpression) (ret []byte) { + ret = append(ret, expr.Opcode) + ret = append(ret, expr.Data...) + ret = append(ret, wasm.OpcodeEnd) + return +} diff --git a/vendor/github.com/tetratelabs/wabin/binary/custom.go b/vendor/github.com/tetratelabs/wabin/binary/custom.go new file mode 100644 index 00000000000..6504efe7464 --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/binary/custom.go @@ -0,0 +1,22 @@ +package binary + +import ( + "bytes" + + "github.com/tetratelabs/wabin/wasm" +) + +// decodeCustomSection deserializes the data **not** associated with the "name" key in SectionIDCustom. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0 +func decodeCustomSection(r *bytes.Reader, name string, limit uint64) (result *wasm.CustomSection, err error) { + buf := make([]byte, limit) + _, err = r.Read(buf) + + result = &wasm.CustomSection{ + Name: name, + Data: buf, + } + + return +} diff --git a/vendor/github.com/tetratelabs/wabin/binary/data.go b/vendor/github.com/tetratelabs/wabin/binary/data.go new file mode 100644 index 00000000000..fd4cf8b38c6 --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/binary/data.go @@ -0,0 +1,94 @@ +package binary + +import ( + "bytes" + "fmt" + "io" + + "github.com/tetratelabs/wabin/leb128" + "github.com/tetratelabs/wabin/wasm" +) + +// dataSegmentPrefix represents three types of data segments. +// +// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-section +type dataSegmentPrefix = uint32 + +const ( + // dataSegmentPrefixActive is the prefix for the version 1.0 compatible + // data segment, which is classified as "active" in 2.0. + dataSegmentPrefixActive dataSegmentPrefix = 0x0 + // dataSegmentPrefixPassive prefixes the "passive" data segment as in + // version 2.0 specification. + dataSegmentPrefixPassive dataSegmentPrefix = 0x1 + // dataSegmentPrefixActiveWithMemoryIndex is the active prefix with memory + //index encoded which is defined for future use as of 2.0. + dataSegmentPrefixActiveWithMemoryIndex dataSegmentPrefix = 0x2 +) + +func decodeDataSegment(r *bytes.Reader, features wasm.CoreFeatures) (*wasm.DataSegment, error) { + dataSegmentPrefix, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("read data segment prefix: %w", err) + } + + if dataSegmentPrefix != dataSegmentPrefixActive { + if err := features.RequireEnabled(wasm.CoreFeatureBulkMemoryOperations); err != nil { + return nil, fmt.Errorf("non-zero prefix for data segment is invalid as %w", err) + } + } + + var expr *wasm.ConstantExpression + switch dataSegmentPrefix { + case dataSegmentPrefixActive, + dataSegmentPrefixActiveWithMemoryIndex: + // Active data segment as in + // https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-section + if dataSegmentPrefix == 0x2 { + d, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("read memory index: %v", err) + } else if d != 0 { + return nil, fmt.Errorf("memory index must be zero but was %d", d) + } + } + + expr, err = decodeConstantExpression(r, features) + if err != nil { + return nil, fmt.Errorf("read offset expression: %v", err) + } + case dataSegmentPrefixPassive: + // Passive data segment doesn't need const expr nor memory index encoded. + // https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-section + default: + return nil, fmt.Errorf("invalid data segment prefix: 0x%x", dataSegmentPrefix) + } + + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get the size of vector: %v", err) + } + + b := make([]byte, vs) + if _, err := io.ReadFull(r, b); err != nil { + return nil, fmt.Errorf("read bytes for init: %v", err) + } + + return &wasm.DataSegment{ + OffsetExpression: expr, + Init: b, + }, nil +} + +func encodeDataSegment(d *wasm.DataSegment) (ret []byte) { + if d.OffsetExpression == nil { + ret = append(ret, leb128.EncodeInt32(int32(dataSegmentPrefixPassive))...) + } else { + // Currently multiple memories are not supported. + ret = append(ret, leb128.EncodeInt32(int32(dataSegmentPrefixActive))...) + ret = append(ret, encodeConstantExpression(d.OffsetExpression)...) + } + ret = append(ret, leb128.EncodeUint32(uint32(len(d.Init)))...) + ret = append(ret, d.Init...) + return +} diff --git a/vendor/github.com/tetratelabs/wabin/binary/decoder.go b/vendor/github.com/tetratelabs/wabin/binary/decoder.go new file mode 100644 index 00000000000..e28428bd184 --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/binary/decoder.go @@ -0,0 +1,126 @@ +package binary + +import ( + "bytes" + "errors" + "fmt" + "io" + + "github.com/tetratelabs/wabin/leb128" + "github.com/tetratelabs/wabin/wasm" +) + +// DecodeModule implements wasm.DecodeModule for the WebAssembly Binary Format +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-format%E2%91%A0 +func DecodeModule(binary []byte, features wasm.CoreFeatures) (*wasm.Module, error) { + r := bytes.NewReader(binary) + + // Magic number. + buf := make([]byte, 4) + if _, err := io.ReadFull(r, buf); err != nil || !bytes.Equal(buf, Magic) { + return nil, ErrInvalidMagicNumber + } + + // Version. + if _, err := io.ReadFull(r, buf); err != nil || !bytes.Equal(buf, version) { + return nil, ErrInvalidVersion + } + + m := &wasm.Module{} + for { + // TODO: except custom sections, all others are required to be in order, but we aren't checking yet. + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#modules%E2%91%A0%E2%93%AA + sectionID, err := r.ReadByte() + if err == io.EOF { + break + } else if err != nil { + return nil, fmt.Errorf("read section id: %w", err) + } + + sectionSize, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get size of section %s: %v", wasm.SectionIDName(sectionID), err) + } + + sectionContentStart := r.Len() + switch sectionID { + case wasm.SectionIDCustom: + // First, validate the section and determine if the section for this name has already been set + name, nameSize, decodeErr := decodeUTF8(r, "custom section name") + if decodeErr != nil { + err = decodeErr + break + } else if sectionSize < nameSize { + err = fmt.Errorf("malformed custom section %s", name) + break + } else if name == "name" && m.NameSection != nil { + err = fmt.Errorf("redundant custom section %s", name) + break + } + + // Now, either decode the NameSection or CustomSection + limit := sectionSize - nameSize + if name == "name" { + m.NameSection, err = decodeNameSection(r, uint64(limit)) + } else { + custom, err := decodeCustomSection(r, name, uint64(limit)) + if err != nil { + return nil, fmt.Errorf("failed to read custom section name[%s]: %w", name, err) + } + m.CustomSections = append(m.CustomSections, custom) + } + + case wasm.SectionIDType: + m.TypeSection, err = decodeTypeSection(features, r) + case wasm.SectionIDImport: + if m.ImportSection, err = decodeImportSection(r, features); err != nil { + return nil, err // avoid re-wrapping the error. + } + case wasm.SectionIDFunction: + m.FunctionSection, err = decodeFunctionSection(r) + case wasm.SectionIDTable: + m.TableSection, err = decodeTableSection(r, features) + case wasm.SectionIDMemory: + m.MemorySection, err = decodeMemorySection(r) + case wasm.SectionIDGlobal: + if m.GlobalSection, err = decodeGlobalSection(r, features); err != nil { + return nil, err // avoid re-wrapping the error. + } + case wasm.SectionIDExport: + m.ExportSection, err = decodeExportSection(r) + case wasm.SectionIDStart: + if m.StartSection != nil { + return nil, errors.New("multiple start sections are invalid") + } + m.StartSection, err = decodeStartSection(r) + case wasm.SectionIDElement: + m.ElementSection, err = decodeElementSection(r, features) + case wasm.SectionIDCode: + m.CodeSection, err = decodeCodeSection(r) + case wasm.SectionIDData: + m.DataSection, err = decodeDataSection(r, features) + case wasm.SectionIDDataCount: + if err := features.RequireEnabled(wasm.CoreFeatureBulkMemoryOperations); err != nil { + return nil, fmt.Errorf("data count section not supported as %v", err) + } + m.DataCountSection, err = decodeDataCountSection(r) + default: + err = ErrInvalidSectionID + } + + readBytes := sectionContentStart - r.Len() + if err == nil && int(sectionSize) != readBytes { + err = fmt.Errorf("invalid section length: expected to be %d but got %d", sectionSize, readBytes) + } + + if err != nil { + return nil, fmt.Errorf("section %s: %v", wasm.SectionIDName(sectionID), err) + } + } + + functionCount, codeCount := m.SectionElementCount(wasm.SectionIDFunction), m.SectionElementCount(wasm.SectionIDCode) + if functionCount != codeCount { + return nil, fmt.Errorf("function and code section have inconsistent lengths: %d != %d", functionCount, codeCount) + } + return m, nil +} diff --git a/vendor/github.com/tetratelabs/wabin/binary/element.go b/vendor/github.com/tetratelabs/wabin/binary/element.go new file mode 100644 index 00000000000..93b7fbbee1f --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/binary/element.go @@ -0,0 +1,308 @@ +package binary + +import ( + "bytes" + "errors" + "fmt" + + "github.com/tetratelabs/wabin/leb128" + "github.com/tetratelabs/wabin/wasm" +) + +func ensureElementKindFuncRef(r *bytes.Reader) error { + elemKind, err := r.ReadByte() + if err != nil { + return fmt.Errorf("read element prefix: %w", err) + } + if elemKind != 0x0 { // ElemKind is fixed to 0x0 now: https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#element-section + return fmt.Errorf("element kind must be zero but was 0x%x", elemKind) + } + return nil +} + +func decodeElementInitValueVector(r *bytes.Reader) ([]*wasm.Index, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get size of vector: %w", err) + } + + vec := make([]*wasm.Index, vs) + for i := range vec { + u32, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("read function index: %w", err) + } + vec[i] = &u32 + } + return vec, nil +} + +func decodeElementConstExprVector(r *bytes.Reader, elemType wasm.RefType, features wasm.CoreFeatures) ([]*wasm.Index, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("failed to get the size of constexpr vector: %w", err) + } + vec := make([]*wasm.Index, vs) + for i := range vec { + expr, err := decodeConstantExpression(r, features) + if err != nil { + return nil, err + } + switch expr.Opcode { + case wasm.OpcodeRefFunc: + if elemType != wasm.RefTypeFuncref { + return nil, fmt.Errorf("element type mismatch: want %s, but constexpr has funcref", wasm.RefTypeName(elemType)) + } + v, _, _ := leb128.DecodeUint32(bytes.NewReader(expr.Data)) + vec[i] = &v + case wasm.OpcodeRefNull: + if elemType != expr.Data[0] { + return nil, fmt.Errorf("element type mismatch: want %s, but constexpr has %s", + wasm.RefTypeName(elemType), wasm.RefTypeName(expr.Data[0])) + } + // vec[i] is already nil, so nothing to do. + default: + return nil, fmt.Errorf("const expr must be either ref.null or ref.func but was %s", wasm.InstructionName(expr.Opcode)) + } + } + return vec, nil +} + +func decodeElementRefType(r *bytes.Reader) (ret wasm.RefType, err error) { + ret, err = r.ReadByte() + if err != nil { + err = fmt.Errorf("read element ref type: %w", err) + return + } + if ret != wasm.RefTypeFuncref && ret != wasm.RefTypeExternref { + return 0, errors.New("ref type must be funcref or externref for element as of WebAssembly 2.0") + } + return +} + +const ( + // The prefix is explained at https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#element-section + + // elementSegmentPrefixLegacy is the legacy prefix and is only valid one + // before FeatureBulkMemoryOperations. + elementSegmentPrefixLegacy = iota + // elementSegmentPrefixPassiveFuncrefValueVector is the passive element + // whose indexes are encoded as vec(varint), and reftype is fixed to funcref. + elementSegmentPrefixPassiveFuncrefValueVector + // elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex is the same + // as elementSegmentPrefixPassiveFuncrefValueVector but active and table + // index is encoded. + elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex + // elementSegmentPrefixDeclarativeFuncrefValueVector is the same as + // elementSegmentPrefixPassiveFuncrefValueVector but declarative. + elementSegmentPrefixDeclarativeFuncrefValueVector + // elementSegmentPrefixActiveFuncrefConstExprVector is active and reftype + // is fixed to funcref and indexes are encoded as vec(const_expr). + elementSegmentPrefixActiveFuncrefConstExprVector + // elementSegmentPrefixPassiveConstExprVector is passive where indexes + // are encoded as vec(const_expr), and reftype is encoded. + elementSegmentPrefixPassiveConstExprVector + // elementSegmentPrefixPassiveConstExprVector is active where indexes are + // encoded as vec(const_expr), and reftype and table index are encoded. + elementSegmentPrefixActiveConstExprVector + // elementSegmentPrefixDeclarativeConstExprVector is declarative where + // indexes are encoded as vec(const_expr), and reftype is encoded. + elementSegmentPrefixDeclarativeConstExprVector +) + +func decodeElementSegment(r *bytes.Reader, features wasm.CoreFeatures) (*wasm.ElementSegment, error) { + prefix, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("read element prefix: %w", err) + } + + if prefix != elementSegmentPrefixLegacy { + if err := features.RequireEnabled(wasm.CoreFeatureBulkMemoryOperations); err != nil { + return nil, fmt.Errorf("non-zero prefix for element segment is invalid as %w", err) + } + } + + // Encoding depends on the prefix and described at https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#element-section + switch prefix { + case elementSegmentPrefixLegacy: + // Legacy prefix which is WebAssembly 1.0 compatible. + expr, err := decodeConstantExpression(r, features) + if err != nil { + return nil, fmt.Errorf("read expr for offset: %w", err) + } + + init, err := decodeElementInitValueVector(r) + if err != nil { + return nil, err + } + + return &wasm.ElementSegment{ + OffsetExpr: expr, + Init: init, + Type: wasm.RefTypeFuncref, + Mode: wasm.ElementModeActive, + // Legacy prefix has the fixed table index zero. + TableIndex: 0, + }, nil + case elementSegmentPrefixPassiveFuncrefValueVector: + // Prefix 1 requires funcref. + if err = ensureElementKindFuncRef(r); err != nil { + return nil, err + } + + init, err := decodeElementInitValueVector(r) + if err != nil { + return nil, err + } + return &wasm.ElementSegment{ + Init: init, + Type: wasm.RefTypeFuncref, + Mode: wasm.ElementModePassive, + }, nil + case elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex: + tableIndex, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get size of vector: %w", err) + } + + if tableIndex != 0 { + if err := features.RequireEnabled(wasm.CoreFeatureReferenceTypes); err != nil { + return nil, fmt.Errorf("table index must be zero but was %d: %w", tableIndex, err) + } + } + + expr, err := decodeConstantExpression(r, features) + if err != nil { + return nil, fmt.Errorf("read expr for offset: %w", err) + } + + // Prefix 2 requires funcref. + if err = ensureElementKindFuncRef(r); err != nil { + return nil, err + } + + init, err := decodeElementInitValueVector(r) + if err != nil { + return nil, err + } + return &wasm.ElementSegment{ + OffsetExpr: expr, + Init: init, + Type: wasm.RefTypeFuncref, + Mode: wasm.ElementModeActive, + TableIndex: tableIndex, + }, nil + case elementSegmentPrefixDeclarativeFuncrefValueVector: + // Prefix 3 requires funcref. + if err = ensureElementKindFuncRef(r); err != nil { + return nil, err + } + init, err := decodeElementInitValueVector(r) + if err != nil { + return nil, err + } + return &wasm.ElementSegment{ + Init: init, + Type: wasm.RefTypeFuncref, + Mode: wasm.ElementModeDeclarative, + }, nil + case elementSegmentPrefixActiveFuncrefConstExprVector: + expr, err := decodeConstantExpression(r, features) + if err != nil { + return nil, fmt.Errorf("read expr for offset: %w", err) + } + + init, err := decodeElementConstExprVector(r, wasm.RefTypeFuncref, features) + if err != nil { + return nil, err + } + + return &wasm.ElementSegment{ + OffsetExpr: expr, + Init: init, + Type: wasm.RefTypeFuncref, + Mode: wasm.ElementModeActive, + TableIndex: 0, + }, nil + case elementSegmentPrefixPassiveConstExprVector: + refType, err := decodeElementRefType(r) + if err != nil { + return nil, err + } + init, err := decodeElementConstExprVector(r, refType, features) + if err != nil { + return nil, err + } + return &wasm.ElementSegment{ + Init: init, + Type: refType, + Mode: wasm.ElementModePassive, + }, nil + case elementSegmentPrefixActiveConstExprVector: + tableIndex, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get size of vector: %w", err) + } + + if tableIndex != 0 { + if err := features.RequireEnabled(wasm.CoreFeatureReferenceTypes); err != nil { + return nil, fmt.Errorf("table index must be zero but was %d: %w", tableIndex, err) + } + } + expr, err := decodeConstantExpression(r, features) + if err != nil { + return nil, fmt.Errorf("read expr for offset: %w", err) + } + + refType, err := decodeElementRefType(r) + if err != nil { + return nil, err + } + + init, err := decodeElementConstExprVector(r, refType, features) + if err != nil { + return nil, err + } + + return &wasm.ElementSegment{ + OffsetExpr: expr, + Init: init, + Type: refType, + Mode: wasm.ElementModeActive, + TableIndex: tableIndex, + }, nil + case elementSegmentPrefixDeclarativeConstExprVector: + refType, err := decodeElementRefType(r) + if err != nil { + return nil, err + } + init, err := decodeElementConstExprVector(r, refType, features) + if err != nil { + return nil, err + } + return &wasm.ElementSegment{ + Init: init, + Type: refType, + Mode: wasm.ElementModeDeclarative, + }, nil + default: + return nil, fmt.Errorf("invalid element segment prefix: 0x%x", prefix) + } +} + +// encodeCode returns the wasm.ElementSegment encoded in WebAssembly Binary Format. +// +// https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#element-section%E2%91%A0 +func encodeElement(e *wasm.ElementSegment) (ret []byte) { + if e.Mode == wasm.ElementModeActive { + ret = append(ret, leb128.EncodeInt32(int32(e.TableIndex))...) + ret = append(ret, encodeConstantExpression(e.OffsetExpr)...) + ret = append(ret, leb128.EncodeUint32(uint32(len(e.Init)))...) + for _, idx := range e.Init { + ret = append(ret, leb128.EncodeInt32(int32(*idx))...) + } + } else { + panic("TODO: support encoding for non-active elements in bulk-memory-operations proposal") + } + return +} diff --git a/vendor/github.com/tetratelabs/wabin/binary/encoder.go b/vendor/github.com/tetratelabs/wabin/binary/encoder.go new file mode 100644 index 00000000000..8a56c472429 --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/binary/encoder.go @@ -0,0 +1,59 @@ +package binary + +import ( + "github.com/tetratelabs/wabin/wasm" +) + +var sizePrefixedName = []byte{4, 'n', 'a', 'm', 'e'} + +// EncodeModule implements wasm.EncodeModule for the WebAssembly Binary Format. +// Note: If saving to a file, the conventional extension is wasm +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-format%E2%91%A0 +func EncodeModule(m *wasm.Module) (bytes []byte) { + bytes = append(Magic, version...) + if m.SectionElementCount(wasm.SectionIDType) > 0 { + bytes = append(bytes, encodeTypeSection(m.TypeSection)...) + } + if m.SectionElementCount(wasm.SectionIDImport) > 0 { + bytes = append(bytes, encodeImportSection(m.ImportSection)...) + } + if m.SectionElementCount(wasm.SectionIDFunction) > 0 { + bytes = append(bytes, encodeFunctionSection(m.FunctionSection)...) + } + if m.SectionElementCount(wasm.SectionIDTable) > 0 { + bytes = append(bytes, encodeTableSection(m.TableSection)...) + } + if m.SectionElementCount(wasm.SectionIDMemory) > 0 { + bytes = append(bytes, encodeMemorySection(m.MemorySection)...) + } + if m.SectionElementCount(wasm.SectionIDGlobal) > 0 { + bytes = append(bytes, encodeGlobalSection(m.GlobalSection)...) + } + if m.SectionElementCount(wasm.SectionIDExport) > 0 { + bytes = append(bytes, encodeExportSection(m.ExportSection)...) + } + if m.SectionElementCount(wasm.SectionIDStart) > 0 { + bytes = append(bytes, encodeStartSection(*m.StartSection)...) + } + if m.SectionElementCount(wasm.SectionIDElement) > 0 { + bytes = append(bytes, encodeElementSection(m.ElementSection)...) + } + if m.SectionElementCount(wasm.SectionIDCode) > 0 { + bytes = append(bytes, encodeCodeSection(m.CodeSection)...) + } + if m.SectionElementCount(wasm.SectionIDData) > 0 { + bytes = append(bytes, encodeDataSection(m.DataSection)...) + } + if m.SectionElementCount(wasm.SectionIDCustom) > 0 { + // >> The name section should appear only once in a module, and only after the data section. + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namesec + if m.NameSection != nil { + nameSection := append(sizePrefixedName, encodeNameSectionData(m.NameSection)...) + bytes = append(bytes, encodeSection(wasm.SectionIDCustom, nameSection)...) + } + for _, custom := range m.CustomSections { + bytes = append(bytes, encodeSection(wasm.SectionIDCustom, encodeCustomSection(custom))...) + } + } + return +} diff --git a/vendor/github.com/tetratelabs/wabin/binary/errors.go b/vendor/github.com/tetratelabs/wabin/binary/errors.go new file mode 100644 index 00000000000..b9125b0387d --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/binary/errors.go @@ -0,0 +1,11 @@ +package binary + +import "errors" + +var ( + ErrInvalidByte = errors.New("invalid byte") + ErrInvalidMagicNumber = errors.New("invalid magic number") + ErrInvalidVersion = errors.New("invalid version header") + ErrInvalidSectionID = errors.New("invalid section id") + ErrCustomSectionNotFound = errors.New("custom section not found") +) diff --git a/vendor/github.com/tetratelabs/wabin/binary/export.go b/vendor/github.com/tetratelabs/wabin/binary/export.go new file mode 100644 index 00000000000..a6128631e60 --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/binary/export.go @@ -0,0 +1,43 @@ +package binary + +import ( + "bytes" + "fmt" + + "github.com/tetratelabs/wabin/leb128" + "github.com/tetratelabs/wabin/wasm" +) + +func decodeExport(r *bytes.Reader) (i *wasm.Export, err error) { + i = &wasm.Export{} + + if i.Name, _, err = decodeUTF8(r, "export name"); err != nil { + return nil, err + } + + b, err := r.ReadByte() + if err != nil { + return nil, fmt.Errorf("error decoding export kind: %w", err) + } + + i.Type = b + switch i.Type { + case wasm.ExternTypeFunc, wasm.ExternTypeTable, wasm.ExternTypeMemory, wasm.ExternTypeGlobal: + if i.Index, _, err = leb128.DecodeUint32(r); err != nil { + return nil, fmt.Errorf("error decoding export index: %w", err) + } + default: + return nil, fmt.Errorf("%w: invalid byte for exportdesc: %#x", ErrInvalidByte, b) + } + return +} + +// encodeExport returns the wasm.Export encoded in WebAssembly Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#export-section%E2%91%A0 +func encodeExport(i *wasm.Export) []byte { + data := encodeSizePrefixed([]byte(i.Name)) + data = append(data, i.Type) + data = append(data, leb128.EncodeUint32(i.Index)...) + return data +} diff --git a/vendor/github.com/tetratelabs/wabin/binary/function.go b/vendor/github.com/tetratelabs/wabin/binary/function.go new file mode 100644 index 00000000000..cbec5fee95d --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/binary/function.go @@ -0,0 +1,100 @@ +package binary + +import ( + "bytes" + "fmt" + + "github.com/tetratelabs/wabin/leb128" + "github.com/tetratelabs/wabin/wasm" +) + +var nullary = []byte{0x60, 0, 0} + +// encodedOneParam is a cache of wasm.FunctionType values for param length 1 and result length 0 +var encodedOneParam = map[wasm.ValueType][]byte{ + wasm.ValueTypeI32: {0x60, 1, wasm.ValueTypeI32, 0}, + wasm.ValueTypeI64: {0x60, 1, wasm.ValueTypeI64, 0}, + wasm.ValueTypeF32: {0x60, 1, wasm.ValueTypeF32, 0}, + wasm.ValueTypeF64: {0x60, 1, wasm.ValueTypeF64, 0}, +} + +// encodedOneResult is a cache of wasm.FunctionType values for param length 0 and result length 1 +var encodedOneResult = map[wasm.ValueType][]byte{ + wasm.ValueTypeI32: {0x60, 0, 1, wasm.ValueTypeI32}, + wasm.ValueTypeI64: {0x60, 0, 1, wasm.ValueTypeI64}, + wasm.ValueTypeF32: {0x60, 0, 1, wasm.ValueTypeF32}, + wasm.ValueTypeF64: {0x60, 0, 1, wasm.ValueTypeF64}, +} + +// encodeFunctionType returns the wasm.FunctionType encoded in WebAssembly Binary Format. +// +// Note: Function types are encoded by the byte 0x60 followed by the respective vectors of parameter and result types. +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-types%E2%91%A4 +func encodeFunctionType(t *wasm.FunctionType) []byte { + paramCount, resultCount := len(t.Params), len(t.Results) + if paramCount == 0 && resultCount == 0 { + return nullary + } + if resultCount == 0 { + if paramCount == 1 { + if encoded, ok := encodedOneParam[t.Params[0]]; ok { + return encoded + } + } + return append(append([]byte{0x60}, encodeValTypes(t.Params)...), 0) + } else if resultCount == 1 { + if paramCount == 0 { + if encoded, ok := encodedOneResult[t.Results[0]]; ok { + return encoded + } + } + return append(append([]byte{0x60}, encodeValTypes(t.Params)...), 1, t.Results[0]) + } + // Only reached when "multi-value" is enabled because WebAssembly supports at most 1 result. + data := append([]byte{0x60}, encodeValTypes(t.Params)...) + return append(data, encodeValTypes(t.Results)...) +} + +func decodeFunctionType(features wasm.CoreFeatures, r *bytes.Reader) (*wasm.FunctionType, error) { + b, err := r.ReadByte() + if err != nil { + return nil, fmt.Errorf("read leading byte: %w", err) + } + + if b != 0x60 { + return nil, fmt.Errorf("%w: %#x != 0x60", ErrInvalidByte, b) + } + + paramCount, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("could not read parameter count: %w", err) + } + + paramTypes, err := decodeValueTypes(r, paramCount) + if err != nil { + return nil, fmt.Errorf("could not read parameter types: %w", err) + } + + resultCount, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("could not read result count: %w", err) + } + + // Guard >1.0 feature multi-value + if resultCount > 1 { + if err = features.RequireEnabled(wasm.CoreFeatureMultiValue); err != nil { + return nil, fmt.Errorf("multiple result types invalid as %v", err) + } + } + + resultTypes, err := decodeValueTypes(r, resultCount) + if err != nil { + return nil, fmt.Errorf("could not read result types: %w", err) + } + + ret := &wasm.FunctionType{ + Params: paramTypes, + Results: resultTypes, + } + return ret, nil +} diff --git a/vendor/github.com/tetratelabs/wabin/binary/global.go b/vendor/github.com/tetratelabs/wabin/binary/global.go new file mode 100644 index 00000000000..f5965c978e7 --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/binary/global.go @@ -0,0 +1,66 @@ +package binary + +import ( + "bytes" + "fmt" + + "github.com/tetratelabs/wabin/wasm" +) + +// decodeGlobal returns the wasm.Global decoded with the WebAssembly Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-global +func decodeGlobal(r *bytes.Reader, features wasm.CoreFeatures) (*wasm.Global, error) { + gt, err := decodeGlobalType(r) + if err != nil { + return nil, err + } + + init, err := decodeConstantExpression(r, features) + if err != nil { + return nil, err + } + + return &wasm.Global{Type: gt, Init: init}, nil +} + +// decodeGlobalType returns the wasm.GlobalType decoded with the WebAssembly Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-globaltype +func decodeGlobalType(r *bytes.Reader) (*wasm.GlobalType, error) { + vt, err := decodeValueTypes(r, 1) + if err != nil { + return nil, fmt.Errorf("read value type: %w", err) + } + + ret := &wasm.GlobalType{ + ValType: vt[0], + } + + b, err := r.ReadByte() + if err != nil { + return nil, fmt.Errorf("read mutablity: %w", err) + } + + switch mut := b; mut { + case 0x00: // not mutable + case 0x01: // mutable + ret.Mutable = true + default: + return nil, fmt.Errorf("%w for mutability: %#x != 0x00 or 0x01", ErrInvalidByte, mut) + } + return ret, nil +} + +// encodeGlobal returns the wasm.Global encoded in WebAssembly Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-section%E2%91%A0 +func encodeGlobal(g *wasm.Global) (data []byte) { + var mutable byte + if g.Type.Mutable { + mutable = 1 + } + data = []byte{g.Type.ValType, mutable} + data = append(data, encodeConstantExpression(g.Init)...) + return +} diff --git a/vendor/github.com/tetratelabs/wabin/binary/header.go b/vendor/github.com/tetratelabs/wabin/binary/header.go new file mode 100644 index 00000000000..29ba1b599e4 --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/binary/header.go @@ -0,0 +1,9 @@ +package binary + +// Magic is the 4 byte preamble (literally "\0asm") of the binary format +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-magic +var Magic = []byte{0x00, 0x61, 0x73, 0x6D} + +// version is format version and doesn't change between known specification versions +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-version +var version = []byte{0x01, 0x00, 0x00, 0x00} diff --git a/vendor/github.com/tetratelabs/wabin/binary/import.go b/vendor/github.com/tetratelabs/wabin/binary/import.go new file mode 100644 index 00000000000..fe40629001a --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/binary/import.go @@ -0,0 +1,78 @@ +package binary + +import ( + "bytes" + "fmt" + + "github.com/tetratelabs/wabin/leb128" + "github.com/tetratelabs/wabin/wasm" +) + +func decodeImport( + r *bytes.Reader, + idx uint32, + features wasm.CoreFeatures, +) (i *wasm.Import, err error) { + i = &wasm.Import{} + if i.Module, _, err = decodeUTF8(r, "import module"); err != nil { + return nil, fmt.Errorf("import[%d] error decoding module: %w", idx, err) + } + + if i.Name, _, err = decodeUTF8(r, "import name"); err != nil { + return nil, fmt.Errorf("import[%d] error decoding name: %w", idx, err) + } + + b, err := r.ReadByte() + if err != nil { + return nil, fmt.Errorf("import[%d] error decoding type: %w", idx, err) + } + i.Type = b + switch i.Type { + case wasm.ExternTypeFunc: + i.DescFunc, _, err = leb128.DecodeUint32(r) + case wasm.ExternTypeTable: + i.DescTable, err = decodeTable(r, features) + case wasm.ExternTypeMemory: + i.DescMem, err = decodeMemory(r) + case wasm.ExternTypeGlobal: + i.DescGlobal, err = decodeGlobalType(r) + default: + err = fmt.Errorf("%w: invalid byte for importdesc: %#x", ErrInvalidByte, b) + } + if err != nil { + return nil, fmt.Errorf("import[%d] %s[%s.%s]: %w", idx, wasm.ExternTypeName(i.Type), i.Module, i.Name, err) + } + return +} + +// encodeImport returns the wasm.Import encoded in WebAssembly Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-import +func encodeImport(i *wasm.Import) []byte { + data := encodeSizePrefixed([]byte(i.Module)) + data = append(data, encodeSizePrefixed([]byte(i.Name))...) + data = append(data, i.Type) + switch i.Type { + case wasm.ExternTypeFunc: + data = append(data, leb128.EncodeUint32(i.DescFunc)...) + case wasm.ExternTypeTable: + data = append(data, wasm.RefTypeFuncref) + data = append(data, encodeLimitsType(i.DescTable.Min, i.DescTable.Max)...) + case wasm.ExternTypeMemory: + maxPtr := &i.DescMem.Max + if !i.DescMem.IsMaxEncoded { + maxPtr = nil + } + data = append(data, encodeLimitsType(i.DescMem.Min, maxPtr)...) + case wasm.ExternTypeGlobal: + g := i.DescGlobal + var mutable byte + if g.Mutable { + mutable = 1 + } + data = append(data, g.ValType, mutable) + default: + panic(fmt.Errorf("invalid externtype: %s", wasm.ExternTypeName(i.Type))) + } + return data +} diff --git a/vendor/github.com/tetratelabs/wabin/binary/limits.go b/vendor/github.com/tetratelabs/wabin/binary/limits.go new file mode 100644 index 00000000000..bb9ecb938a7 --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/binary/limits.go @@ -0,0 +1,52 @@ +package binary + +import ( + "bytes" + "fmt" + + "github.com/tetratelabs/wabin/leb128" +) + +// decodeLimitsType returns the `limitsType` (min, max) decoded with the WebAssembly Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#limits%E2%91%A6 +func decodeLimitsType(r *bytes.Reader) (min uint32, max *uint32, err error) { + var flag byte + if flag, err = r.ReadByte(); err != nil { + err = fmt.Errorf("read leading byte: %v", err) + return + } + + switch flag { + case 0x00: + min, _, err = leb128.DecodeUint32(r) + if err != nil { + err = fmt.Errorf("read min of limit: %v", err) + } + case 0x01: + min, _, err = leb128.DecodeUint32(r) + if err != nil { + err = fmt.Errorf("read min of limit: %v", err) + return + } + var m uint32 + if m, _, err = leb128.DecodeUint32(r); err != nil { + err = fmt.Errorf("read max of limit: %v", err) + } else { + max = &m + } + default: + err = fmt.Errorf("%v for limits: %#x != 0x00 or 0x01", ErrInvalidByte, flag) + } + return +} + +// encodeLimitsType returns the `limitsType` (min, max) encoded in WebAssembly Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#limits%E2%91%A6 +func encodeLimitsType(min uint32, max *uint32) []byte { + if max == nil { + return append(leb128.EncodeUint32(0x00), leb128.EncodeUint32(min)...) + } + return append(leb128.EncodeUint32(0x01), append(leb128.EncodeUint32(min), leb128.EncodeUint32(*max)...)...) +} diff --git a/vendor/github.com/tetratelabs/wabin/binary/memory.go b/vendor/github.com/tetratelabs/wabin/binary/memory.go new file mode 100644 index 00000000000..3cc59d18869 --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/binary/memory.go @@ -0,0 +1,43 @@ +package binary + +import ( + "bytes" + "fmt" + + "github.com/tetratelabs/wabin/wasm" +) + +// decodeMemory returns the wasm.Memory decoded with the WebAssembly +// Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-memory +func decodeMemory(r *bytes.Reader) (*wasm.Memory, error) { + min, maxP, err := decodeLimitsType(r) + if err != nil { + return nil, err + } + + mem := &wasm.Memory{Min: min} + if maxP != nil { + mem.Max = *maxP + mem.IsMaxEncoded = true + + if min > mem.Max { + return nil, fmt.Errorf("min %d pages (%s) > max %d pages (%s)", + min, wasm.PagesToUnitOfBytes(min), mem.Max, wasm.PagesToUnitOfBytes(mem.Max)) + } + } + + return mem, nil +} + +// encodeMemory returns the wasm.Memory encoded in WebAssembly Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-memory +func encodeMemory(i *wasm.Memory) []byte { + maxPtr := &i.Max + if !i.IsMaxEncoded { + maxPtr = nil + } + return encodeLimitsType(i.Min, maxPtr) +} diff --git a/vendor/github.com/tetratelabs/wabin/binary/names.go b/vendor/github.com/tetratelabs/wabin/binary/names.go new file mode 100644 index 00000000000..bc2b3cdf5ed --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/binary/names.go @@ -0,0 +1,228 @@ +package binary + +import ( + "bytes" + "fmt" + "io" + + "github.com/tetratelabs/wabin/leb128" + "github.com/tetratelabs/wabin/wasm" +) + +const ( + // subsectionIDModuleName contains only the module name. + subsectionIDModuleName = uint8(0) + // subsectionIDFunctionNames is a map of indices to function names, in ascending order by function index + subsectionIDFunctionNames = uint8(1) + // subsectionIDLocalNames contain a map of function indices to a map of local indices to their names, in ascending + // order by function and local index + subsectionIDLocalNames = uint8(2) +) + +// decodeNameSection deserializes the data associated with the "name" key in SectionIDCustom according to the +// standard: +// +// * ModuleName decode from subsection 0 +// * FunctionNames decode from subsection 1 +// * LocalNames decode from subsection 2 +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namesec +func decodeNameSection(r *bytes.Reader, limit uint64) (result *wasm.NameSection, err error) { + // TODO: add leb128 functions that work on []byte and offset. While using a reader allows us to reuse reader-based + // leb128 functions, it is less efficient, causes untestable code and in some cases more complex vs plain []byte. + result = &wasm.NameSection{} + + // subsectionID is decoded if known, and skipped if not + var subsectionID uint8 + // subsectionSize is the length to skip when the subsectionID is unknown + var subsectionSize uint32 + var bytesRead uint64 + for limit > 0 { + if subsectionID, err = r.ReadByte(); err != nil { + if err == io.EOF { + return result, nil + } + // TODO: untestable as this can't fail for a reason beside EOF reading a byte from a buffer + return nil, fmt.Errorf("failed to read a subsection ID: %w", err) + } + limit-- + + if subsectionSize, bytesRead, err = leb128.DecodeUint32(r); err != nil { + return nil, fmt.Errorf("failed to read the size of subsection[%d]: %w", subsectionID, err) + } + limit -= bytesRead + + switch subsectionID { + case subsectionIDModuleName: + if result.ModuleName, _, err = decodeUTF8(r, "module name"); err != nil { + return nil, err + } + case subsectionIDFunctionNames: + if result.FunctionNames, err = decodeFunctionNames(r); err != nil { + return nil, err + } + case subsectionIDLocalNames: + if result.LocalNames, err = decodeLocalNames(r); err != nil { + return nil, err + } + default: // Skip other subsections. + // Note: Not Seek because it doesn't err when given an offset past EOF. Rather, it leads to undefined state. + if _, err = io.CopyN(io.Discard, r, int64(subsectionSize)); err != nil { + return nil, fmt.Errorf("failed to skip subsection[%d]: %w", subsectionID, err) + } + } + limit -= uint64(subsectionSize) + } + return +} + +func decodeFunctionNames(r *bytes.Reader) (wasm.NameMap, error) { + functionCount, err := decodeFunctionCount(r, subsectionIDFunctionNames) + if err != nil { + return nil, err + } + + result := make(wasm.NameMap, functionCount) + for i := uint32(0); i < functionCount; i++ { + functionIndex, err := decodeFunctionIndex(r, subsectionIDFunctionNames) + if err != nil { + return nil, err + } + + name, _, err := decodeUTF8(r, "function[%d] name", functionIndex) + if err != nil { + return nil, err + } + result[i] = &wasm.NameAssoc{Index: functionIndex, Name: name} + } + return result, nil +} + +func decodeLocalNames(r *bytes.Reader) (wasm.IndirectNameMap, error) { + functionCount, err := decodeFunctionCount(r, subsectionIDLocalNames) + if err != nil { + return nil, err + } + + result := make(wasm.IndirectNameMap, functionCount) + for i := uint32(0); i < functionCount; i++ { + functionIndex, err := decodeFunctionIndex(r, subsectionIDLocalNames) + if err != nil { + return nil, err + } + + localCount, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("failed to read the local count for function[%d]: %w", functionIndex, err) + } + + locals := make(wasm.NameMap, localCount) + for j := uint32(0); j < localCount; j++ { + localIndex, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("failed to read a local index of function[%d]: %w", functionIndex, err) + } + + name, _, err := decodeUTF8(r, "function[%d] local[%d] name", functionIndex, localIndex) + if err != nil { + return nil, err + } + locals[j] = &wasm.NameAssoc{Index: localIndex, Name: name} + } + result[i] = &wasm.NameMapAssoc{Index: functionIndex, NameMap: locals} + } + return result, nil +} + +func decodeFunctionIndex(r *bytes.Reader, subsectionID uint8) (uint32, error) { + functionIndex, _, err := leb128.DecodeUint32(r) + if err != nil { + return 0, fmt.Errorf("failed to read a function index in subsection[%d]: %w", subsectionID, err) + } + return functionIndex, nil +} + +func decodeFunctionCount(r *bytes.Reader, subsectionID uint8) (uint32, error) { + functionCount, _, err := leb128.DecodeUint32(r) + if err != nil { + return 0, fmt.Errorf("failed to read the function count of subsection[%d]: %w", subsectionID, err) + } + return functionCount, nil +} + +// encodeNameSectionData serializes the data for the "name" key in wasm.SectionIDCustom according to the +// standard: +// +// Note: The result can be nil because this does not encode empty subsections +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namesec +func encodeNameSectionData(n *wasm.NameSection) (data []byte) { + if n.ModuleName != "" { + data = append(data, encodeNameSubsection(subsectionIDModuleName, encodeSizePrefixed([]byte(n.ModuleName)))...) + } + if fd := encodeFunctionNameData(n); len(fd) > 0 { + data = append(data, encodeNameSubsection(subsectionIDFunctionNames, fd)...) + } + if ld := encodeLocalNameData(n); len(ld) > 0 { + data = append(data, encodeNameSubsection(subsectionIDLocalNames, ld)...) + } + return +} + +// encodeFunctionNameData encodes the data for the function name subsection. +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-funcnamesec +func encodeFunctionNameData(n *wasm.NameSection) []byte { + if len(n.FunctionNames) == 0 { + return nil + } + + return encodeNameMap(n.FunctionNames) +} + +func encodeNameMap(m wasm.NameMap) []byte { + count := uint32(len(m)) + data := leb128.EncodeUint32(count) + for _, na := range m { + data = append(data, encodeNameAssoc(na)...) + } + return data +} + +// encodeLocalNameData encodes the data for the local name subsection. +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-localnamesec +func encodeLocalNameData(n *wasm.NameSection) []byte { + if len(n.LocalNames) == 0 { + return nil + } + + funcNameCount := uint32(len(n.LocalNames)) + subsection := leb128.EncodeUint32(funcNameCount) + + for _, na := range n.LocalNames { + locals := encodeNameMap(na.NameMap) + subsection = append(subsection, append(leb128.EncodeUint32(na.Index), locals...)...) + } + return subsection +} + +// encodeNameSubsection returns a buffer encoding the given subsection +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#subsections%E2%91%A0 +func encodeNameSubsection(subsectionID uint8, content []byte) []byte { + contentSizeInBytes := leb128.EncodeUint32(uint32(len(content))) + result := []byte{subsectionID} + result = append(result, contentSizeInBytes...) + result = append(result, content...) + return result +} + +// encodeNameAssoc encodes the index and data prefixed by their size. +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namemap +func encodeNameAssoc(na *wasm.NameAssoc) []byte { + return append(leb128.EncodeUint32(na.Index), encodeSizePrefixed([]byte(na.Name))...) +} + +// encodeSizePrefixed encodes the data prefixed by their size. +func encodeSizePrefixed(data []byte) []byte { + size := leb128.EncodeUint32(uint32(len(data))) + return append(size, data...) +} diff --git a/vendor/github.com/tetratelabs/wabin/binary/section.go b/vendor/github.com/tetratelabs/wabin/binary/section.go new file mode 100644 index 00000000000..a031632246d --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/binary/section.go @@ -0,0 +1,342 @@ +package binary + +import ( + "bytes" + "fmt" + "io" + + "github.com/tetratelabs/wabin/leb128" + "github.com/tetratelabs/wabin/wasm" +) + +func decodeTypeSection(features wasm.CoreFeatures, r *bytes.Reader) ([]*wasm.FunctionType, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get size of vector: %w", err) + } + + result := make([]*wasm.FunctionType, vs) + for i := uint32(0); i < vs; i++ { + if result[i], err = decodeFunctionType(features, r); err != nil { + return nil, fmt.Errorf("read %d-th type: %v", i, err) + } + } + return result, nil +} + +func decodeImportSection(r *bytes.Reader, features wasm.CoreFeatures) ([]*wasm.Import, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get size of vector: %w", err) + } + + result := make([]*wasm.Import, vs) + for i := uint32(0); i < vs; i++ { + if result[i], err = decodeImport(r, i, features); err != nil { + return nil, err + } + } + return result, nil +} + +func decodeFunctionSection(r *bytes.Reader) ([]uint32, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get size of vector: %w", err) + } + + result := make([]uint32, vs) + for i := uint32(0); i < vs; i++ { + if result[i], _, err = leb128.DecodeUint32(r); err != nil { + return nil, fmt.Errorf("get type index: %w", err) + } + } + return result, err +} + +func decodeTableSection(r *bytes.Reader, features wasm.CoreFeatures) ([]*wasm.Table, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("error reading size") + } + if vs > 1 { + if err := features.RequireEnabled(wasm.CoreFeatureReferenceTypes); err != nil { + return nil, fmt.Errorf("at most one table allowed in module as %w", err) + } + } + + ret := make([]*wasm.Table, vs) + for i := range ret { + table, err := decodeTable(r, features) + if err != nil { + return nil, err + } + ret[i] = table + } + return ret, nil +} + +func decodeMemorySection(r *bytes.Reader) (*wasm.Memory, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("error reading size") + } + if vs > 1 { + return nil, fmt.Errorf("at most one memory allowed in module, but read %d", vs) + } + + return decodeMemory(r) +} + +func decodeGlobalSection(r *bytes.Reader, features wasm.CoreFeatures) ([]*wasm.Global, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get size of vector: %w", err) + } + + result := make([]*wasm.Global, vs) + for i := uint32(0); i < vs; i++ { + if result[i], err = decodeGlobal(r, features); err != nil { + return nil, fmt.Errorf("global[%d]: %w", i, err) + } + } + return result, nil +} + +func decodeExportSection(r *bytes.Reader) ([]*wasm.Export, error) { + vs, _, sizeErr := leb128.DecodeUint32(r) + if sizeErr != nil { + return nil, fmt.Errorf("get size of vector: %v", sizeErr) + } + + usedName := make(map[string]struct{}, vs) + exportSection := make([]*wasm.Export, 0, vs) + for i := wasm.Index(0); i < vs; i++ { + export, err := decodeExport(r) + if err != nil { + return nil, fmt.Errorf("read export: %w", err) + } + if _, ok := usedName[export.Name]; ok { + return nil, fmt.Errorf("export[%d] duplicates name %q", i, export.Name) + } else { + usedName[export.Name] = struct{}{} + } + exportSection = append(exportSection, export) + } + return exportSection, nil +} + +func decodeStartSection(r *bytes.Reader) (*wasm.Index, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get function index: %w", err) + } + return &vs, nil +} + +func decodeElementSection(r *bytes.Reader, features wasm.CoreFeatures) ([]*wasm.ElementSegment, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get size of vector: %w", err) + } + + result := make([]*wasm.ElementSegment, vs) + for i := uint32(0); i < vs; i++ { + if result[i], err = decodeElementSegment(r, features); err != nil { + return nil, fmt.Errorf("read element: %w", err) + } + } + return result, nil +} + +func decodeCodeSection(r *bytes.Reader) ([]*wasm.Code, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get size of vector: %w", err) + } + + result := make([]*wasm.Code, vs) + for i := uint32(0); i < vs; i++ { + if result[i], err = decodeCode(r); err != nil { + return nil, fmt.Errorf("read %d-th code segment: %v", i, err) + } + } + return result, nil +} + +func decodeDataSection(r *bytes.Reader, features wasm.CoreFeatures) ([]*wasm.DataSegment, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get size of vector: %w", err) + } + + result := make([]*wasm.DataSegment, vs) + for i := uint32(0); i < vs; i++ { + if result[i], err = decodeDataSegment(r, features); err != nil { + return nil, fmt.Errorf("read data segment: %w", err) + } + } + return result, nil +} + +func decodeDataCountSection(r *bytes.Reader) (count *uint32, err error) { + v, _, err := leb128.DecodeUint32(r) + if err != nil && err != io.EOF { + // data count is optional, so EOF is fine. + return nil, err + } + return &v, nil +} + +// encodeSection encodes the sectionID, the size of its contents in bytes, +// followed by the contents. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0 +func encodeSection(sectionID wasm.SectionID, contents []byte) []byte { + return append([]byte{sectionID}, encodeSizePrefixed(contents)...) +} + +// encodeTypeSection encodes a wasm.SectionIDType for the given imports in +// WebAssembly Binary Format. +// +// See encodeFunctionType +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#type-section%E2%91%A0 +func encodeTypeSection(types []*wasm.FunctionType) []byte { + contents := leb128.EncodeUint32(uint32(len(types))) + for _, t := range types { + contents = append(contents, encodeFunctionType(t)...) + } + return encodeSection(wasm.SectionIDType, contents) +} + +// encodeImportSection encodes a wasm.SectionIDImport for the given imports in +// WebAssembly Binary Format. +// +// See encodeImport +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#import-section%E2%91%A0 +func encodeImportSection(imports []*wasm.Import) []byte { + contents := leb128.EncodeUint32(uint32(len(imports))) + for _, i := range imports { + contents = append(contents, encodeImport(i)...) + } + return encodeSection(wasm.SectionIDImport, contents) +} + +// encodeFunctionSection encodes a wasm.SectionIDFunction for the type indices +// associated with module-defined functions in WebAssembly Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-section%E2%91%A0 +func encodeFunctionSection(typeIndices []wasm.Index) []byte { + contents := leb128.EncodeUint32(uint32(len(typeIndices))) + for _, index := range typeIndices { + contents = append(contents, leb128.EncodeUint32(index)...) + } + return encodeSection(wasm.SectionIDFunction, contents) +} + +// encodeCodeSection encodes a wasm.SectionIDCode for the module-defined +// function in WebAssembly Binary Format. +// +// See encodeCode +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#code-section%E2%91%A0 +func encodeCodeSection(code []*wasm.Code) []byte { + contents := leb128.EncodeUint32(uint32(len(code))) + for _, i := range code { + contents = append(contents, encodeCode(i)...) + } + return encodeSection(wasm.SectionIDCode, contents) +} + +// encodeTableSection encodes a wasm.SectionIDTable for the module-defined +// function in WebAssembly Binary Format. +// +// See encodeTable +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#table-section%E2%91%A0 +func encodeTableSection(tables []*wasm.Table) []byte { + var contents = leb128.EncodeUint32(uint32(len(tables))) + for _, table := range tables { + contents = append(contents, encodeTable(table)...) + } + return encodeSection(wasm.SectionIDTable, contents) +} + +// encodeMemorySection encodes a wasm.SectionIDMemory for the module-defined +// function in WebAssembly Binary Format. +// +// See encodeMemory +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-section%E2%91%A0 +func encodeMemorySection(memory *wasm.Memory) []byte { + contents := append([]byte{1}, encodeMemory(memory)...) + return encodeSection(wasm.SectionIDMemory, contents) +} + +// encodeGlobalSection encodes a wasm.SectionIDGlobal for the given globals in +// WebAssembly Binary Format. +// +// See encodeGlobal +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-section%E2%91%A0 +func encodeGlobalSection(globals []*wasm.Global) []byte { + contents := leb128.EncodeUint32(uint32(len(globals))) + for _, g := range globals { + contents = append(contents, encodeGlobal(g)...) + } + return encodeSection(wasm.SectionIDGlobal, contents) +} + +// encodeExportSection encodes a wasm.SectionIDExport for the given exports in +// WebAssembly Binary Format. +// +// See encodeExport +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#export-section%E2%91%A0 +func encodeExportSection(exports []*wasm.Export) []byte { + contents := leb128.EncodeUint32(uint32(len(exports))) + for _, e := range exports { + contents = append(contents, encodeExport(e)...) + } + return encodeSection(wasm.SectionIDExport, contents) +} + +// encodeStartSection encodes a wasm.SectionIDStart for the given function +// index in WebAssembly Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#start-section%E2%91%A0 +func encodeStartSection(funcidx wasm.Index) []byte { + return encodeSection(wasm.SectionIDStart, leb128.EncodeUint32(funcidx)) +} + +// encodeElementSection encodes a wasm.SectionIDElement for the elements in +// WebAssembly Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#element-section%E2%91%A0 +func encodeElementSection(elements []*wasm.ElementSegment) []byte { + contents := leb128.EncodeUint32(uint32(len(elements))) + for _, e := range elements { + contents = append(contents, encodeElement(e)...) + } + return encodeSection(wasm.SectionIDElement, contents) +} + +// encodeDataSection encodes a wasm.SectionIDData for the data in WebAssembly 1.0 (20191205) +// Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#data-section%E2%91%A0 +func encodeDataSection(datum []*wasm.DataSegment) []byte { + contents := leb128.EncodeUint32(uint32(len(datum))) + for _, d := range datum { + contents = append(contents, encodeDataSegment(d)...) + } + return encodeSection(wasm.SectionIDData, contents) +} + +// encodeCustomSection encodes a wasm.SectionIDCustom for the data in WebAssembly 1.0 (20191205) +// Binary Format. This is used for custom sections that are **not** associated with the "name" key. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0 +func encodeCustomSection(c *wasm.CustomSection) (data []byte) { + data = make([]byte, 0, 1+len(c.Name)+len(c.Data)) + l := byte(len(c.Name)) + data = append(data, l) + data = append(data, []byte(c.Name)...) + data = append(data, c.Data...) + return +} diff --git a/vendor/github.com/tetratelabs/wabin/binary/table.go b/vendor/github.com/tetratelabs/wabin/binary/table.go new file mode 100644 index 00000000000..a1b5fb34e42 --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/binary/table.go @@ -0,0 +1,45 @@ +package binary + +import ( + "bytes" + "fmt" + + "github.com/tetratelabs/wabin/wasm" +) + +// decodeTable returns the wasm.Table decoded with the WebAssembly Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-table +func decodeTable(r *bytes.Reader, features wasm.CoreFeatures) (*wasm.Table, error) { + tableType, err := r.ReadByte() + if err != nil { + return nil, fmt.Errorf("read leading byte: %v", err) + } + + if tableType != wasm.RefTypeFuncref { + if err := features.RequireEnabled(wasm.CoreFeatureReferenceTypes); err != nil { + return nil, fmt.Errorf("table type funcref is invalid: %w", err) + } + } + + min, max, err := decodeLimitsType(r) + if err != nil { + return nil, fmt.Errorf("read limits: %v", err) + } + if min > wasm.MaximumFunctionIndex { + return nil, fmt.Errorf("table min must be at most %d", wasm.MaximumFunctionIndex) + } + if max != nil { + if *max < min { + return nil, fmt.Errorf("table size minimum must not be greater than maximum") + } + } + return &wasm.Table{Min: min, Max: max, Type: tableType}, nil +} + +// encodeTable returns the wasm.Table encoded in WebAssembly Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-table +func encodeTable(i *wasm.Table) []byte { + return append([]byte{i.Type}, encodeLimitsType(i.Min, i.Max)...) +} diff --git a/vendor/github.com/tetratelabs/wabin/binary/value.go b/vendor/github.com/tetratelabs/wabin/binary/value.go new file mode 100644 index 00000000000..7d6913ce56f --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/binary/value.go @@ -0,0 +1,89 @@ +package binary + +import ( + "bytes" + "fmt" + "io" + "unicode/utf8" + + "github.com/tetratelabs/wabin/leb128" + "github.com/tetratelabs/wabin/wasm" +) + +var noValType = []byte{0} + +// encodedValTypes is a cache of size prefixed binary encoding of known val types. +var encodedValTypes = map[wasm.ValueType][]byte{ + wasm.ValueTypeI32: {1, wasm.ValueTypeI32}, + wasm.ValueTypeI64: {1, wasm.ValueTypeI64}, + wasm.ValueTypeF32: {1, wasm.ValueTypeF32}, + wasm.ValueTypeF64: {1, wasm.ValueTypeF64}, + wasm.ValueTypeExternref: {1, wasm.ValueTypeExternref}, + wasm.ValueTypeFuncref: {1, wasm.ValueTypeFuncref}, + wasm.ValueTypeV128: {1, wasm.ValueTypeV128}, +} + +// encodeValTypes fast paths binary encoding of common value type lengths +func encodeValTypes(vt []wasm.ValueType) []byte { + // Special case nullary and parameter lengths of wasi_snapshot_preview1 to avoid excess allocations + switch uint32(len(vt)) { + case 0: // nullary + return noValType + case 1: // ex $wasi.fd_close or any result + if encoded, ok := encodedValTypes[vt[0]]; ok { + return encoded + } + case 2: // ex $wasi.environ_sizes_get + return []byte{2, vt[0], vt[1]} + case 4: // ex $wasi.fd_write + return []byte{4, vt[0], vt[1], vt[2], vt[3]} + case 9: // ex $wasi.fd_write + return []byte{9, vt[0], vt[1], vt[2], vt[3], vt[4], vt[5], vt[6], vt[7], vt[8]} + } + // Slow path others until someone complains with a valid signature + count := leb128.EncodeUint32(uint32(len(vt))) + return append(count, vt...) +} + +func decodeValueTypes(r *bytes.Reader, num uint32) ([]wasm.ValueType, error) { + if num == 0 { + return nil, nil + } + ret := make([]wasm.ValueType, num) + buf := make([]wasm.ValueType, num) + _, err := io.ReadFull(r, buf) + if err != nil { + return nil, err + } + + for i, v := range buf { + switch v { + case wasm.ValueTypeI32, wasm.ValueTypeF32, wasm.ValueTypeI64, wasm.ValueTypeF64, + wasm.ValueTypeExternref, wasm.ValueTypeFuncref, wasm.ValueTypeV128: + ret[i] = v + default: + return nil, fmt.Errorf("invalid value type: %d", v) + } + } + return ret, nil +} + +// decodeUTF8 decodes a size prefixed string from the reader, returning it and the count of bytes read. +// contextFormat and contextArgs apply an error format when present +func decodeUTF8(r *bytes.Reader, contextFormat string, contextArgs ...interface{}) (string, uint32, error) { + size, sizeOfSize, err := leb128.DecodeUint32(r) + if err != nil { + return "", 0, fmt.Errorf("failed to read %s size: %w", fmt.Sprintf(contextFormat, contextArgs...), err) + } + + buf := make([]byte, size) + if _, err = io.ReadFull(r, buf); err != nil { + return "", 0, fmt.Errorf("failed to read %s: %w", fmt.Sprintf(contextFormat, contextArgs...), err) + } + + if !utf8.Valid(buf) { + return "", 0, fmt.Errorf("%s is not valid UTF-8", fmt.Sprintf(contextFormat, contextArgs...)) + } + + return string(buf), size + uint32(sizeOfSize), nil +} diff --git a/vendor/github.com/tetratelabs/wabin/ieee754/ieee754.go b/vendor/github.com/tetratelabs/wabin/ieee754/ieee754.go new file mode 100644 index 00000000000..5968a6a171f --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/ieee754/ieee754.go @@ -0,0 +1,31 @@ +package ieee754 + +import ( + "encoding/binary" + "io" + "math" +) + +// DecodeFloat32 decodes a float32 in IEEE 754 binary representation. +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#floating-point%E2%91%A2 +func DecodeFloat32(r io.Reader) (float32, error) { + buf := make([]byte, 4) + _, err := io.ReadFull(r, buf) + if err != nil { + return 0, err + } + raw := binary.LittleEndian.Uint32(buf) + return math.Float32frombits(raw), nil +} + +// DecodeFloat64 decodes a float64 in IEEE 754 binary representation. +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#floating-point%E2%91%A2 +func DecodeFloat64(r io.Reader) (float64, error) { + buf := make([]byte, 8) + _, err := io.ReadFull(r, buf) + if err != nil { + return 0, err + } + raw := binary.LittleEndian.Uint64(buf) + return math.Float64frombits(raw), nil +} diff --git a/vendor/github.com/tetratelabs/wabin/leb128/leb128.go b/vendor/github.com/tetratelabs/wabin/leb128/leb128.go new file mode 100644 index 00000000000..1a493535d61 --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/leb128/leb128.go @@ -0,0 +1,240 @@ +package leb128 + +import ( + "bytes" + "errors" + "fmt" +) + +const ( + maxVarintLen32 = 5 + maxVarintLen64 = 10 +) + +var ( + errOverflow32 = errors.New("overflows a 32-bit integer") + errOverflow33 = errors.New("overflows a 33-bit integer") + errOverflow64 = errors.New("overflows a 64-bit integer") +) + +// EncodeInt32 encodes the signed value into a buffer in LEB128 format +// +// See https://en.wikipedia.org/wiki/LEB128#Encode_signed_integer +func EncodeInt32(value int32) []byte { + return EncodeInt64(int64(value)) +} + +// EncodeInt64 encodes the signed value into a buffer in LEB128 format +// +// See https://en.wikipedia.org/wiki/LEB128#Encode_signed_integer +func EncodeInt64(value int64) (buf []byte) { + for { + // Take 7 remaining low-order bits from the value into b. + b := uint8(value & 0x7f) + // Extract the sign bit. + s := uint8(value & 0x40) + value >>= 7 + + // The encoding unsigned numbers is simpler as it only needs to check if the value is non-zero to tell if there + // are more bits to encode. Signed is a little more complicated as you have to double-check the sign bit. + // If either case, set the high-order bit to tell the reader there are more bytes in this int. + if (value != -1 || s == 0) && (value != 0 || s != 0) { + b |= 0x80 + } + + // Append b into the buffer + buf = append(buf, b) + if b&0x80 == 0 { + break + } + } + return buf +} + +// EncodeUint32 encodes the value into a buffer in LEB128 format +// +// See https://en.wikipedia.org/wiki/LEB128#Encode_unsigned_integer +func EncodeUint32(value uint32) []byte { + return EncodeUint64(uint64(value)) +} + +// EncodeUint64 encodes the value into a buffer in LEB128 format +// +// See https://en.wikipedia.org/wiki/LEB128#Encode_unsigned_integer +func EncodeUint64(value uint64) (buf []byte) { + // This is effectively a do/while loop where we take 7 bits of the value and encode them until it is zero. + for { + // Take 7 remaining low-order bits from the value into b. + b := uint8(value & 0x7f) + value = value >> 7 + + // If there are remaining bits, the value won't be zero: Set the high + // order bit to tell the reader there are more bytes in this uint. + if value != 0 { + b |= 0x80 + } + + // Append b into the buffer + buf = append(buf, b) + if b&0x80 == 0 { + return buf + } + } +} + +func DecodeUint32(r *bytes.Reader) (ret uint32, bytesRead uint64, err error) { + // Derived from https://github.com/golang/go/blob/aafad20b617ee63d58fcd4f6e0d98fe27760678c/src/encoding/binary/varint.go + // with the modification on the overflow handling tailored for 32-bits. + var s uint32 + for i := 0; i < maxVarintLen32; i++ { + b, err := r.ReadByte() + if err != nil { + return 0, 0, err + } + if b < 0x80 { + // Unused bits must be all zero. + if i == maxVarintLen32-1 && (b&0xf0) > 0 { + return 0, 0, errOverflow32 + } + return ret | uint32(b)< 1 { + return 0, 0, errOverflow64 + } + return ret | uint64(b)< 5 { + return 0, 0, errOverflow32 + } else if unused := b & 0b00110000; bytesRead == 5 && ret < 0 && unused != 0b00110000 { + return 0, 0, errOverflow32 + } else if bytesRead == 5 && ret >= 0 && unused != 0x00 { + return 0, 0, errOverflow32 + } + return + } + } +} + +// DecodeInt33AsInt64 is a special cased decoder for wasm.BlockType which is encoded as a positive signed integer, yet +// still needs to fit the 32-bit range of allowed indices. Hence, this is 33, not 32-bit! +// +// See https://webassembly.github.io/spec/core/binary/instructions.html#control-instructions +func DecodeInt33AsInt64(r *bytes.Reader) (ret int64, bytesRead uint64, err error) { + const ( + int33Mask int64 = 1 << 7 + int33Mask2 = ^int33Mask + int33Mask3 = 1 << 6 + int33Mask4 = 8589934591 // 2^33-1 + int33Mask5 = 1 << 32 + int33Mask6 = int33Mask4 + 1 // 2^33 + ) + var shift int + var b int64 + var rb byte + for shift < 35 { + rb, err = r.ReadByte() + if err != nil { + return 0, 0, fmt.Errorf("readByte failed: %w", err) + } + b = int64(rb) + ret |= (b & int33Mask2) << shift + shift += 7 + bytesRead++ + if b&int33Mask == 0 { + break + } + } + + // fixme: can be optimized + if shift < 33 && (b&int33Mask3) == int33Mask3 { + ret |= int33Mask4 << shift + } + ret = ret & int33Mask4 + + // if 33rd bit == 1, we translate it as a corresponding signed-33bit minus value + if ret&int33Mask5 > 0 { + ret = ret - int33Mask6 + } + // Over flow checks. + // fixme: can be optimized. + if bytesRead > 5 { + return 0, 0, errOverflow33 + } else if unused := b & 0b00100000; bytesRead == 5 && ret < 0 && unused != 0b00100000 { + return 0, 0, errOverflow33 + } else if bytesRead == 5 && ret >= 0 && unused != 0x00 { + return 0, 0, errOverflow33 + } + return ret, bytesRead, nil +} + +func DecodeInt64(r *bytes.Reader) (ret int64, bytesRead uint64, err error) { + const ( + int64Mask3 = 1 << 6 + int64Mask4 = ^0 + ) + var shift int + var b byte + for { + b, err = r.ReadByte() + if err != nil { + return 0, 0, fmt.Errorf("readByte failed: %w", err) + } + ret |= (int64(b) & 0x7f) << shift + shift += 7 + bytesRead++ + if b&0x80 == 0 { + if shift < 64 && (b&int64Mask3) == int64Mask3 { + ret |= int64Mask4 << shift + } + // Over flow checks. + // fixme: can be optimized. + if bytesRead > 10 { + return 0, 0, errOverflow64 + } else if unused := b & 0b00111110; bytesRead == 10 && ret < 0 && unused != 0b00111110 { + return 0, 0, errOverflow64 + } else if bytesRead == 10 && ret >= 0 && unused != 0x00 { + return 0, 0, errOverflow64 + } + return + } + } +} diff --git a/vendor/github.com/tetratelabs/wabin/wasm/counts.go b/vendor/github.com/tetratelabs/wabin/wasm/counts.go new file mode 100644 index 00000000000..89bd5f5bf89 --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/wasm/counts.go @@ -0,0 +1,86 @@ +package wasm + +import "fmt" + +// ImportFuncCount returns the possibly empty count of imported functions. This plus SectionElementCount of +// SectionIDFunction is the size of the function index namespace. +func (m *Module) ImportFuncCount() uint32 { + return m.importCount(ExternTypeFunc) +} + +// ImportTableCount returns the possibly empty count of imported tables. This plus SectionElementCount of SectionIDTable +// is the size of the table index namespace. +func (m *Module) ImportTableCount() uint32 { + return m.importCount(ExternTypeTable) +} + +// ImportMemoryCount returns the possibly empty count of imported memories. This plus SectionElementCount of +// SectionIDMemory is the size of the memory index namespace. +func (m *Module) ImportMemoryCount() uint32 { + return m.importCount(ExternTypeMemory) // TODO: once validation happens on decode, this is zero or one. +} + +// ImportGlobalCount returns the possibly empty count of imported globals. This plus SectionElementCount of +// SectionIDGlobal is the size of the global index namespace. +func (m *Module) ImportGlobalCount() uint32 { + return m.importCount(ExternTypeGlobal) +} + +// importCount returns the count of a specific type of import. This is important because it is easy to mistake the +// length of the import section with the count of a specific kind of import. +func (m *Module) importCount(et ExternType) (res uint32) { + for _, im := range m.ImportSection { + if im.Type == et { + res++ + } + } + return +} + +// SectionElementCount returns the count of elements in a given section ID +// +// For example... +// * SectionIDType returns the count of FunctionType +// * SectionIDCustom returns one if the NameSection is present +// * SectionIDHostFunction returns the count of HostFunctionSection +// * SectionIDExport returns the count of unique export names +func (m *Module) SectionElementCount(sectionID SectionID) uint32 { // element as in vector elements! + switch sectionID { + case SectionIDCustom: + numCustomSections := uint32(len(m.CustomSections)) + if m.NameSection != nil { + numCustomSections++ + } + return numCustomSections + case SectionIDType: + return uint32(len(m.TypeSection)) + case SectionIDImport: + return uint32(len(m.ImportSection)) + case SectionIDFunction: + return uint32(len(m.FunctionSection)) + case SectionIDTable: + return uint32(len(m.TableSection)) + case SectionIDMemory: + if m.MemorySection != nil { + return 1 + } + return 0 + case SectionIDGlobal: + return uint32(len(m.GlobalSection)) + case SectionIDExport: + return uint32(len(m.ExportSection)) + case SectionIDStart: + if m.StartSection != nil { + return 1 + } + return 0 + case SectionIDElement: + return uint32(len(m.ElementSection)) + case SectionIDCode: + return uint32(len(m.CodeSection)) + case SectionIDData: + return uint32(len(m.DataSection)) + default: + panic(fmt.Errorf("BUG: unknown section: %d", sectionID)) + } +} diff --git a/vendor/github.com/tetratelabs/wabin/wasm/features.go b/vendor/github.com/tetratelabs/wabin/wasm/features.go new file mode 100644 index 00000000000..65d98091067 --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/wasm/features.go @@ -0,0 +1,212 @@ +package wasm + +import ( + "fmt" + "strings" +) + +// CoreFeatures is a bit flag of WebAssembly Core specification features. See +// https://github.com/WebAssembly/proposals for proposals and their status. +// +// Constants define individual features, such as CoreFeatureMultiValue, or +// groups of "finished" features, assigned to a WebAssembly Core Specification +// version, ex. CoreFeaturesV1 or CoreFeaturesV2. +// +// Note: Numeric values are not intended to be interpreted except as bit flags. +type CoreFeatures uint64 + +// CoreFeaturesV1 are features included in the WebAssembly Core Specification +// 1.0. As of late 2022, this is the only version that is a Web Standard (W3C +// Recommendation). +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/ +const CoreFeaturesV1 = CoreFeatureMutableGlobal + +// CoreFeaturesV2 are features included in the WebAssembly Core Specification +// 2.0 (20220419). As of late 2022, version 2.0 is a W3C working draft, not yet +// a Web Standard (W3C Recommendation). +// +// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#release-1-1 +const CoreFeaturesV2 = CoreFeaturesV1 | + CoreFeatureBulkMemoryOperations | + CoreFeatureMultiValue | + CoreFeatureNonTrappingFloatToIntConversion | + CoreFeatureReferenceTypes | + CoreFeatureSignExtensionOps | + CoreFeatureSIMD + +const ( + // CoreFeatureBulkMemoryOperations adds instructions modify ranges of + // memory or table entries ("bulk-memory-operations"). This is included in + // CoreFeaturesV2, but not CoreFeaturesV1. + // + // Here are the notable effects: + // - Adds `memory.fill`, `memory.init`, `memory.copy` and `data.drop` + // instructions. + // - Adds `table.init`, `table.copy` and `elem.drop` instructions. + // - Introduces a "passive" form of element and data segments. + // - Stops checking "active" element and data segment boundaries at + // compile-time, meaning they can error at runtime. + // + // Note: "bulk-memory-operations" is mixed with the "reference-types" + // proposal due to the WebAssembly Working Group merging them + // "mutually dependent". Therefore, enabling this feature requires enabling + // CoreFeatureReferenceTypes, and vice-versa. + // + // See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/bulk-memory-operations/Overview.md + // https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/reference-types/Overview.md and + // https://github.com/WebAssembly/spec/pull/1287 + CoreFeatureBulkMemoryOperations CoreFeatures = 1 << iota + + // CoreFeatureMultiValue enables multiple values ("multi-value"). This is + // included in CoreFeaturesV2, but not CoreFeaturesV1. + // + // Here are the notable effects: + // - Function (`func`) types allow more than one result. + // - Block types (`block`, `loop` and `if`) can be arbitrary function + // types. + // + // See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/multi-value/Overview.md + CoreFeatureMultiValue + + // CoreFeatureMutableGlobal allows globals to be mutable. This is included + // in both CoreFeaturesV1 and CoreFeaturesV2. + // + // When false, an api.Global can never be cast to an api.MutableGlobal, and + // any wasm that includes global vars will fail to parse. + CoreFeatureMutableGlobal + + // CoreFeatureNonTrappingFloatToIntConversion enables non-trapping + // float-to-int conversions ("nontrapping-float-to-int-conversion"). This + // is included in CoreFeaturesV2, but not CoreFeaturesV1. + // + // The only effect of enabling is allowing the following instructions, + // which return 0 on NaN instead of panicking. + // - `i32.trunc_sat_f32_s` + // - `i32.trunc_sat_f32_u` + // - `i32.trunc_sat_f64_s` + // - `i32.trunc_sat_f64_u` + // - `i64.trunc_sat_f32_s` + // - `i64.trunc_sat_f32_u` + // - `i64.trunc_sat_f64_s` + // - `i64.trunc_sat_f64_u` + // + // See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/nontrapping-float-to-int-conversion/Overview.md + CoreFeatureNonTrappingFloatToIntConversion + + // CoreFeatureReferenceTypes enables various instructions and features + // related to table and new reference types. This is included in + // CoreFeaturesV2, but not CoreFeaturesV1. + // + // - Introduction of new value types: `funcref` and `externref`. + // - Support for the following new instructions: + // - `ref.null` + // - `ref.func` + // - `ref.is_null` + // - `table.fill` + // - `table.get` + // - `table.grow` + // - `table.set` + // - `table.size` + // - Support for multiple tables per module: + // - `call_indirect`, `table.init`, `table.copy` and `elem.drop` + // - Support for instructions can take non-zero table index. + // - Element segments can take non-zero table index. + // + // Note: "reference-types" is mixed with the "bulk-memory-operations" + // proposal due to the WebAssembly Working Group merging them + // "mutually dependent". Therefore, enabling this feature requires enabling + // CoreFeatureBulkMemoryOperations, and vice-versa. + // + // See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/bulk-memory-operations/Overview.md + // https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/reference-types/Overview.md and + // https://github.com/WebAssembly/spec/pull/1287 + CoreFeatureReferenceTypes + + // CoreFeatureSignExtensionOps enables sign extension instructions + // ("sign-extension-ops"). This is included in CoreFeaturesV2, but not + // CoreFeaturesV1. + // + // Adds instructions: + // - `i32.extend8_s` + // - `i32.extend16_s` + // - `i64.extend8_s` + // - `i64.extend16_s` + // - `i64.extend32_s` + // + // See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/sign-extension-ops/Overview.md + CoreFeatureSignExtensionOps + + // CoreFeatureSIMD enables the vector value type and vector instructions + // (aka SIMD). This is included in CoreFeaturesV2, but not CoreFeaturesV1. + // + // Note: The instruction list is too long to enumerate in godoc. + // See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md + CoreFeatureSIMD +) + +// SetEnabled enables or disables the feature or group of features. +func (f CoreFeatures) SetEnabled(feature CoreFeatures, val bool) CoreFeatures { + if val { + return f | feature + } + return f &^ feature +} + +// IsEnabled returns true if the feature (or group of features) is enabled. +func (f CoreFeatures) IsEnabled(feature CoreFeatures) bool { + return f&feature != 0 +} + +// RequireEnabled returns an error if the feature (or group of features) is not +// enabled. +func (f CoreFeatures) RequireEnabled(feature CoreFeatures) error { + if f&feature == 0 { + return fmt.Errorf("feature %q is disabled", feature) + } + return nil +} + +// String implements fmt.Stringer by returning each enabled feature. +func (f CoreFeatures) String() string { + var builder strings.Builder + for i := 0; i <= 63; i++ { // cycle through all bits to reduce code and maintenance + target := CoreFeatures(1 << i) + if f.IsEnabled(target) { + if name := featureName(target); name != "" { + if builder.Len() > 0 { + builder.WriteByte('|') + } + builder.WriteString(name) + } + } + } + return builder.String() +} + +func featureName(f CoreFeatures) string { + switch f { + case CoreFeatureMutableGlobal: + // match https://github.com/WebAssembly/mutable-global + return "mutable-global" + case CoreFeatureSignExtensionOps: + // match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/sign-extension-ops/Overview.md + return "sign-extension-ops" + case CoreFeatureMultiValue: + // match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/multi-value/Overview.md + return "multi-value" + case CoreFeatureNonTrappingFloatToIntConversion: + // match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/nontrapping-float-to-int-conversion/Overview.md + return "nontrapping-float-to-int-conversion" + case CoreFeatureBulkMemoryOperations: + // match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/bulk-memory-operations/Overview.md + return "bulk-memory-operations" + case CoreFeatureReferenceTypes: + // match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/reference-types/Overview.md + return "reference-types" + case CoreFeatureSIMD: + // match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md + return "simd" + } + return "" +} diff --git a/vendor/github.com/tetratelabs/wabin/wasm/instruction.go b/vendor/github.com/tetratelabs/wabin/wasm/instruction.go new file mode 100644 index 00000000000..e92fc7060c7 --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/wasm/instruction.go @@ -0,0 +1,1550 @@ +package wasm + +// Opcode is the binary Opcode of an instruction. See also InstructionName +type Opcode = byte + +const ( + // OpcodeUnreachable causes an unconditional trap. + OpcodeUnreachable Opcode = 0x00 + // OpcodeNop does nothing + OpcodeNop Opcode = 0x01 + // OpcodeBlock brackets a sequence of instructions. A branch instruction on an if label breaks out to after its + // OpcodeEnd. + OpcodeBlock Opcode = 0x02 + // OpcodeLoop brackets a sequence of instructions. A branch instruction on a loop label will jump back to the + // beginning of its block. + OpcodeLoop Opcode = 0x03 + // OpcodeIf brackets a sequence of instructions. When the top of the stack evaluates to 1, the block is executed. + // Zero jumps to the optional OpcodeElse. A branch instruction on an if label breaks out to after its OpcodeEnd. + OpcodeIf Opcode = 0x04 + // OpcodeElse brackets a sequence of instructions enclosed by an OpcodeIf. A branch instruction on a then label + // breaks out to after the OpcodeEnd on the enclosing OpcodeIf. + OpcodeElse Opcode = 0x05 + // OpcodeEnd terminates a control instruction OpcodeBlock, OpcodeLoop or OpcodeIf. + OpcodeEnd Opcode = 0x0b + + // OpcodeBr is a stack-polymorphic opcode that performs an unconditional branch. How the stack is modified depends + // on whether the "br" is enclosed by a loop, and if FeatureMultiValue is enabled. + // + // Here are the rules in pseudocode about how the stack is modified based on the "br" operand L (label): + // if L is loop: append(L.originalStackWithoutInputs, N-values popped from the stack) where N == L.inputs + // else: append(L.originalStackWithoutInputs, N-values popped from the stack) where N == L.results + // + // In WebAssembly 1.0 (20191205), N can be zero or one. When FeatureMultiValue is enabled, N can be more than one, + // depending on the type use of the label L. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#-hrefsyntax-instr-controlmathsfbrl + OpcodeBr Opcode = 0x0c + // ^^ TODO: Add a diagram to help explain br l means that branch into AFTER l for non-loop labels + + OpcodeBrIf Opcode = 0x0d + OpcodeBrTable Opcode = 0x0e + OpcodeReturn Opcode = 0x0f + OpcodeCall Opcode = 0x10 + OpcodeCallIndirect Opcode = 0x11 + + // parametric instructions + + OpcodeDrop Opcode = 0x1a + OpcodeSelect Opcode = 0x1b + OpcodeTypedSelect Opcode = 0x1c + + // variable instructions + + OpcodeLocalGet Opcode = 0x20 + OpcodeLocalSet Opcode = 0x21 + OpcodeLocalTee Opcode = 0x22 + OpcodeGlobalGet Opcode = 0x23 + OpcodeGlobalSet Opcode = 0x24 + + // Below are toggled with FeatureReferenceTypes + + OpcodeTableGet Opcode = 0x25 + OpcodeTableSet Opcode = 0x26 + + // memory instructions + + OpcodeI32Load Opcode = 0x28 + OpcodeI64Load Opcode = 0x29 + OpcodeF32Load Opcode = 0x2a + OpcodeF64Load Opcode = 0x2b + OpcodeI32Load8S Opcode = 0x2c + OpcodeI32Load8U Opcode = 0x2d + OpcodeI32Load16S Opcode = 0x2e + OpcodeI32Load16U Opcode = 0x2f + OpcodeI64Load8S Opcode = 0x30 + OpcodeI64Load8U Opcode = 0x31 + OpcodeI64Load16S Opcode = 0x32 + OpcodeI64Load16U Opcode = 0x33 + OpcodeI64Load32S Opcode = 0x34 + OpcodeI64Load32U Opcode = 0x35 + OpcodeI32Store Opcode = 0x36 + OpcodeI64Store Opcode = 0x37 + OpcodeF32Store Opcode = 0x38 + OpcodeF64Store Opcode = 0x39 + OpcodeI32Store8 Opcode = 0x3a + OpcodeI32Store16 Opcode = 0x3b + OpcodeI64Store8 Opcode = 0x3c + OpcodeI64Store16 Opcode = 0x3d + OpcodeI64Store32 Opcode = 0x3e + OpcodeMemorySize Opcode = 0x3f + OpcodeMemoryGrow Opcode = 0x40 + + // const instructions + + OpcodeI32Const Opcode = 0x41 + OpcodeI64Const Opcode = 0x42 + OpcodeF32Const Opcode = 0x43 + OpcodeF64Const Opcode = 0x44 + + // numeric instructions + + OpcodeI32Eqz Opcode = 0x45 + OpcodeI32Eq Opcode = 0x46 + OpcodeI32Ne Opcode = 0x47 + OpcodeI32LtS Opcode = 0x48 + OpcodeI32LtU Opcode = 0x49 + OpcodeI32GtS Opcode = 0x4a + OpcodeI32GtU Opcode = 0x4b + OpcodeI32LeS Opcode = 0x4c + OpcodeI32LeU Opcode = 0x4d + OpcodeI32GeS Opcode = 0x4e + OpcodeI32GeU Opcode = 0x4f + + OpcodeI64Eqz Opcode = 0x50 + OpcodeI64Eq Opcode = 0x51 + OpcodeI64Ne Opcode = 0x52 + OpcodeI64LtS Opcode = 0x53 + OpcodeI64LtU Opcode = 0x54 + OpcodeI64GtS Opcode = 0x55 + OpcodeI64GtU Opcode = 0x56 + OpcodeI64LeS Opcode = 0x57 + OpcodeI64LeU Opcode = 0x58 + OpcodeI64GeS Opcode = 0x59 + OpcodeI64GeU Opcode = 0x5a + + OpcodeF32Eq Opcode = 0x5b + OpcodeF32Ne Opcode = 0x5c + OpcodeF32Lt Opcode = 0x5d + OpcodeF32Gt Opcode = 0x5e + OpcodeF32Le Opcode = 0x5f + OpcodeF32Ge Opcode = 0x60 + + OpcodeF64Eq Opcode = 0x61 + OpcodeF64Ne Opcode = 0x62 + OpcodeF64Lt Opcode = 0x63 + OpcodeF64Gt Opcode = 0x64 + OpcodeF64Le Opcode = 0x65 + OpcodeF64Ge Opcode = 0x66 + + OpcodeI32Clz Opcode = 0x67 + OpcodeI32Ctz Opcode = 0x68 + OpcodeI32Popcnt Opcode = 0x69 + OpcodeI32Add Opcode = 0x6a + OpcodeI32Sub Opcode = 0x6b + OpcodeI32Mul Opcode = 0x6c + OpcodeI32DivS Opcode = 0x6d + OpcodeI32DivU Opcode = 0x6e + OpcodeI32RemS Opcode = 0x6f + OpcodeI32RemU Opcode = 0x70 + OpcodeI32And Opcode = 0x71 + OpcodeI32Or Opcode = 0x72 + OpcodeI32Xor Opcode = 0x73 + OpcodeI32Shl Opcode = 0x74 + OpcodeI32ShrS Opcode = 0x75 + OpcodeI32ShrU Opcode = 0x76 + OpcodeI32Rotl Opcode = 0x77 + OpcodeI32Rotr Opcode = 0x78 + + OpcodeI64Clz Opcode = 0x79 + OpcodeI64Ctz Opcode = 0x7a + OpcodeI64Popcnt Opcode = 0x7b + OpcodeI64Add Opcode = 0x7c + OpcodeI64Sub Opcode = 0x7d + OpcodeI64Mul Opcode = 0x7e + OpcodeI64DivS Opcode = 0x7f + OpcodeI64DivU Opcode = 0x80 + OpcodeI64RemS Opcode = 0x81 + OpcodeI64RemU Opcode = 0x82 + OpcodeI64And Opcode = 0x83 + OpcodeI64Or Opcode = 0x84 + OpcodeI64Xor Opcode = 0x85 + OpcodeI64Shl Opcode = 0x86 + OpcodeI64ShrS Opcode = 0x87 + OpcodeI64ShrU Opcode = 0x88 + OpcodeI64Rotl Opcode = 0x89 + OpcodeI64Rotr Opcode = 0x8a + + OpcodeF32Abs Opcode = 0x8b + OpcodeF32Neg Opcode = 0x8c + OpcodeF32Ceil Opcode = 0x8d + OpcodeF32Floor Opcode = 0x8e + OpcodeF32Trunc Opcode = 0x8f + OpcodeF32Nearest Opcode = 0x90 + OpcodeF32Sqrt Opcode = 0x91 + OpcodeF32Add Opcode = 0x92 + OpcodeF32Sub Opcode = 0x93 + OpcodeF32Mul Opcode = 0x94 + OpcodeF32Div Opcode = 0x95 + OpcodeF32Min Opcode = 0x96 + OpcodeF32Max Opcode = 0x97 + OpcodeF32Copysign Opcode = 0x98 + + OpcodeF64Abs Opcode = 0x99 + OpcodeF64Neg Opcode = 0x9a + OpcodeF64Ceil Opcode = 0x9b + OpcodeF64Floor Opcode = 0x9c + OpcodeF64Trunc Opcode = 0x9d + OpcodeF64Nearest Opcode = 0x9e + OpcodeF64Sqrt Opcode = 0x9f + OpcodeF64Add Opcode = 0xa0 + OpcodeF64Sub Opcode = 0xa1 + OpcodeF64Mul Opcode = 0xa2 + OpcodeF64Div Opcode = 0xa3 + OpcodeF64Min Opcode = 0xa4 + OpcodeF64Max Opcode = 0xa5 + OpcodeF64Copysign Opcode = 0xa6 + + OpcodeI32WrapI64 Opcode = 0xa7 + OpcodeI32TruncF32S Opcode = 0xa8 + OpcodeI32TruncF32U Opcode = 0xa9 + OpcodeI32TruncF64S Opcode = 0xaa + OpcodeI32TruncF64U Opcode = 0xab + + OpcodeI64ExtendI32S Opcode = 0xac + OpcodeI64ExtendI32U Opcode = 0xad + OpcodeI64TruncF32S Opcode = 0xae + OpcodeI64TruncF32U Opcode = 0xaf + OpcodeI64TruncF64S Opcode = 0xb0 + OpcodeI64TruncF64U Opcode = 0xb1 + + OpcodeF32ConvertI32S Opcode = 0xb2 + OpcodeF32ConvertI32U Opcode = 0xb3 + OpcodeF32ConvertI64S Opcode = 0xb4 + OpcodeF32ConvertI64U Opcode = 0xb5 + OpcodeF32DemoteF64 Opcode = 0xb6 + + OpcodeF64ConvertI32S Opcode = 0xb7 + OpcodeF64ConvertI32U Opcode = 0xb8 + OpcodeF64ConvertI64S Opcode = 0xb9 + OpcodeF64ConvertI64U Opcode = 0xba + OpcodeF64PromoteF32 Opcode = 0xbb + + OpcodeI32ReinterpretF32 Opcode = 0xbc + OpcodeI64ReinterpretF64 Opcode = 0xbd + OpcodeF32ReinterpretI32 Opcode = 0xbe + OpcodeF64ReinterpretI64 Opcode = 0xbf + + // OpcodeRefNull pushes a null reference value whose type is specified by immediate to this opcode. + // This is defined in the reference-types proposal, but necessary for FeatureBulkMemoryOperations as well. + // + // Currently only supported in the constant expression in element segments. + OpcodeRefNull = 0xd0 + // OpcodeRefIsNull pops a reference value, and pushes 1 if it is null, 0 otherwise. + // This is defined in the reference-types proposal, but necessary for FeatureBulkMemoryOperations as well. + // + // Currently not supported. + OpcodeRefIsNull = 0xd1 + // OpcodeRefFunc pushes a funcref value whose index equals the immediate to this opcode. + // This is defined in the reference-types proposal, but necessary for FeatureBulkMemoryOperations as well. + // + // Currently, this is only supported in the constant expression in element segments. + OpcodeRefFunc = 0xd2 + + // Below are toggled with FeatureSignExtensionOps + + // OpcodeI32Extend8S extends a signed 8-bit integer to a 32-bit integer. + // Note: This is dependent on the flag FeatureSignExtensionOps + OpcodeI32Extend8S Opcode = 0xc0 + + // OpcodeI32Extend16S extends a signed 16-bit integer to a 32-bit integer. + // Note: This is dependent on the flag FeatureSignExtensionOps + OpcodeI32Extend16S Opcode = 0xc1 + + // OpcodeI64Extend8S extends a signed 8-bit integer to a 64-bit integer. + // Note: This is dependent on the flag FeatureSignExtensionOps + OpcodeI64Extend8S Opcode = 0xc2 + + // OpcodeI64Extend16S extends a signed 16-bit integer to a 64-bit integer. + // Note: This is dependent on the flag FeatureSignExtensionOps + OpcodeI64Extend16S Opcode = 0xc3 + + // OpcodeI64Extend32S extends a signed 32-bit integer to a 64-bit integer. + // Note: This is dependent on the flag FeatureSignExtensionOps + OpcodeI64Extend32S Opcode = 0xc4 + + // OpcodeMiscPrefix is the prefix of various multi-byte opcodes. + // Introduced in FeatureNonTrappingFloatToIntConversion, but used in other + // features, such as FeatureBulkMemoryOperations. + OpcodeMiscPrefix Opcode = 0xfc + + // OpcodeVecPrefix is the prefix of all vector isntructions introduced in + // FeatureSIMD. + OpcodeVecPrefix Opcode = 0xfd +) + +// OpcodeMisc represents opcodes of the miscellaneous operations. +// Such an operations has multi-byte encoding which is prefixed by OpcodeMiscPrefix. +type OpcodeMisc = byte + +const ( + // Below are toggled with FeatureNonTrappingFloatToIntConversion. + // https://github.com/WebAssembly/spec/blob/ce4b6c4d47eb06098cc7ab2e81f24748da822f20/proposals/nontrapping-float-to-int-conversion/Overview.md + + OpcodeMiscI32TruncSatF32S OpcodeMisc = 0x00 + OpcodeMiscI32TruncSatF32U OpcodeMisc = 0x01 + OpcodeMiscI32TruncSatF64S OpcodeMisc = 0x02 + OpcodeMiscI32TruncSatF64U OpcodeMisc = 0x03 + OpcodeMiscI64TruncSatF32S OpcodeMisc = 0x04 + OpcodeMiscI64TruncSatF32U OpcodeMisc = 0x05 + OpcodeMiscI64TruncSatF64S OpcodeMisc = 0x06 + OpcodeMiscI64TruncSatF64U OpcodeMisc = 0x07 + + // Below are toggled with FeatureBulkMemoryOperations. + // Opcodes are those new in document/core/appendix/index-instructions.rst (the commit that merged the feature). + // See https://github.com/WebAssembly/spec/commit/7fa2f20a6df4cf1c114582c8cb60f5bfcdbf1be1 + // See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#bulk-memory-and-table-instructions + + OpcodeMiscMemoryInit OpcodeMisc = 0x08 + OpcodeMiscDataDrop OpcodeMisc = 0x09 + OpcodeMiscMemoryCopy OpcodeMisc = 0x0a + OpcodeMiscMemoryFill OpcodeMisc = 0x0b + OpcodeMiscTableInit OpcodeMisc = 0x0c + OpcodeMiscElemDrop OpcodeMisc = 0x0d + OpcodeMiscTableCopy OpcodeMisc = 0x0e + + // Below are toggled with FeatureReferenceTypes + + OpcodeMiscTableGrow OpcodeMisc = 0x0f + OpcodeMiscTableSize OpcodeMisc = 0x10 + OpcodeMiscTableFill OpcodeMisc = 0x11 +) + +// OpcodeVec represents an opcode of a vector instructions which has +// multi-byte encoding and is prefixed by OpcodeMiscPrefix. +// +// These opcodes are toggled with FeatureSIMD. +type OpcodeVec = byte + +const ( + // Loads and stores. + + OpcodeVecV128Load OpcodeVec = 0x00 + OpcodeVecV128Load8x8s OpcodeVec = 0x01 + OpcodeVecV128Load8x8u OpcodeVec = 0x02 + OpcodeVecV128Load16x4s OpcodeVec = 0x03 + OpcodeVecV128Load16x4u OpcodeVec = 0x04 + OpcodeVecV128Load32x2s OpcodeVec = 0x05 + OpcodeVecV128Load32x2u OpcodeVec = 0x06 + OpcodeVecV128Load8Splat OpcodeVec = 0x07 + OpcodeVecV128Load16Splat OpcodeVec = 0x08 + OpcodeVecV128Load32Splat OpcodeVec = 0x09 + OpcodeVecV128Load64Splat OpcodeVec = 0x0a + + OpcodeVecV128Load32zero OpcodeVec = 0x5c + OpcodeVecV128Load64zero OpcodeVec = 0x5d + + OpcodeVecV128Store OpcodeVec = 0x0b + OpcodeVecV128Load8Lane OpcodeVec = 0x54 + OpcodeVecV128Load16Lane OpcodeVec = 0x55 + OpcodeVecV128Load32Lane OpcodeVec = 0x56 + OpcodeVecV128Load64Lane OpcodeVec = 0x57 + OpcodeVecV128Store8Lane OpcodeVec = 0x58 + OpcodeVecV128Store16Lane OpcodeVec = 0x59 + OpcodeVecV128Store32Lane OpcodeVec = 0x5a + OpcodeVecV128Store64Lane OpcodeVec = 0x5b + + // OpcodeVecV128Const is the vector const instruction. + OpcodeVecV128Const OpcodeVec = 0x0c + + // OpcodeVecV128i8x16Shuffle is the vector shuffle instruction. + OpcodeVecV128i8x16Shuffle OpcodeVec = 0x0d + + // Extrac and replaces. + + OpcodeVecI8x16ExtractLaneS OpcodeVec = 0x15 + OpcodeVecI8x16ExtractLaneU OpcodeVec = 0x16 + OpcodeVecI8x16ReplaceLane OpcodeVec = 0x17 + OpcodeVecI16x8ExtractLaneS OpcodeVec = 0x18 + OpcodeVecI16x8ExtractLaneU OpcodeVec = 0x19 + OpcodeVecI16x8ReplaceLane OpcodeVec = 0x1a + OpcodeVecI32x4ExtractLane OpcodeVec = 0x1b + OpcodeVecI32x4ReplaceLane OpcodeVec = 0x1c + OpcodeVecI64x2ExtractLane OpcodeVec = 0x1d + OpcodeVecI64x2ReplaceLane OpcodeVec = 0x1e + OpcodeVecF32x4ExtractLane OpcodeVec = 0x1f + OpcodeVecF32x4ReplaceLane OpcodeVec = 0x20 + OpcodeVecF64x2ExtractLane OpcodeVec = 0x21 + OpcodeVecF64x2ReplaceLane OpcodeVec = 0x22 + + // Splat and swizzle. + + OpcodeVecI8x16Swizzle OpcodeVec = 0x0e + OpcodeVecI8x16Splat OpcodeVec = 0x0f + OpcodeVecI16x8Splat OpcodeVec = 0x10 + OpcodeVecI32x4Splat OpcodeVec = 0x11 + OpcodeVecI64x2Splat OpcodeVec = 0x12 + OpcodeVecF32x4Splat OpcodeVec = 0x13 + OpcodeVecF64x2Splat OpcodeVec = 0x14 + + // i8 comparisons. + + OpcodeVecI8x16Eq OpcodeVec = 0x23 + OpcodeVecI8x16Ne OpcodeVec = 0x24 + OpcodeVecI8x16LtS OpcodeVec = 0x25 + OpcodeVecI8x16LtU OpcodeVec = 0x26 + OpcodeVecI8x16GtS OpcodeVec = 0x27 + OpcodeVecI8x16GtU OpcodeVec = 0x28 + OpcodeVecI8x16LeS OpcodeVec = 0x29 + OpcodeVecI8x16LeU OpcodeVec = 0x2a + OpcodeVecI8x16GeS OpcodeVec = 0x2b + OpcodeVecI8x16GeU OpcodeVec = 0x2c + + // i16 comparisons. + + OpcodeVecI16x8Eq OpcodeVec = 0x2d + OpcodeVecI16x8Ne OpcodeVec = 0x2e + OpcodeVecI16x8LtS OpcodeVec = 0x2f + OpcodeVecI16x8LtU OpcodeVec = 0x30 + OpcodeVecI16x8GtS OpcodeVec = 0x31 + OpcodeVecI16x8GtU OpcodeVec = 0x32 + OpcodeVecI16x8LeS OpcodeVec = 0x33 + OpcodeVecI16x8LeU OpcodeVec = 0x34 + OpcodeVecI16x8GeS OpcodeVec = 0x35 + OpcodeVecI16x8GeU OpcodeVec = 0x36 + + // i32 comparisons. + + OpcodeVecI32x4Eq OpcodeVec = 0x37 + OpcodeVecI32x4Ne OpcodeVec = 0x38 + OpcodeVecI32x4LtS OpcodeVec = 0x39 + OpcodeVecI32x4LtU OpcodeVec = 0x3a + OpcodeVecI32x4GtS OpcodeVec = 0x3b + OpcodeVecI32x4GtU OpcodeVec = 0x3c + OpcodeVecI32x4LeS OpcodeVec = 0x3d + OpcodeVecI32x4LeU OpcodeVec = 0x3e + OpcodeVecI32x4GeS OpcodeVec = 0x3f + OpcodeVecI32x4GeU OpcodeVec = 0x40 + + // i64 comparisons. + + OpcodeVecI64x2Eq OpcodeVec = 0xd6 + OpcodeVecI64x2Ne OpcodeVec = 0xd7 + OpcodeVecI64x2LtS OpcodeVec = 0xd8 + OpcodeVecI64x2GtS OpcodeVec = 0xd9 + OpcodeVecI64x2LeS OpcodeVec = 0xda + OpcodeVecI64x2GeS OpcodeVec = 0xdb + + // f32 comparisons. + + OpcodeVecF32x4Eq OpcodeVec = 0x41 + OpcodeVecF32x4Ne OpcodeVec = 0x42 + OpcodeVecF32x4Lt OpcodeVec = 0x43 + OpcodeVecF32x4Gt OpcodeVec = 0x44 + OpcodeVecF32x4Le OpcodeVec = 0x45 + OpcodeVecF32x4Ge OpcodeVec = 0x46 + + // f64 comparisons. + + OpcodeVecF64x2Eq OpcodeVec = 0x47 + OpcodeVecF64x2Ne OpcodeVec = 0x48 + OpcodeVecF64x2Lt OpcodeVec = 0x49 + OpcodeVecF64x2Gt OpcodeVec = 0x4a + OpcodeVecF64x2Le OpcodeVec = 0x4b + OpcodeVecF64x2Ge OpcodeVec = 0x4c + + // v128 logical instructions. + + OpcodeVecV128Not OpcodeVec = 0x4d + OpcodeVecV128And OpcodeVec = 0x4e + OpcodeVecV128AndNot OpcodeVec = 0x4f + OpcodeVecV128Or OpcodeVec = 0x50 + OpcodeVecV128Xor OpcodeVec = 0x51 + OpcodeVecV128Bitselect OpcodeVec = 0x52 + OpcodeVecV128AnyTrue OpcodeVec = 0x53 + + // i8 misc. + + OpcodeVecI8x16Abs OpcodeVec = 0x60 + OpcodeVecI8x16Neg OpcodeVec = 0x61 + OpcodeVecI8x16Popcnt OpcodeVec = 0x62 + OpcodeVecI8x16AllTrue OpcodeVec = 0x63 + OpcodeVecI8x16BitMask OpcodeVec = 0x64 + OpcodeVecI8x16NarrowI16x8S OpcodeVec = 0x65 + OpcodeVecI8x16NarrowI16x8U OpcodeVec = 0x66 + + OpcodeVecI8x16Shl OpcodeVec = 0x6b + OpcodeVecI8x16ShrS OpcodeVec = 0x6c + OpcodeVecI8x16ShrU OpcodeVec = 0x6d + OpcodeVecI8x16Add OpcodeVec = 0x6e + OpcodeVecI8x16AddSatS OpcodeVec = 0x6f + + OpcodeVecI8x16AddSatU OpcodeVec = 0x70 + OpcodeVecI8x16Sub OpcodeVec = 0x71 + OpcodeVecI8x16SubSatS OpcodeVec = 0x72 + OpcodeVecI8x16SubSatU OpcodeVec = 0x73 + OpcodeVecI8x16MinS OpcodeVec = 0x76 + OpcodeVecI8x16MinU OpcodeVec = 0x77 + OpcodeVecI8x16MaxS OpcodeVec = 0x78 + OpcodeVecI8x16MaxU OpcodeVec = 0x79 + OpcodeVecI8x16AvgrU OpcodeVec = 0x7b + + // i16 misc. + + OpcodeVecI16x8ExtaddPairwiseI8x16S OpcodeVec = 0x7c + OpcodeVecI16x8ExtaddPairwiseI8x16U OpcodeVec = 0x7d + OpcodeVecI16x8Abs OpcodeVec = 0x80 + OpcodeVecI16x8Neg OpcodeVec = 0x81 + OpcodeVecI16x8Q15mulrSatS OpcodeVec = 0x82 + OpcodeVecI16x8AllTrue OpcodeVec = 0x83 + OpcodeVecI16x8BitMask OpcodeVec = 0x84 + OpcodeVecI16x8NarrowI32x4S OpcodeVec = 0x85 + OpcodeVecI16x8NarrowI32x4U OpcodeVec = 0x86 + OpcodeVecI16x8ExtendLowI8x16S OpcodeVec = 0x87 + OpcodeVecI16x8ExtendHighI8x16S OpcodeVec = 0x88 + OpcodeVecI16x8ExtendLowI8x16U OpcodeVec = 0x89 + OpcodeVecI16x8ExtendHighI8x16U OpcodeVec = 0x8a + OpcodeVecI16x8Shl OpcodeVec = 0x8b + OpcodeVecI16x8ShrS OpcodeVec = 0x8c + OpcodeVecI16x8ShrU OpcodeVec = 0x8d + OpcodeVecI16x8Add OpcodeVec = 0x8e + OpcodeVecI16x8AddSatS OpcodeVec = 0x8f + OpcodeVecI16x8AddSatU OpcodeVec = 0x90 + OpcodeVecI16x8Sub OpcodeVec = 0x91 + OpcodeVecI16x8SubSatS OpcodeVec = 0x92 + OpcodeVecI16x8SubSatU OpcodeVec = 0x93 + OpcodeVecI16x8Mul OpcodeVec = 0x95 + OpcodeVecI16x8MinS OpcodeVec = 0x96 + OpcodeVecI16x8MinU OpcodeVec = 0x97 + OpcodeVecI16x8MaxS OpcodeVec = 0x98 + OpcodeVecI16x8MaxU OpcodeVec = 0x99 + OpcodeVecI16x8AvgrU OpcodeVec = 0x9b + OpcodeVecI16x8ExtMulLowI8x16S OpcodeVec = 0x9c + OpcodeVecI16x8ExtMulHighI8x16S OpcodeVec = 0x9d + OpcodeVecI16x8ExtMulLowI8x16U OpcodeVec = 0x9e + OpcodeVecI16x8ExtMulHighI8x16U OpcodeVec = 0x9f + + // i32 misc. + + OpcodeVecI32x4ExtaddPairwiseI16x8S OpcodeVec = 0x7e + OpcodeVecI32x4ExtaddPairwiseI16x8U OpcodeVec = 0x7f + OpcodeVecI32x4Abs OpcodeVec = 0xa0 + OpcodeVecI32x4Neg OpcodeVec = 0xa1 + OpcodeVecI32x4AllTrue OpcodeVec = 0xa3 + OpcodeVecI32x4BitMask OpcodeVec = 0xa4 + OpcodeVecI32x4ExtendLowI16x8S OpcodeVec = 0xa7 + OpcodeVecI32x4ExtendHighI16x8S OpcodeVec = 0xa8 + OpcodeVecI32x4ExtendLowI16x8U OpcodeVec = 0xa9 + OpcodeVecI32x4ExtendHighI16x8U OpcodeVec = 0xaa + OpcodeVecI32x4Shl OpcodeVec = 0xab + OpcodeVecI32x4ShrS OpcodeVec = 0xac + OpcodeVecI32x4ShrU OpcodeVec = 0xad + OpcodeVecI32x4Add OpcodeVec = 0xae + OpcodeVecI32x4Sub OpcodeVec = 0xb1 + OpcodeVecI32x4Mul OpcodeVec = 0xb5 + OpcodeVecI32x4MinS OpcodeVec = 0xb6 + OpcodeVecI32x4MinU OpcodeVec = 0xb7 + OpcodeVecI32x4MaxS OpcodeVec = 0xb8 + OpcodeVecI32x4MaxU OpcodeVec = 0xb9 + OpcodeVecI32x4DotI16x8S OpcodeVec = 0xba + OpcodeVecI32x4ExtMulLowI16x8S OpcodeVec = 0xbc + OpcodeVecI32x4ExtMulHighI16x8S OpcodeVec = 0xbd + OpcodeVecI32x4ExtMulLowI16x8U OpcodeVec = 0xbe + OpcodeVecI32x4ExtMulHighI16x8U OpcodeVec = 0xbf + + // i64 misc. + + OpcodeVecI64x2Abs OpcodeVec = 0xc0 + OpcodeVecI64x2Neg OpcodeVec = 0xc1 + OpcodeVecI64x2AllTrue OpcodeVec = 0xc3 + OpcodeVecI64x2BitMask OpcodeVec = 0xc4 + OpcodeVecI64x2ExtendLowI32x4S OpcodeVec = 0xc7 + OpcodeVecI64x2ExtendHighI32x4S OpcodeVec = 0xc8 + OpcodeVecI64x2ExtendLowI32x4U OpcodeVec = 0xc9 + OpcodeVecI64x2ExtendHighI32x4U OpcodeVec = 0xca + OpcodeVecI64x2Shl OpcodeVec = 0xcb + OpcodeVecI64x2ShrS OpcodeVec = 0xcc + OpcodeVecI64x2ShrU OpcodeVec = 0xcd + OpcodeVecI64x2Add OpcodeVec = 0xce + OpcodeVecI64x2Sub OpcodeVec = 0xd1 + OpcodeVecI64x2Mul OpcodeVec = 0xd5 + OpcodeVecI64x2ExtMulLowI32x4S OpcodeVec = 0xdc + OpcodeVecI64x2ExtMulHighI32x4S OpcodeVec = 0xdd + OpcodeVecI64x2ExtMulLowI32x4U OpcodeVec = 0xde + OpcodeVecI64x2ExtMulHighI32x4U OpcodeVec = 0xdf + + // f32 misc. + + OpcodeVecF32x4Ceil OpcodeVec = 0x67 + OpcodeVecF32x4Floor OpcodeVec = 0x68 + OpcodeVecF32x4Trunc OpcodeVec = 0x69 + OpcodeVecF32x4Nearest OpcodeVec = 0x6a + OpcodeVecF32x4Abs OpcodeVec = 0xe0 + OpcodeVecF32x4Neg OpcodeVec = 0xe1 + OpcodeVecF32x4Sqrt OpcodeVec = 0xe3 + OpcodeVecF32x4Add OpcodeVec = 0xe4 + OpcodeVecF32x4Sub OpcodeVec = 0xe5 + OpcodeVecF32x4Mul OpcodeVec = 0xe6 + OpcodeVecF32x4Div OpcodeVec = 0xe7 + OpcodeVecF32x4Min OpcodeVec = 0xe8 + OpcodeVecF32x4Max OpcodeVec = 0xe9 + OpcodeVecF32x4Pmin OpcodeVec = 0xea + OpcodeVecF32x4Pmax OpcodeVec = 0xeb + + // f64 misc. + + OpcodeVecF64x2Ceil OpcodeVec = 0x74 + OpcodeVecF64x2Floor OpcodeVec = 0x75 + OpcodeVecF64x2Trunc OpcodeVec = 0x7a + OpcodeVecF64x2Nearest OpcodeVec = 0x94 + OpcodeVecF64x2Abs OpcodeVec = 0xec + OpcodeVecF64x2Neg OpcodeVec = 0xed + OpcodeVecF64x2Sqrt OpcodeVec = 0xef + OpcodeVecF64x2Add OpcodeVec = 0xf0 + OpcodeVecF64x2Sub OpcodeVec = 0xf1 + OpcodeVecF64x2Mul OpcodeVec = 0xf2 + OpcodeVecF64x2Div OpcodeVec = 0xf3 + OpcodeVecF64x2Min OpcodeVec = 0xf4 + OpcodeVecF64x2Max OpcodeVec = 0xf5 + OpcodeVecF64x2Pmin OpcodeVec = 0xf6 + OpcodeVecF64x2Pmax OpcodeVec = 0xf7 + + // conversions. + + OpcodeVecI32x4TruncSatF32x4S OpcodeVec = 0xf8 + OpcodeVecI32x4TruncSatF32x4U OpcodeVec = 0xf9 + OpcodeVecF32x4ConvertI32x4S OpcodeVec = 0xfa + OpcodeVecF32x4ConvertI32x4U OpcodeVec = 0xfb + OpcodeVecI32x4TruncSatF64x2SZero OpcodeVec = 0xfc + OpcodeVecI32x4TruncSatF64x2UZero OpcodeVec = 0xfd + OpcodeVecF64x2ConvertLowI32x4S OpcodeVec = 0xfe + OpcodeVecF64x2ConvertLowI32x4U OpcodeVec = 0xff + OpcodeVecF32x4DemoteF64x2Zero OpcodeVec = 0x5e + OpcodeVecF64x2PromoteLowF32x4Zero OpcodeVec = 0x5f +) + +const ( + OpcodeUnreachableName = "unreachable" + OpcodeNopName = "nop" + OpcodeBlockName = "block" + OpcodeLoopName = "loop" + OpcodeIfName = "if" + OpcodeElseName = "else" + OpcodeEndName = "end" + OpcodeBrName = "br" + OpcodeBrIfName = "br_if" + OpcodeBrTableName = "br_table" + OpcodeReturnName = "return" + OpcodeCallName = "call" + OpcodeCallIndirectName = "call_indirect" + OpcodeDropName = "drop" + OpcodeSelectName = "select" + OpcodeTypedSelectName = "typed_select" + OpcodeLocalGetName = "local.get" + OpcodeLocalSetName = "local.set" + OpcodeLocalTeeName = "local.tee" + OpcodeGlobalGetName = "global.get" + OpcodeGlobalSetName = "global.set" + OpcodeI32LoadName = "i32.load" + OpcodeI64LoadName = "i64.load" + OpcodeF32LoadName = "f32.load" + OpcodeF64LoadName = "f64.load" + OpcodeI32Load8SName = "i32.load8_s" + OpcodeI32Load8UName = "i32.load8_u" + OpcodeI32Load16SName = "i32.load16_s" + OpcodeI32Load16UName = "i32.load16_u" + OpcodeI64Load8SName = "i64.load8_s" + OpcodeI64Load8UName = "i64.load8_u" + OpcodeI64Load16SName = "i64.load16_s" + OpcodeI64Load16UName = "i64.load16_u" + OpcodeI64Load32SName = "i64.load32_s" + OpcodeI64Load32UName = "i64.load32_u" + OpcodeI32StoreName = "i32.store" + OpcodeI64StoreName = "i64.store" + OpcodeF32StoreName = "f32.store" + OpcodeF64StoreName = "f64.store" + OpcodeI32Store8Name = "i32.store8" + OpcodeI32Store16Name = "i32.store16" + OpcodeI64Store8Name = "i64.store8" + OpcodeI64Store16Name = "i64.store16" + OpcodeI64Store32Name = "i64.store32" + OpcodeMemorySizeName = "memory.size" + OpcodeMemoryGrowName = "memory.grow" + OpcodeI32ConstName = "i32.const" + OpcodeI64ConstName = "i64.const" + OpcodeF32ConstName = "f32.const" + OpcodeF64ConstName = "f64.const" + OpcodeI32EqzName = "i32.eqz" + OpcodeI32EqName = "i32.eq" + OpcodeI32NeName = "i32.ne" + OpcodeI32LtSName = "i32.lt_s" + OpcodeI32LtUName = "i32.lt_u" + OpcodeI32GtSName = "i32.gt_s" + OpcodeI32GtUName = "i32.gt_u" + OpcodeI32LeSName = "i32.le_s" + OpcodeI32LeUName = "i32.le_u" + OpcodeI32GeSName = "i32.ge_s" + OpcodeI32GeUName = "i32.ge_u" + OpcodeI64EqzName = "i64.eqz" + OpcodeI64EqName = "i64.eq" + OpcodeI64NeName = "i64.ne" + OpcodeI64LtSName = "i64.lt_s" + OpcodeI64LtUName = "i64.lt_u" + OpcodeI64GtSName = "i64.gt_s" + OpcodeI64GtUName = "i64.gt_u" + OpcodeI64LeSName = "i64.le_s" + OpcodeI64LeUName = "i64.le_u" + OpcodeI64GeSName = "i64.ge_s" + OpcodeI64GeUName = "i64.ge_u" + OpcodeF32EqName = "f32.eq" + OpcodeF32NeName = "f32.ne" + OpcodeF32LtName = "f32.lt" + OpcodeF32GtName = "f32.gt" + OpcodeF32LeName = "f32.le" + OpcodeF32GeName = "f32.ge" + OpcodeF64EqName = "f64.eq" + OpcodeF64NeName = "f64.ne" + OpcodeF64LtName = "f64.lt" + OpcodeF64GtName = "f64.gt" + OpcodeF64LeName = "f64.le" + OpcodeF64GeName = "f64.ge" + OpcodeI32ClzName = "i32.clz" + OpcodeI32CtzName = "i32.ctz" + OpcodeI32PopcntName = "i32.popcnt" + OpcodeI32AddName = "i32.add" + OpcodeI32SubName = "i32.sub" + OpcodeI32MulName = "i32.mul" + OpcodeI32DivSName = "i32.div_s" + OpcodeI32DivUName = "i32.div_u" + OpcodeI32RemSName = "i32.rem_s" + OpcodeI32RemUName = "i32.rem_u" + OpcodeI32AndName = "i32.and" + OpcodeI32OrName = "i32.or" + OpcodeI32XorName = "i32.xor" + OpcodeI32ShlName = "i32.shl" + OpcodeI32ShrSName = "i32.shr_s" + OpcodeI32ShrUName = "i32.shr_u" + OpcodeI32RotlName = "i32.rotl" + OpcodeI32RotrName = "i32.rotr" + OpcodeI64ClzName = "i64.clz" + OpcodeI64CtzName = "i64.ctz" + OpcodeI64PopcntName = "i64.popcnt" + OpcodeI64AddName = "i64.add" + OpcodeI64SubName = "i64.sub" + OpcodeI64MulName = "i64.mul" + OpcodeI64DivSName = "i64.div_s" + OpcodeI64DivUName = "i64.div_u" + OpcodeI64RemSName = "i64.rem_s" + OpcodeI64RemUName = "i64.rem_u" + OpcodeI64AndName = "i64.and" + OpcodeI64OrName = "i64.or" + OpcodeI64XorName = "i64.xor" + OpcodeI64ShlName = "i64.shl" + OpcodeI64ShrSName = "i64.shr_s" + OpcodeI64ShrUName = "i64.shr_u" + OpcodeI64RotlName = "i64.rotl" + OpcodeI64RotrName = "i64.rotr" + OpcodeF32AbsName = "f32.abs" + OpcodeF32NegName = "f32.neg" + OpcodeF32CeilName = "f32.ceil" + OpcodeF32FloorName = "f32.floor" + OpcodeF32TruncName = "f32.trunc" + OpcodeF32NearestName = "f32.nearest" + OpcodeF32SqrtName = "f32.sqrt" + OpcodeF32AddName = "f32.add" + OpcodeF32SubName = "f32.sub" + OpcodeF32MulName = "f32.mul" + OpcodeF32DivName = "f32.div" + OpcodeF32MinName = "f32.min" + OpcodeF32MaxName = "f32.max" + OpcodeF32CopysignName = "f32.copysign" + OpcodeF64AbsName = "f64.abs" + OpcodeF64NegName = "f64.neg" + OpcodeF64CeilName = "f64.ceil" + OpcodeF64FloorName = "f64.floor" + OpcodeF64TruncName = "f64.trunc" + OpcodeF64NearestName = "f64.nearest" + OpcodeF64SqrtName = "f64.sqrt" + OpcodeF64AddName = "f64.add" + OpcodeF64SubName = "f64.sub" + OpcodeF64MulName = "f64.mul" + OpcodeF64DivName = "f64.div" + OpcodeF64MinName = "f64.min" + OpcodeF64MaxName = "f64.max" + OpcodeF64CopysignName = "f64.copysign" + OpcodeI32WrapI64Name = "i32.wrap_i64" + OpcodeI32TruncF32SName = "i32.trunc_f32_s" + OpcodeI32TruncF32UName = "i32.trunc_f32_u" + OpcodeI32TruncF64SName = "i32.trunc_f64_s" + OpcodeI32TruncF64UName = "i32.trunc_f64_u" + OpcodeI64ExtendI32SName = "i64.extend_i32_s" + OpcodeI64ExtendI32UName = "i64.extend_i32_u" + OpcodeI64TruncF32SName = "i64.trunc_f32_s" + OpcodeI64TruncF32UName = "i64.trunc_f32_u" + OpcodeI64TruncF64SName = "i64.trunc_f64_s" + OpcodeI64TruncF64UName = "i64.trunc_f64_u" + OpcodeF32ConvertI32SName = "f32.convert_i32_s" + OpcodeF32ConvertI32UName = "f32.convert_i32_u" + OpcodeF32ConvertI64SName = "f32.convert_i64_s" + OpcodeF32ConvertI64UName = "f32.convert_i64u" + OpcodeF32DemoteF64Name = "f32.demote_f64" + OpcodeF64ConvertI32SName = "f64.convert_i32_s" + OpcodeF64ConvertI32UName = "f64.convert_i32_u" + OpcodeF64ConvertI64SName = "f64.convert_i64_s" + OpcodeF64ConvertI64UName = "f64.convert_i64_u" + OpcodeF64PromoteF32Name = "f64.promote_f32" + OpcodeI32ReinterpretF32Name = "i32.reinterpret_f32" + OpcodeI64ReinterpretF64Name = "i64.reinterpret_f64" + OpcodeF32ReinterpretI32Name = "f32.reinterpret_i32" + OpcodeF64ReinterpretI64Name = "f64.reinterpret_i64" + + OpcodeRefNullName = "ref.null" + OpcodeRefIsNullName = "ref.is_null" + OpcodeRefFuncName = "ref.func" + + OpcodeTableGetName = "table.get" + OpcodeTableSetName = "table.set" + + // Below are toggled with FeatureSignExtensionOps + + OpcodeI32Extend8SName = "i32.extend8_s" + OpcodeI32Extend16SName = "i32.extend16_s" + OpcodeI64Extend8SName = "i64.extend8_s" + OpcodeI64Extend16SName = "i64.extend16_s" + OpcodeI64Extend32SName = "i64.extend32_s" + + OpcodeMiscPrefixName = "misc_prefix" + OpcodeVecPrefixName = "vector_prefix" +) + +var instructionNames = [256]string{ + OpcodeUnreachable: OpcodeUnreachableName, + OpcodeNop: OpcodeNopName, + OpcodeBlock: OpcodeBlockName, + OpcodeLoop: OpcodeLoopName, + OpcodeIf: OpcodeIfName, + OpcodeElse: OpcodeElseName, + OpcodeEnd: OpcodeEndName, + OpcodeBr: OpcodeBrName, + OpcodeBrIf: OpcodeBrIfName, + OpcodeBrTable: OpcodeBrTableName, + OpcodeReturn: OpcodeReturnName, + OpcodeCall: OpcodeCallName, + OpcodeCallIndirect: OpcodeCallIndirectName, + OpcodeDrop: OpcodeDropName, + OpcodeSelect: OpcodeSelectName, + OpcodeTypedSelect: OpcodeTypedSelectName, + OpcodeLocalGet: OpcodeLocalGetName, + OpcodeLocalSet: OpcodeLocalSetName, + OpcodeLocalTee: OpcodeLocalTeeName, + OpcodeGlobalGet: OpcodeGlobalGetName, + OpcodeGlobalSet: OpcodeGlobalSetName, + OpcodeI32Load: OpcodeI32LoadName, + OpcodeI64Load: OpcodeI64LoadName, + OpcodeF32Load: OpcodeF32LoadName, + OpcodeF64Load: OpcodeF64LoadName, + OpcodeI32Load8S: OpcodeI32Load8SName, + OpcodeI32Load8U: OpcodeI32Load8UName, + OpcodeI32Load16S: OpcodeI32Load16SName, + OpcodeI32Load16U: OpcodeI32Load16UName, + OpcodeI64Load8S: OpcodeI64Load8SName, + OpcodeI64Load8U: OpcodeI64Load8UName, + OpcodeI64Load16S: OpcodeI64Load16SName, + OpcodeI64Load16U: OpcodeI64Load16UName, + OpcodeI64Load32S: OpcodeI64Load32SName, + OpcodeI64Load32U: OpcodeI64Load32UName, + OpcodeI32Store: OpcodeI32StoreName, + OpcodeI64Store: OpcodeI64StoreName, + OpcodeF32Store: OpcodeF32StoreName, + OpcodeF64Store: OpcodeF64StoreName, + OpcodeI32Store8: OpcodeI32Store8Name, + OpcodeI32Store16: OpcodeI32Store16Name, + OpcodeI64Store8: OpcodeI64Store8Name, + OpcodeI64Store16: OpcodeI64Store16Name, + OpcodeI64Store32: OpcodeI64Store32Name, + OpcodeMemorySize: OpcodeMemorySizeName, + OpcodeMemoryGrow: OpcodeMemoryGrowName, + OpcodeI32Const: OpcodeI32ConstName, + OpcodeI64Const: OpcodeI64ConstName, + OpcodeF32Const: OpcodeF32ConstName, + OpcodeF64Const: OpcodeF64ConstName, + OpcodeI32Eqz: OpcodeI32EqzName, + OpcodeI32Eq: OpcodeI32EqName, + OpcodeI32Ne: OpcodeI32NeName, + OpcodeI32LtS: OpcodeI32LtSName, + OpcodeI32LtU: OpcodeI32LtUName, + OpcodeI32GtS: OpcodeI32GtSName, + OpcodeI32GtU: OpcodeI32GtUName, + OpcodeI32LeS: OpcodeI32LeSName, + OpcodeI32LeU: OpcodeI32LeUName, + OpcodeI32GeS: OpcodeI32GeSName, + OpcodeI32GeU: OpcodeI32GeUName, + OpcodeI64Eqz: OpcodeI64EqzName, + OpcodeI64Eq: OpcodeI64EqName, + OpcodeI64Ne: OpcodeI64NeName, + OpcodeI64LtS: OpcodeI64LtSName, + OpcodeI64LtU: OpcodeI64LtUName, + OpcodeI64GtS: OpcodeI64GtSName, + OpcodeI64GtU: OpcodeI64GtUName, + OpcodeI64LeS: OpcodeI64LeSName, + OpcodeI64LeU: OpcodeI64LeUName, + OpcodeI64GeS: OpcodeI64GeSName, + OpcodeI64GeU: OpcodeI64GeUName, + OpcodeF32Eq: OpcodeF32EqName, + OpcodeF32Ne: OpcodeF32NeName, + OpcodeF32Lt: OpcodeF32LtName, + OpcodeF32Gt: OpcodeF32GtName, + OpcodeF32Le: OpcodeF32LeName, + OpcodeF32Ge: OpcodeF32GeName, + OpcodeF64Eq: OpcodeF64EqName, + OpcodeF64Ne: OpcodeF64NeName, + OpcodeF64Lt: OpcodeF64LtName, + OpcodeF64Gt: OpcodeF64GtName, + OpcodeF64Le: OpcodeF64LeName, + OpcodeF64Ge: OpcodeF64GeName, + OpcodeI32Clz: OpcodeI32ClzName, + OpcodeI32Ctz: OpcodeI32CtzName, + OpcodeI32Popcnt: OpcodeI32PopcntName, + OpcodeI32Add: OpcodeI32AddName, + OpcodeI32Sub: OpcodeI32SubName, + OpcodeI32Mul: OpcodeI32MulName, + OpcodeI32DivS: OpcodeI32DivSName, + OpcodeI32DivU: OpcodeI32DivUName, + OpcodeI32RemS: OpcodeI32RemSName, + OpcodeI32RemU: OpcodeI32RemUName, + OpcodeI32And: OpcodeI32AndName, + OpcodeI32Or: OpcodeI32OrName, + OpcodeI32Xor: OpcodeI32XorName, + OpcodeI32Shl: OpcodeI32ShlName, + OpcodeI32ShrS: OpcodeI32ShrSName, + OpcodeI32ShrU: OpcodeI32ShrUName, + OpcodeI32Rotl: OpcodeI32RotlName, + OpcodeI32Rotr: OpcodeI32RotrName, + OpcodeI64Clz: OpcodeI64ClzName, + OpcodeI64Ctz: OpcodeI64CtzName, + OpcodeI64Popcnt: OpcodeI64PopcntName, + OpcodeI64Add: OpcodeI64AddName, + OpcodeI64Sub: OpcodeI64SubName, + OpcodeI64Mul: OpcodeI64MulName, + OpcodeI64DivS: OpcodeI64DivSName, + OpcodeI64DivU: OpcodeI64DivUName, + OpcodeI64RemS: OpcodeI64RemSName, + OpcodeI64RemU: OpcodeI64RemUName, + OpcodeI64And: OpcodeI64AndName, + OpcodeI64Or: OpcodeI64OrName, + OpcodeI64Xor: OpcodeI64XorName, + OpcodeI64Shl: OpcodeI64ShlName, + OpcodeI64ShrS: OpcodeI64ShrSName, + OpcodeI64ShrU: OpcodeI64ShrUName, + OpcodeI64Rotl: OpcodeI64RotlName, + OpcodeI64Rotr: OpcodeI64RotrName, + OpcodeF32Abs: OpcodeF32AbsName, + OpcodeF32Neg: OpcodeF32NegName, + OpcodeF32Ceil: OpcodeF32CeilName, + OpcodeF32Floor: OpcodeF32FloorName, + OpcodeF32Trunc: OpcodeF32TruncName, + OpcodeF32Nearest: OpcodeF32NearestName, + OpcodeF32Sqrt: OpcodeF32SqrtName, + OpcodeF32Add: OpcodeF32AddName, + OpcodeF32Sub: OpcodeF32SubName, + OpcodeF32Mul: OpcodeF32MulName, + OpcodeF32Div: OpcodeF32DivName, + OpcodeF32Min: OpcodeF32MinName, + OpcodeF32Max: OpcodeF32MaxName, + OpcodeF32Copysign: OpcodeF32CopysignName, + OpcodeF64Abs: OpcodeF64AbsName, + OpcodeF64Neg: OpcodeF64NegName, + OpcodeF64Ceil: OpcodeF64CeilName, + OpcodeF64Floor: OpcodeF64FloorName, + OpcodeF64Trunc: OpcodeF64TruncName, + OpcodeF64Nearest: OpcodeF64NearestName, + OpcodeF64Sqrt: OpcodeF64SqrtName, + OpcodeF64Add: OpcodeF64AddName, + OpcodeF64Sub: OpcodeF64SubName, + OpcodeF64Mul: OpcodeF64MulName, + OpcodeF64Div: OpcodeF64DivName, + OpcodeF64Min: OpcodeF64MinName, + OpcodeF64Max: OpcodeF64MaxName, + OpcodeF64Copysign: OpcodeF64CopysignName, + OpcodeI32WrapI64: OpcodeI32WrapI64Name, + OpcodeI32TruncF32S: OpcodeI32TruncF32SName, + OpcodeI32TruncF32U: OpcodeI32TruncF32UName, + OpcodeI32TruncF64S: OpcodeI32TruncF64SName, + OpcodeI32TruncF64U: OpcodeI32TruncF64UName, + OpcodeI64ExtendI32S: OpcodeI64ExtendI32SName, + OpcodeI64ExtendI32U: OpcodeI64ExtendI32UName, + OpcodeI64TruncF32S: OpcodeI64TruncF32SName, + OpcodeI64TruncF32U: OpcodeI64TruncF32UName, + OpcodeI64TruncF64S: OpcodeI64TruncF64SName, + OpcodeI64TruncF64U: OpcodeI64TruncF64UName, + OpcodeF32ConvertI32S: OpcodeF32ConvertI32SName, + OpcodeF32ConvertI32U: OpcodeF32ConvertI32UName, + OpcodeF32ConvertI64S: OpcodeF32ConvertI64SName, + OpcodeF32ConvertI64U: OpcodeF32ConvertI64UName, + OpcodeF32DemoteF64: OpcodeF32DemoteF64Name, + OpcodeF64ConvertI32S: OpcodeF64ConvertI32SName, + OpcodeF64ConvertI32U: OpcodeF64ConvertI32UName, + OpcodeF64ConvertI64S: OpcodeF64ConvertI64SName, + OpcodeF64ConvertI64U: OpcodeF64ConvertI64UName, + OpcodeF64PromoteF32: OpcodeF64PromoteF32Name, + OpcodeI32ReinterpretF32: OpcodeI32ReinterpretF32Name, + OpcodeI64ReinterpretF64: OpcodeI64ReinterpretF64Name, + OpcodeF32ReinterpretI32: OpcodeF32ReinterpretI32Name, + OpcodeF64ReinterpretI64: OpcodeF64ReinterpretI64Name, + + OpcodeRefNull: OpcodeRefNullName, + OpcodeRefIsNull: OpcodeRefIsNullName, + OpcodeRefFunc: OpcodeRefFuncName, + + OpcodeTableGet: OpcodeTableGetName, + OpcodeTableSet: OpcodeTableSetName, + + // Below are toggled with FeatureSignExtensionOps + + OpcodeI32Extend8S: OpcodeI32Extend8SName, + OpcodeI32Extend16S: OpcodeI32Extend16SName, + OpcodeI64Extend8S: OpcodeI64Extend8SName, + OpcodeI64Extend16S: OpcodeI64Extend16SName, + OpcodeI64Extend32S: OpcodeI64Extend32SName, + + OpcodeMiscPrefix: OpcodeMiscPrefixName, + OpcodeVecPrefix: OpcodeVecPrefixName, +} + +// InstructionName returns the instruction corresponding to this binary Opcode. +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#a7-index-of-instructions +func InstructionName(oc Opcode) string { + return instructionNames[oc] +} + +const ( + OpcodeI32TruncSatF32SName = "i32.trunc_sat_f32_s" + OpcodeI32TruncSatF32UName = "i32.trunc_sat_f32_u" + OpcodeI32TruncSatF64SName = "i32.trunc_sat_f64_s" + OpcodeI32TruncSatF64UName = "i32.trunc_sat_f64_u" + OpcodeI64TruncSatF32SName = "i64.trunc_sat_f32_s" + OpcodeI64TruncSatF32UName = "i64.trunc_sat_f32_u" + OpcodeI64TruncSatF64SName = "i64.trunc_sat_f64_s" + OpcodeI64TruncSatF64UName = "i64.trunc_sat_f64_u" + + OpcodeMemoryInitName = "memory.init" + OpcodeDataDropName = "data.drop" + OpcodeMemoryCopyName = "memory.copy" + OpcodeMemoryFillName = "memory.fill" + OpcodeTableInitName = "table.init" + OpcodeElemDropName = "elem.drop" + OpcodeTableCopyName = "table.copy" + OpcodeTableGrowName = "table.grow" + OpcodeTableSizeName = "table.size" + OpcodeTableFillName = "table.fill" +) + +var miscInstructionNames = [256]string{ + OpcodeMiscI32TruncSatF32S: OpcodeI32TruncSatF32SName, + OpcodeMiscI32TruncSatF32U: OpcodeI32TruncSatF32UName, + OpcodeMiscI32TruncSatF64S: OpcodeI32TruncSatF64SName, + OpcodeMiscI32TruncSatF64U: OpcodeI32TruncSatF64UName, + OpcodeMiscI64TruncSatF32S: OpcodeI64TruncSatF32SName, + OpcodeMiscI64TruncSatF32U: OpcodeI64TruncSatF32UName, + OpcodeMiscI64TruncSatF64S: OpcodeI64TruncSatF64SName, + OpcodeMiscI64TruncSatF64U: OpcodeI64TruncSatF64UName, + + OpcodeMiscMemoryInit: OpcodeMemoryInitName, + OpcodeMiscDataDrop: OpcodeDataDropName, + OpcodeMiscMemoryCopy: OpcodeMemoryCopyName, + OpcodeMiscMemoryFill: OpcodeMemoryFillName, + OpcodeMiscTableInit: OpcodeTableInitName, + OpcodeMiscElemDrop: OpcodeElemDropName, + OpcodeMiscTableCopy: OpcodeTableCopyName, + OpcodeMiscTableGrow: OpcodeTableGrowName, + OpcodeMiscTableSize: OpcodeTableSizeName, + OpcodeMiscTableFill: OpcodeTableFillName, +} + +// MiscInstructionName returns the instruction corresponding to this miscellaneous Opcode. +func MiscInstructionName(oc OpcodeMisc) string { + return miscInstructionNames[oc] +} + +const ( + OpcodeVecV128LoadName = "v128.load" + OpcodeVecV128Load8x8SName = "v128.load8x8_s" + OpcodeVecV128Load8x8UName = "v128.load8x8_u" + OpcodeVecV128Load16x4SName = "v128.load16x4_s" + OpcodeVecV128Load16x4UName = "v128.load16x4_u" + OpcodeVecV128Load32x2SName = "v128.load32x2_s" + OpcodeVecV128Load32x2UName = "v128.load32x2_u" + OpcodeVecV128Load8SplatName = "v128.load8_splat" + OpcodeVecV128Load16SplatName = "v128.load16_splat" + OpcodeVecV128Load32SplatName = "v128.load32_splat" + OpcodeVecV128Load64SplatName = "v128.load64_splat" + OpcodeVecV128Load32zeroName = "v128.load32_zero" + OpcodeVecV128Load64zeroName = "v128.load64_zero" + OpcodeVecV128StoreName = "v128.store" + OpcodeVecV128Load8LaneName = "v128.load8_lane" + OpcodeVecV128Load16LaneName = "v128.load16_lane" + OpcodeVecV128Load32LaneName = "v128.load32_lane" + OpcodeVecV128Load64LaneName = "v128.load64_lane" + OpcodeVecV128Store8LaneName = "v128.store8_lane" + OpcodeVecV128Store16LaneName = "v128.store16_lane" + OpcodeVecV128Store32LaneName = "v128.store32_lane" + OpcodeVecV128Store64LaneName = "v128.store64_lane" + OpcodeVecV128ConstName = "v128.const" + OpcodeVecV128i8x16ShuffleName = "v128.shuffle" + OpcodeVecI8x16ExtractLaneSName = "i8x16.extract_lane_s" + OpcodeVecI8x16ExtractLaneUName = "i8x16.extract_lane_u" + OpcodeVecI8x16ReplaceLaneName = "i8x16.replace_lane" + OpcodeVecI16x8ExtractLaneSName = "i16x8.extract_lane_s" + OpcodeVecI16x8ExtractLaneUName = "i16x8.extract_lane_u" + OpcodeVecI16x8ReplaceLaneName = "i16x8.replace_lane" + OpcodeVecI32x4ExtractLaneName = "i32x4.extract_lane" + OpcodeVecI32x4ReplaceLaneName = "i32x4.replace_lane" + OpcodeVecI64x2ExtractLaneName = "i64x2.extract_lane" + OpcodeVecI64x2ReplaceLaneName = "i64x2.replace_lane" + OpcodeVecF32x4ExtractLaneName = "f32x4.extract_lane" + OpcodeVecF32x4ReplaceLaneName = "f32x4.replace_lane" + OpcodeVecF64x2ExtractLaneName = "f64x2.extract_lane" + OpcodeVecF64x2ReplaceLaneName = "f64x2.replace_lane" + OpcodeVecI8x16SwizzleName = "i8x16.swizzle" + OpcodeVecI8x16SplatName = "i8x16.splat" + OpcodeVecI16x8SplatName = "i16x8.splat" + OpcodeVecI32x4SplatName = "i32x4.splat" + OpcodeVecI64x2SplatName = "i64x2.splat" + OpcodeVecF32x4SplatName = "f32x4.splat" + OpcodeVecF64x2SplatName = "f64x2.splat" + OpcodeVecI8x16EqName = "i8x16.eq" + OpcodeVecI8x16NeName = "i8x16.ne" + OpcodeVecI8x16LtSName = "i8x16.lt_s" + OpcodeVecI8x16LtUName = "i8x16.lt_u" + OpcodeVecI8x16GtSName = "i8x16.gt_s" + OpcodeVecI8x16GtUName = "i8x16.gt_u" + OpcodeVecI8x16LeSName = "i8x16.le_s" + OpcodeVecI8x16LeUName = "i8x16.le_u" + OpcodeVecI8x16GeSName = "i8x16.ge_s" + OpcodeVecI8x16GeUName = "i8x16.ge_u" + OpcodeVecI16x8EqName = "i16x8.eq" + OpcodeVecI16x8NeName = "i16x8.ne" + OpcodeVecI16x8LtSName = "i16x8.lt_s" + OpcodeVecI16x8LtUName = "i16x8.lt_u" + OpcodeVecI16x8GtSName = "i16x8.gt_s" + OpcodeVecI16x8GtUName = "i16x8.gt_u" + OpcodeVecI16x8LeSName = "i16x8.le_s" + OpcodeVecI16x8LeUName = "i16x8.le_u" + OpcodeVecI16x8GeSName = "i16x8.ge_s" + OpcodeVecI16x8GeUName = "i16x8.ge_u" + OpcodeVecI32x4EqName = "i32x4.eq" + OpcodeVecI32x4NeName = "i32x4.ne" + OpcodeVecI32x4LtSName = "i32x4.lt_s" + OpcodeVecI32x4LtUName = "i32x4.lt_u" + OpcodeVecI32x4GtSName = "i32x4.gt_s" + OpcodeVecI32x4GtUName = "i32x4.gt_u" + OpcodeVecI32x4LeSName = "i32x4.le_s" + OpcodeVecI32x4LeUName = "i32x4.le_u" + OpcodeVecI32x4GeSName = "i32x4.ge_s" + OpcodeVecI32x4GeUName = "i32x4.ge_u" + OpcodeVecI64x2EqName = "i64x2.eq" + OpcodeVecI64x2NeName = "i64x2.ne" + OpcodeVecI64x2LtSName = "i64x2.lt" + OpcodeVecI64x2GtSName = "i64x2.gt" + OpcodeVecI64x2LeSName = "i64x2.le" + OpcodeVecI64x2GeSName = "i64x2.ge" + OpcodeVecF32x4EqName = "f32x4.eq" + OpcodeVecF32x4NeName = "f32x4.ne" + OpcodeVecF32x4LtName = "f32x4.lt" + OpcodeVecF32x4GtName = "f32x4.gt" + OpcodeVecF32x4LeName = "f32x4.le" + OpcodeVecF32x4GeName = "f32x4.ge" + OpcodeVecF64x2EqName = "f64x2.eq" + OpcodeVecF64x2NeName = "f64x2.ne" + OpcodeVecF64x2LtName = "f64x2.lt" + OpcodeVecF64x2GtName = "f64x2.gt" + OpcodeVecF64x2LeName = "f64x2.le" + OpcodeVecF64x2GeName = "f64x2.ge" + OpcodeVecV128NotName = "v128.not" + OpcodeVecV128AndName = "v128.and" + OpcodeVecV128AndNotName = "v128.andnot" + OpcodeVecV128OrName = "v128.or" + OpcodeVecV128XorName = "v128.xor" + OpcodeVecV128BitselectName = "v128.bitselect" + OpcodeVecV128AnyTrueName = "v128.any_true" + OpcodeVecI8x16AbsName = "i8x16.abs" + OpcodeVecI8x16NegName = "i8x16.neg" + OpcodeVecI8x16PopcntName = "i8x16.popcnt" + OpcodeVecI8x16AllTrueName = "i8x16.all_true" + OpcodeVecI8x16BitMaskName = "i8x16.bitmask" + OpcodeVecI8x16NarrowI16x8SName = "i8x16.narrow_i16x8_s" + OpcodeVecI8x16NarrowI16x8UName = "i8x16.narrow_i16x8_u" + OpcodeVecI8x16ShlName = "i8x16.shl" + OpcodeVecI8x16ShrSName = "i8x16.shr_s" + OpcodeVecI8x16ShrUName = "i8x16.shr_u" + OpcodeVecI8x16AddName = "i8x16.add" + OpcodeVecI8x16AddSatSName = "i8x16.add_sat_s" + OpcodeVecI8x16AddSatUName = "i8x16.add_sat_u" + OpcodeVecI8x16SubName = "i8x16.sub" + OpcodeVecI8x16SubSatSName = "i8x16.sub_s" + OpcodeVecI8x16SubSatUName = "i8x16.sub_u" + OpcodeVecI8x16MinSName = "i8x16.min_s" + OpcodeVecI8x16MinUName = "i8x16.min_u" + OpcodeVecI8x16MaxSName = "i8x16.max_s" + OpcodeVecI8x16MaxUName = "i8x16.max_u" + OpcodeVecI8x16AvgrUName = "i8x16.avgr_u" + OpcodeVecI16x8ExtaddPairwiseI8x16SName = "i16x8.extadd_pairwise_i8x16_s" + OpcodeVecI16x8ExtaddPairwiseI8x16UName = "i16x8.extadd_pairwise_i8x16_u" + OpcodeVecI16x8AbsName = "i16x8.abs" + OpcodeVecI16x8NegName = "i16x8.neg" + OpcodeVecI16x8Q15mulrSatSName = "i16x8.q15mulr_sat_s" + OpcodeVecI16x8AllTrueName = "i16x8.all_true" + OpcodeVecI16x8BitMaskName = "i16x8.bitmask" + OpcodeVecI16x8NarrowI32x4SName = "i16x8.narrow_i32x4_s" + OpcodeVecI16x8NarrowI32x4UName = "i16x8.narrow_i32x4_u" + OpcodeVecI16x8ExtendLowI8x16SName = "i16x8.extend_low_i8x16_s" + OpcodeVecI16x8ExtendHighI8x16SName = "i16x8.extend_high_i8x16_s" + OpcodeVecI16x8ExtendLowI8x16UName = "i16x8.extend_low_i8x16_u" + OpcodeVecI16x8ExtendHighI8x16UName = "i16x8.extend_high_i8x16_u" + OpcodeVecI16x8ShlName = "i16x8.shl" + OpcodeVecI16x8ShrSName = "i16x8.shr_s" + OpcodeVecI16x8ShrUName = "i16x8.shr_u" + OpcodeVecI16x8AddName = "i16x8.add" + OpcodeVecI16x8AddSatSName = "i16x8.add_sat_s" + OpcodeVecI16x8AddSatUName = "i16x8.add_sat_u" + OpcodeVecI16x8SubName = "i16x8.sub" + OpcodeVecI16x8SubSatSName = "i16x8.sub_sat_s" + OpcodeVecI16x8SubSatUName = "i16x8.sub_sat_u" + OpcodeVecI16x8MulName = "i16x8.mul" + OpcodeVecI16x8MinSName = "i16x8.min_s" + OpcodeVecI16x8MinUName = "i16x8.min_u" + OpcodeVecI16x8MaxSName = "i16x8.max_s" + OpcodeVecI16x8MaxUName = "i16x8.max_u" + OpcodeVecI16x8AvgrUName = "i16x8.avgr_u" + OpcodeVecI16x8ExtMulLowI8x16SName = "i16x8.extmul_low_i8x16_s" + OpcodeVecI16x8ExtMulHighI8x16SName = "i16x8.extmul_high_i8x16_s" + OpcodeVecI16x8ExtMulLowI8x16UName = "i16x8.extmul_low_i8x16_u" + OpcodeVecI16x8ExtMulHighI8x16UName = "i16x8.extmul_high_i8x16_u" + OpcodeVecI32x4ExtaddPairwiseI16x8SName = "i32x4.extadd_pairwise_i16x8_s" + OpcodeVecI32x4ExtaddPairwiseI16x8UName = "i32x4.extadd_pairwise_i16x8_u" + OpcodeVecI32x4AbsName = "i32x4.abs" + OpcodeVecI32x4NegName = "i32x4.neg" + OpcodeVecI32x4AllTrueName = "i32x4.all_true" + OpcodeVecI32x4BitMaskName = "i32x4.bitmask" + OpcodeVecI32x4ExtendLowI16x8SName = "i32x4.extend_low_i16x8_s" + OpcodeVecI32x4ExtendHighI16x8SName = "i32x4.extend_high_i16x8_s" + OpcodeVecI32x4ExtendLowI16x8UName = "i32x4.extend_low_i16x8_u" + OpcodeVecI32x4ExtendHighI16x8UName = "i32x4.extend_high_i16x8_u" + OpcodeVecI32x4ShlName = "i32x4.shl" + OpcodeVecI32x4ShrSName = "i32x4.shr_s" + OpcodeVecI32x4ShrUName = "i32x4.shr_u" + OpcodeVecI32x4AddName = "i32x4.add" + OpcodeVecI32x4SubName = "i32x4.sub" + OpcodeVecI32x4MulName = "i32x4.mul" + OpcodeVecI32x4MinSName = "i32x4.min_s" + OpcodeVecI32x4MinUName = "i32x4.min_u" + OpcodeVecI32x4MaxSName = "i32x4.max_s" + OpcodeVecI32x4MaxUName = "i32x4.max_u" + OpcodeVecI32x4DotI16x8SName = "i32x4.dot_i16x8_s" + OpcodeVecI32x4ExtMulLowI16x8SName = "i32x4.extmul_low_i16x8_s" + OpcodeVecI32x4ExtMulHighI16x8SName = "i32x4.extmul_high_i16x8_s" + OpcodeVecI32x4ExtMulLowI16x8UName = "i32x4.extmul_low_i16x8_u" + OpcodeVecI32x4ExtMulHighI16x8UName = "i32x4.extmul_high_i16x8_u" + OpcodeVecI64x2AbsName = "i64x2.abs" + OpcodeVecI64x2NegName = "i64x2.neg" + OpcodeVecI64x2AllTrueName = "i64x2.all_true" + OpcodeVecI64x2BitMaskName = "i64x2.bitmask" + OpcodeVecI64x2ExtendLowI32x4SName = "i64x2.extend_low_i32x4_s" + OpcodeVecI64x2ExtendHighI32x4SName = "i64x2.extend_high_i32x4_s" + OpcodeVecI64x2ExtendLowI32x4UName = "i64x2.extend_low_i32x4_u" + OpcodeVecI64x2ExtendHighI32x4UName = "i64x2.extend_high_i32x4_u" + OpcodeVecI64x2ShlName = "i64x2.shl" + OpcodeVecI64x2ShrSName = "i64x2.shr_s" + OpcodeVecI64x2ShrUName = "i64x2.shr_u" + OpcodeVecI64x2AddName = "i64x2.add" + OpcodeVecI64x2SubName = "i64x2.sub" + OpcodeVecI64x2MulName = "i64x2.mul" + OpcodeVecI64x2ExtMulLowI32x4SName = "i64x2.extmul_low_i32x4_s" + OpcodeVecI64x2ExtMulHighI32x4SName = "i64x2.extmul_high_i32x4_s" + OpcodeVecI64x2ExtMulLowI32x4UName = "i64x2.extmul_low_i32x4_u" + OpcodeVecI64x2ExtMulHighI32x4UName = "i64x2.extmul_high_i32x4_u" + OpcodeVecF32x4CeilName = "f32x4.ceil" + OpcodeVecF32x4FloorName = "f32x4.floor" + OpcodeVecF32x4TruncName = "f32x4.trunc" + OpcodeVecF32x4NearestName = "f32x4.nearest" + OpcodeVecF32x4AbsName = "f32x4.abs" + OpcodeVecF32x4NegName = "f32x4.neg" + OpcodeVecF32x4SqrtName = "f32x4.sqrt" + OpcodeVecF32x4AddName = "f32x4.add" + OpcodeVecF32x4SubName = "f32x4.sub" + OpcodeVecF32x4MulName = "f32x4.mul" + OpcodeVecF32x4DivName = "f32x4.div" + OpcodeVecF32x4MinName = "f32x4.min" + OpcodeVecF32x4MaxName = "f32x4.max" + OpcodeVecF32x4PminName = "f32x4.pmin" + OpcodeVecF32x4PmaxName = "f32x4.pmax" + OpcodeVecF64x2CeilName = "f64x2.ceil" + OpcodeVecF64x2FloorName = "f64x2.floor" + OpcodeVecF64x2TruncName = "f64x2.trunc" + OpcodeVecF64x2NearestName = "f64x2.nearest" + OpcodeVecF64x2AbsName = "f64x2.abs" + OpcodeVecF64x2NegName = "f64x2.neg" + OpcodeVecF64x2SqrtName = "f64x2.sqrt" + OpcodeVecF64x2AddName = "f64x2.add" + OpcodeVecF64x2SubName = "f64x2.sub" + OpcodeVecF64x2MulName = "f64x2.mul" + OpcodeVecF64x2DivName = "f64x2.div" + OpcodeVecF64x2MinName = "f64x2.min" + OpcodeVecF64x2MaxName = "f64x2.max" + OpcodeVecF64x2PminName = "f64x2.pmin" + OpcodeVecF64x2PmaxName = "f64x2.pmax" + OpcodeVecI32x4TruncSatF32x4SName = "i32x4.trunc_sat_f32x4_s" + OpcodeVecI32x4TruncSatF32x4UName = "i32x4.trunc_sat_f32x4_u" + OpcodeVecF32x4ConvertI32x4SName = "f32x4.convert_i32x4_s" + OpcodeVecF32x4ConvertI32x4UName = "f32x4.convert_i32x4_u" + OpcodeVecI32x4TruncSatF64x2SZeroName = "i32x4.trunc_sat_f64x2_s_zero" + OpcodeVecI32x4TruncSatF64x2UZeroName = "i32x4.trunc_sat_f64x2_u_zero" + OpcodeVecF64x2ConvertLowI32x4SName = "f64x2.convert_low_i32x4_s" + OpcodeVecF64x2ConvertLowI32x4UName = "f64x2.convert_low_i32x4_u" + OpcodeVecF32x4DemoteF64x2ZeroName = "f32x4.demote_f64x2_zero" + OpcodeVecF64x2PromoteLowF32x4ZeroName = "f64x2.promote_low_f32x4" +) + +var vectorInstructionName = map[OpcodeVec]string{ + OpcodeVecV128Load: OpcodeVecV128LoadName, + OpcodeVecV128Load8x8s: OpcodeVecV128Load8x8SName, + OpcodeVecV128Load8x8u: OpcodeVecV128Load8x8UName, + OpcodeVecV128Load16x4s: OpcodeVecV128Load16x4SName, + OpcodeVecV128Load16x4u: OpcodeVecV128Load16x4UName, + OpcodeVecV128Load32x2s: OpcodeVecV128Load32x2SName, + OpcodeVecV128Load32x2u: OpcodeVecV128Load32x2UName, + OpcodeVecV128Load8Splat: OpcodeVecV128Load8SplatName, + OpcodeVecV128Load16Splat: OpcodeVecV128Load16SplatName, + OpcodeVecV128Load32Splat: OpcodeVecV128Load32SplatName, + OpcodeVecV128Load64Splat: OpcodeVecV128Load64SplatName, + OpcodeVecV128Load32zero: OpcodeVecV128Load32zeroName, + OpcodeVecV128Load64zero: OpcodeVecV128Load64zeroName, + OpcodeVecV128Store: OpcodeVecV128StoreName, + OpcodeVecV128Load8Lane: OpcodeVecV128Load8LaneName, + OpcodeVecV128Load16Lane: OpcodeVecV128Load16LaneName, + OpcodeVecV128Load32Lane: OpcodeVecV128Load32LaneName, + OpcodeVecV128Load64Lane: OpcodeVecV128Load64LaneName, + OpcodeVecV128Store8Lane: OpcodeVecV128Store8LaneName, + OpcodeVecV128Store16Lane: OpcodeVecV128Store16LaneName, + OpcodeVecV128Store32Lane: OpcodeVecV128Store32LaneName, + OpcodeVecV128Store64Lane: OpcodeVecV128Store64LaneName, + OpcodeVecV128Const: OpcodeVecV128ConstName, + OpcodeVecV128i8x16Shuffle: OpcodeVecV128i8x16ShuffleName, + OpcodeVecI8x16ExtractLaneS: OpcodeVecI8x16ExtractLaneSName, + OpcodeVecI8x16ExtractLaneU: OpcodeVecI8x16ExtractLaneUName, + OpcodeVecI8x16ReplaceLane: OpcodeVecI8x16ReplaceLaneName, + OpcodeVecI16x8ExtractLaneS: OpcodeVecI16x8ExtractLaneSName, + OpcodeVecI16x8ExtractLaneU: OpcodeVecI16x8ExtractLaneUName, + OpcodeVecI16x8ReplaceLane: OpcodeVecI16x8ReplaceLaneName, + OpcodeVecI32x4ExtractLane: OpcodeVecI32x4ExtractLaneName, + OpcodeVecI32x4ReplaceLane: OpcodeVecI32x4ReplaceLaneName, + OpcodeVecI64x2ExtractLane: OpcodeVecI64x2ExtractLaneName, + OpcodeVecI64x2ReplaceLane: OpcodeVecI64x2ReplaceLaneName, + OpcodeVecF32x4ExtractLane: OpcodeVecF32x4ExtractLaneName, + OpcodeVecF32x4ReplaceLane: OpcodeVecF32x4ReplaceLaneName, + OpcodeVecF64x2ExtractLane: OpcodeVecF64x2ExtractLaneName, + OpcodeVecF64x2ReplaceLane: OpcodeVecF64x2ReplaceLaneName, + OpcodeVecI8x16Swizzle: OpcodeVecI8x16SwizzleName, + OpcodeVecI8x16Splat: OpcodeVecI8x16SplatName, + OpcodeVecI16x8Splat: OpcodeVecI16x8SplatName, + OpcodeVecI32x4Splat: OpcodeVecI32x4SplatName, + OpcodeVecI64x2Splat: OpcodeVecI64x2SplatName, + OpcodeVecF32x4Splat: OpcodeVecF32x4SplatName, + OpcodeVecF64x2Splat: OpcodeVecF64x2SplatName, + OpcodeVecI8x16Eq: OpcodeVecI8x16EqName, + OpcodeVecI8x16Ne: OpcodeVecI8x16NeName, + OpcodeVecI8x16LtS: OpcodeVecI8x16LtSName, + OpcodeVecI8x16LtU: OpcodeVecI8x16LtUName, + OpcodeVecI8x16GtS: OpcodeVecI8x16GtSName, + OpcodeVecI8x16GtU: OpcodeVecI8x16GtUName, + OpcodeVecI8x16LeS: OpcodeVecI8x16LeSName, + OpcodeVecI8x16LeU: OpcodeVecI8x16LeUName, + OpcodeVecI8x16GeS: OpcodeVecI8x16GeSName, + OpcodeVecI8x16GeU: OpcodeVecI8x16GeUName, + OpcodeVecI16x8Eq: OpcodeVecI16x8EqName, + OpcodeVecI16x8Ne: OpcodeVecI16x8NeName, + OpcodeVecI16x8LtS: OpcodeVecI16x8LtSName, + OpcodeVecI16x8LtU: OpcodeVecI16x8LtUName, + OpcodeVecI16x8GtS: OpcodeVecI16x8GtSName, + OpcodeVecI16x8GtU: OpcodeVecI16x8GtUName, + OpcodeVecI16x8LeS: OpcodeVecI16x8LeSName, + OpcodeVecI16x8LeU: OpcodeVecI16x8LeUName, + OpcodeVecI16x8GeS: OpcodeVecI16x8GeSName, + OpcodeVecI16x8GeU: OpcodeVecI16x8GeUName, + OpcodeVecI32x4Eq: OpcodeVecI32x4EqName, + OpcodeVecI32x4Ne: OpcodeVecI32x4NeName, + OpcodeVecI32x4LtS: OpcodeVecI32x4LtSName, + OpcodeVecI32x4LtU: OpcodeVecI32x4LtUName, + OpcodeVecI32x4GtS: OpcodeVecI32x4GtSName, + OpcodeVecI32x4GtU: OpcodeVecI32x4GtUName, + OpcodeVecI32x4LeS: OpcodeVecI32x4LeSName, + OpcodeVecI32x4LeU: OpcodeVecI32x4LeUName, + OpcodeVecI32x4GeS: OpcodeVecI32x4GeSName, + OpcodeVecI32x4GeU: OpcodeVecI32x4GeUName, + OpcodeVecI64x2Eq: OpcodeVecI64x2EqName, + OpcodeVecI64x2Ne: OpcodeVecI64x2NeName, + OpcodeVecI64x2LtS: OpcodeVecI64x2LtSName, + OpcodeVecI64x2GtS: OpcodeVecI64x2GtSName, + OpcodeVecI64x2LeS: OpcodeVecI64x2LeSName, + OpcodeVecI64x2GeS: OpcodeVecI64x2GeSName, + OpcodeVecF32x4Eq: OpcodeVecF32x4EqName, + OpcodeVecF32x4Ne: OpcodeVecF32x4NeName, + OpcodeVecF32x4Lt: OpcodeVecF32x4LtName, + OpcodeVecF32x4Gt: OpcodeVecF32x4GtName, + OpcodeVecF32x4Le: OpcodeVecF32x4LeName, + OpcodeVecF32x4Ge: OpcodeVecF32x4GeName, + OpcodeVecF64x2Eq: OpcodeVecF64x2EqName, + OpcodeVecF64x2Ne: OpcodeVecF64x2NeName, + OpcodeVecF64x2Lt: OpcodeVecF64x2LtName, + OpcodeVecF64x2Gt: OpcodeVecF64x2GtName, + OpcodeVecF64x2Le: OpcodeVecF64x2LeName, + OpcodeVecF64x2Ge: OpcodeVecF64x2GeName, + OpcodeVecV128Not: OpcodeVecV128NotName, + OpcodeVecV128And: OpcodeVecV128AndName, + OpcodeVecV128AndNot: OpcodeVecV128AndNotName, + OpcodeVecV128Or: OpcodeVecV128OrName, + OpcodeVecV128Xor: OpcodeVecV128XorName, + OpcodeVecV128Bitselect: OpcodeVecV128BitselectName, + OpcodeVecV128AnyTrue: OpcodeVecV128AnyTrueName, + OpcodeVecI8x16Abs: OpcodeVecI8x16AbsName, + OpcodeVecI8x16Neg: OpcodeVecI8x16NegName, + OpcodeVecI8x16Popcnt: OpcodeVecI8x16PopcntName, + OpcodeVecI8x16AllTrue: OpcodeVecI8x16AllTrueName, + OpcodeVecI8x16BitMask: OpcodeVecI8x16BitMaskName, + OpcodeVecI8x16NarrowI16x8S: OpcodeVecI8x16NarrowI16x8SName, + OpcodeVecI8x16NarrowI16x8U: OpcodeVecI8x16NarrowI16x8UName, + OpcodeVecI8x16Shl: OpcodeVecI8x16ShlName, + OpcodeVecI8x16ShrS: OpcodeVecI8x16ShrSName, + OpcodeVecI8x16ShrU: OpcodeVecI8x16ShrUName, + OpcodeVecI8x16Add: OpcodeVecI8x16AddName, + OpcodeVecI8x16AddSatS: OpcodeVecI8x16AddSatSName, + OpcodeVecI8x16AddSatU: OpcodeVecI8x16AddSatUName, + OpcodeVecI8x16Sub: OpcodeVecI8x16SubName, + OpcodeVecI8x16SubSatS: OpcodeVecI8x16SubSatSName, + OpcodeVecI8x16SubSatU: OpcodeVecI8x16SubSatUName, + OpcodeVecI8x16MinS: OpcodeVecI8x16MinSName, + OpcodeVecI8x16MinU: OpcodeVecI8x16MinUName, + OpcodeVecI8x16MaxS: OpcodeVecI8x16MaxSName, + OpcodeVecI8x16MaxU: OpcodeVecI8x16MaxUName, + OpcodeVecI8x16AvgrU: OpcodeVecI8x16AvgrUName, + OpcodeVecI16x8ExtaddPairwiseI8x16S: OpcodeVecI16x8ExtaddPairwiseI8x16SName, + OpcodeVecI16x8ExtaddPairwiseI8x16U: OpcodeVecI16x8ExtaddPairwiseI8x16UName, + OpcodeVecI16x8Abs: OpcodeVecI16x8AbsName, + OpcodeVecI16x8Neg: OpcodeVecI16x8NegName, + OpcodeVecI16x8Q15mulrSatS: OpcodeVecI16x8Q15mulrSatSName, + OpcodeVecI16x8AllTrue: OpcodeVecI16x8AllTrueName, + OpcodeVecI16x8BitMask: OpcodeVecI16x8BitMaskName, + OpcodeVecI16x8NarrowI32x4S: OpcodeVecI16x8NarrowI32x4SName, + OpcodeVecI16x8NarrowI32x4U: OpcodeVecI16x8NarrowI32x4UName, + OpcodeVecI16x8ExtendLowI8x16S: OpcodeVecI16x8ExtendLowI8x16SName, + OpcodeVecI16x8ExtendHighI8x16S: OpcodeVecI16x8ExtendHighI8x16SName, + OpcodeVecI16x8ExtendLowI8x16U: OpcodeVecI16x8ExtendLowI8x16UName, + OpcodeVecI16x8ExtendHighI8x16U: OpcodeVecI16x8ExtendHighI8x16UName, + OpcodeVecI16x8Shl: OpcodeVecI16x8ShlName, + OpcodeVecI16x8ShrS: OpcodeVecI16x8ShrSName, + OpcodeVecI16x8ShrU: OpcodeVecI16x8ShrUName, + OpcodeVecI16x8Add: OpcodeVecI16x8AddName, + OpcodeVecI16x8AddSatS: OpcodeVecI16x8AddSatSName, + OpcodeVecI16x8AddSatU: OpcodeVecI16x8AddSatUName, + OpcodeVecI16x8Sub: OpcodeVecI16x8SubName, + OpcodeVecI16x8SubSatS: OpcodeVecI16x8SubSatSName, + OpcodeVecI16x8SubSatU: OpcodeVecI16x8SubSatUName, + OpcodeVecI16x8Mul: OpcodeVecI16x8MulName, + OpcodeVecI16x8MinS: OpcodeVecI16x8MinSName, + OpcodeVecI16x8MinU: OpcodeVecI16x8MinUName, + OpcodeVecI16x8MaxS: OpcodeVecI16x8MaxSName, + OpcodeVecI16x8MaxU: OpcodeVecI16x8MaxUName, + OpcodeVecI16x8AvgrU: OpcodeVecI16x8AvgrUName, + OpcodeVecI16x8ExtMulLowI8x16S: OpcodeVecI16x8ExtMulLowI8x16SName, + OpcodeVecI16x8ExtMulHighI8x16S: OpcodeVecI16x8ExtMulHighI8x16SName, + OpcodeVecI16x8ExtMulLowI8x16U: OpcodeVecI16x8ExtMulLowI8x16UName, + OpcodeVecI16x8ExtMulHighI8x16U: OpcodeVecI16x8ExtMulHighI8x16UName, + OpcodeVecI32x4ExtaddPairwiseI16x8S: OpcodeVecI32x4ExtaddPairwiseI16x8SName, + OpcodeVecI32x4ExtaddPairwiseI16x8U: OpcodeVecI32x4ExtaddPairwiseI16x8UName, + OpcodeVecI32x4Abs: OpcodeVecI32x4AbsName, + OpcodeVecI32x4Neg: OpcodeVecI32x4NegName, + OpcodeVecI32x4AllTrue: OpcodeVecI32x4AllTrueName, + OpcodeVecI32x4BitMask: OpcodeVecI32x4BitMaskName, + OpcodeVecI32x4ExtendLowI16x8S: OpcodeVecI32x4ExtendLowI16x8SName, + OpcodeVecI32x4ExtendHighI16x8S: OpcodeVecI32x4ExtendHighI16x8SName, + OpcodeVecI32x4ExtendLowI16x8U: OpcodeVecI32x4ExtendLowI16x8UName, + OpcodeVecI32x4ExtendHighI16x8U: OpcodeVecI32x4ExtendHighI16x8UName, + OpcodeVecI32x4Shl: OpcodeVecI32x4ShlName, + OpcodeVecI32x4ShrS: OpcodeVecI32x4ShrSName, + OpcodeVecI32x4ShrU: OpcodeVecI32x4ShrUName, + OpcodeVecI32x4Add: OpcodeVecI32x4AddName, + OpcodeVecI32x4Sub: OpcodeVecI32x4SubName, + OpcodeVecI32x4Mul: OpcodeVecI32x4MulName, + OpcodeVecI32x4MinS: OpcodeVecI32x4MinSName, + OpcodeVecI32x4MinU: OpcodeVecI32x4MinUName, + OpcodeVecI32x4MaxS: OpcodeVecI32x4MaxSName, + OpcodeVecI32x4MaxU: OpcodeVecI32x4MaxUName, + OpcodeVecI32x4DotI16x8S: OpcodeVecI32x4DotI16x8SName, + OpcodeVecI32x4ExtMulLowI16x8S: OpcodeVecI32x4ExtMulLowI16x8SName, + OpcodeVecI32x4ExtMulHighI16x8S: OpcodeVecI32x4ExtMulHighI16x8SName, + OpcodeVecI32x4ExtMulLowI16x8U: OpcodeVecI32x4ExtMulLowI16x8UName, + OpcodeVecI32x4ExtMulHighI16x8U: OpcodeVecI32x4ExtMulHighI16x8UName, + OpcodeVecI64x2Abs: OpcodeVecI64x2AbsName, + OpcodeVecI64x2Neg: OpcodeVecI64x2NegName, + OpcodeVecI64x2AllTrue: OpcodeVecI64x2AllTrueName, + OpcodeVecI64x2BitMask: OpcodeVecI64x2BitMaskName, + OpcodeVecI64x2ExtendLowI32x4S: OpcodeVecI64x2ExtendLowI32x4SName, + OpcodeVecI64x2ExtendHighI32x4S: OpcodeVecI64x2ExtendHighI32x4SName, + OpcodeVecI64x2ExtendLowI32x4U: OpcodeVecI64x2ExtendLowI32x4UName, + OpcodeVecI64x2ExtendHighI32x4U: OpcodeVecI64x2ExtendHighI32x4UName, + OpcodeVecI64x2Shl: OpcodeVecI64x2ShlName, + OpcodeVecI64x2ShrS: OpcodeVecI64x2ShrSName, + OpcodeVecI64x2ShrU: OpcodeVecI64x2ShrUName, + OpcodeVecI64x2Add: OpcodeVecI64x2AddName, + OpcodeVecI64x2Sub: OpcodeVecI64x2SubName, + OpcodeVecI64x2Mul: OpcodeVecI64x2MulName, + OpcodeVecI64x2ExtMulLowI32x4S: OpcodeVecI64x2ExtMulLowI32x4SName, + OpcodeVecI64x2ExtMulHighI32x4S: OpcodeVecI64x2ExtMulHighI32x4SName, + OpcodeVecI64x2ExtMulLowI32x4U: OpcodeVecI64x2ExtMulLowI32x4UName, + OpcodeVecI64x2ExtMulHighI32x4U: OpcodeVecI64x2ExtMulHighI32x4UName, + OpcodeVecF32x4Ceil: OpcodeVecF32x4CeilName, + OpcodeVecF32x4Floor: OpcodeVecF32x4FloorName, + OpcodeVecF32x4Trunc: OpcodeVecF32x4TruncName, + OpcodeVecF32x4Nearest: OpcodeVecF32x4NearestName, + OpcodeVecF32x4Abs: OpcodeVecF32x4AbsName, + OpcodeVecF32x4Neg: OpcodeVecF32x4NegName, + OpcodeVecF32x4Sqrt: OpcodeVecF32x4SqrtName, + OpcodeVecF32x4Add: OpcodeVecF32x4AddName, + OpcodeVecF32x4Sub: OpcodeVecF32x4SubName, + OpcodeVecF32x4Mul: OpcodeVecF32x4MulName, + OpcodeVecF32x4Div: OpcodeVecF32x4DivName, + OpcodeVecF32x4Min: OpcodeVecF32x4MinName, + OpcodeVecF32x4Max: OpcodeVecF32x4MaxName, + OpcodeVecF32x4Pmin: OpcodeVecF32x4PminName, + OpcodeVecF32x4Pmax: OpcodeVecF32x4PmaxName, + OpcodeVecF64x2Ceil: OpcodeVecF64x2CeilName, + OpcodeVecF64x2Floor: OpcodeVecF64x2FloorName, + OpcodeVecF64x2Trunc: OpcodeVecF64x2TruncName, + OpcodeVecF64x2Nearest: OpcodeVecF64x2NearestName, + OpcodeVecF64x2Abs: OpcodeVecF64x2AbsName, + OpcodeVecF64x2Neg: OpcodeVecF64x2NegName, + OpcodeVecF64x2Sqrt: OpcodeVecF64x2SqrtName, + OpcodeVecF64x2Add: OpcodeVecF64x2AddName, + OpcodeVecF64x2Sub: OpcodeVecF64x2SubName, + OpcodeVecF64x2Mul: OpcodeVecF64x2MulName, + OpcodeVecF64x2Div: OpcodeVecF64x2DivName, + OpcodeVecF64x2Min: OpcodeVecF64x2MinName, + OpcodeVecF64x2Max: OpcodeVecF64x2MaxName, + OpcodeVecF64x2Pmin: OpcodeVecF64x2PminName, + OpcodeVecF64x2Pmax: OpcodeVecF64x2PmaxName, + OpcodeVecI32x4TruncSatF32x4S: OpcodeVecI32x4TruncSatF32x4SName, + OpcodeVecI32x4TruncSatF32x4U: OpcodeVecI32x4TruncSatF32x4UName, + OpcodeVecF32x4ConvertI32x4S: OpcodeVecF32x4ConvertI32x4SName, + OpcodeVecF32x4ConvertI32x4U: OpcodeVecF32x4ConvertI32x4UName, + OpcodeVecI32x4TruncSatF64x2SZero: OpcodeVecI32x4TruncSatF64x2SZeroName, + OpcodeVecI32x4TruncSatF64x2UZero: OpcodeVecI32x4TruncSatF64x2UZeroName, + OpcodeVecF64x2ConvertLowI32x4S: OpcodeVecF64x2ConvertLowI32x4SName, + OpcodeVecF64x2ConvertLowI32x4U: OpcodeVecF64x2ConvertLowI32x4UName, + OpcodeVecF32x4DemoteF64x2Zero: OpcodeVecF32x4DemoteF64x2ZeroName, + OpcodeVecF64x2PromoteLowF32x4Zero: OpcodeVecF64x2PromoteLowF32x4ZeroName, +} + +// VectorInstructionName returns the instruction name corresponding to the vector Opcode. +func VectorInstructionName(oc OpcodeVec) (ret string) { + return vectorInstructionName[oc] +} diff --git a/vendor/github.com/tetratelabs/wabin/wasm/memory.go b/vendor/github.com/tetratelabs/wabin/wasm/memory.go new file mode 100644 index 00000000000..ed18dee7e01 --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/wasm/memory.go @@ -0,0 +1,45 @@ +package wasm + +import ( + "fmt" +) + +const ( + // MemoryPageSize is the unit of memory length in WebAssembly, + // and is defined as 2^16 = 65536. + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0 + MemoryPageSize = uint32(65536) + // MemoryPageSizeInBits satisfies the relation: "1 << MemoryPageSizeInBits == MemoryPageSize". + MemoryPageSizeInBits = 16 +) + +// MemoryPagesToBytesNum converts the given pages into the number of bytes contained in these pages. +func MemoryPagesToBytesNum(pages uint32) (bytesNum uint64) { + return uint64(pages) << MemoryPageSizeInBits +} + +// PagesToUnitOfBytes converts the pages to a human-readable form similar to what's specified. Ex. 1 -> "64Ki" +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0 +func PagesToUnitOfBytes(pages uint32) string { + k := pages * 64 + if k < 1024 { + return fmt.Sprintf("%d Ki", k) + } + m := k / 1024 + if m < 1024 { + return fmt.Sprintf("%d Mi", m) + } + g := m / 1024 + if g < 1024 { + return fmt.Sprintf("%d Gi", g) + } + return fmt.Sprintf("%d Ti", g/1024) +} + +// Below are raw functions used to implement the api.Memory API: + +// memoryBytesNumToPages converts the given number of bytes into the number of pages. +func memoryBytesNumToPages(bytesNum uint64) (pages uint32) { + return uint32(bytesNum >> MemoryPageSizeInBits) +} diff --git a/vendor/github.com/tetratelabs/wabin/wasm/module.go b/vendor/github.com/tetratelabs/wabin/wasm/module.go new file mode 100644 index 00000000000..dcefb737662 --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/wasm/module.go @@ -0,0 +1,496 @@ +package wasm + +import ( + "bytes" + "fmt" +) + +// DecodeModule parses the WebAssembly Binary Format (%.wasm) into a Module. This function returns when the input is +// exhausted or an error occurs. The result can be initialized for use via Store.Instantiate. +// +// Here's a description of the return values: +// * result is the module parsed or nil on error +// * err is a FormatError invoking the parser, dangling block comments or unexpected characters. +// See binary.DecodeModule and text.DecodeModule +type DecodeModule func( + wasm []byte, + features CoreFeatures, + memorySizer func(minPages uint32, maxPages *uint32) (min, capacity, max uint32), +) (result *Module, err error) + +// EncodeModule encodes the given module into a byte slice depending on the format of the implementation. +// See binary.EncodeModule +type EncodeModule func(m *Module) (bytes []byte) + +// The wazero specific limitation described at RATIONALE.md. +// TL;DR; We multiply by 8 (to get offsets in bytes) and the multiplication result must be less than 32bit max +const ( + MaximumGlobals = uint32(1 << 27) + MaximumFunctionIndex = uint32(1 << 27) + MaximumTableIndex = uint32(1 << 27) +) + +// Module is a WebAssembly binary representation. +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#modules%E2%91%A8 +// +// Differences from the specification: +// * NameSection is the only key ("name") decoded from the SectionIDCustom. +// * ExportSection is represented as a map for lookup convenience. +// * Code.GoFunc is contains any go `func`. It may be present when Code.Body is not. +type Module struct { + // TypeSection contains the unique FunctionType of functions imported or defined in this module. + // + // Note: Currently, there is no type ambiguity in the index as WebAssembly 1.0 only defines function type. + // In the future, other types may be introduced to support CoreFeatures such as module linking. + // + // Note: In the Binary Format, this is SectionIDType. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#types%E2%91%A0%E2%91%A0 + TypeSection []*FunctionType + + // ImportSection contains imported functions, tables, memories or globals required for instantiation + // (Store.Instantiate). + // + // Note: there are no unique constraints relating to the two-level namespace of Import.Module and Import.Name. + // + // Note: In the Binary Format, this is SectionIDImport. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#import-section%E2%91%A0 + ImportSection []*Import + + // FunctionSection contains the index in TypeSection of each function defined in this module. + // + // Note: The function Index namespace begins with imported functions and ends with those defined in this module. + // For example, if there are two imported functions and one defined in this module, the function Index 3 is defined + // in this module at FunctionSection[0]. + // + // Note: FunctionSection is index correlated with the CodeSection. If given the same position, ex. 2, a function + // type is at TypeSection[FunctionSection[2]], while its locals and body are at CodeSection[2]. + // + // Note: In the Binary Format, this is SectionIDFunction. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-section%E2%91%A0 + FunctionSection []Index + + // TableSection contains each table defined in this module. + // + // Note: The table Index namespace begins with imported tables and ends with those defined in this module. + // For example, if there are two imported tables and one defined in this module, the table Index 3 is defined in + // this module at TableSection[0]. + // + // Note: Version of the WebAssembly spec allows at most one table definition per module, so the + // length of the TableSection can be zero or one, and can only be one if there is no imported table. + // + // Note: In the Binary Format, this is SectionIDTable. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#table-section%E2%91%A0 + TableSection []*Table + + // MemorySection contains each memory defined in this module. + // + // Note: The memory Index namespace begins with imported memories and ends with those defined in this module. + // For example, if there are two imported memories and one defined in this module, the memory Index 3 is defined in + // this module at TableSection[0]. + // + // Note: Version of the WebAssembly spec allows at most one memory definition per module, so the + // length of the MemorySection can be zero or one, and can only be one if there is no imported memory. + // + // Note: In the Binary Format, this is SectionIDMemory. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-section%E2%91%A0 + MemorySection *Memory + + // GlobalSection contains each global defined in this module. + // + // Global indexes are offset by any imported globals because the global index space begins with imports, followed by + // ones defined in this module. For example, if there are two imported globals and three defined in this module, the + // global at index 3 is defined in this module at GlobalSection[0]. + // + // Note: In the Binary Format, this is SectionIDGlobal. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-section%E2%91%A0 + GlobalSection []*Global + + // ExportSection contains each export defined in this module. + // + // Note: In the Binary Format, this is SectionIDExport. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A0 + ExportSection []*Export + + // StartSection is the index of a function to call before returning from Store.Instantiate. + // + // Note: The index here is not the position in the FunctionSection, rather in the function index namespace, which + // begins with imported functions. + // + // Note: In the Binary Format, this is SectionIDStart. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#start-section%E2%91%A0 + StartSection *Index + + // Note: In the Binary Format, this is SectionIDElement. + ElementSection []*ElementSegment + + // CodeSection is index-correlated with FunctionSection and contains each + // function's locals and body. + // + // When present, the HostFunctionSection of the same index must be nil. + // + // Note: In the Binary Format, this is SectionIDCode. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#code-section%E2%91%A0 + CodeSection []*Code + + // Note: In the Binary Format, this is SectionIDData. + DataSection []*DataSegment + + // DataCountSection is the optional section and holds the number of data segments in the data section. + // + // Note: This may exist in WebAssembly 2.0 or WebAssembly 1.0 with FeatureBulkMemoryOperations. + // See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-count-section + // See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#bulk-memory-and-table-instructions + DataCountSection *uint32 + + // NameSection is set when the SectionIDCustom "name" was successfully decoded from the binary format. + // + // Note: This is the only SectionIDCustom defined in the WebAssembly Binary Format. + // Others are read into CustomSections. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0 + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0 + NameSection *NameSection + + // CustomSections are set when the SectionIDCustom other than "name" were successfully decoded from the binary format. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0 + CustomSections []*CustomSection +} + +// Index is the offset in an index namespace, not necessarily an absolute position in a Module section. This is because +// index namespaces are often preceded by a corresponding type in the Module.ImportSection. +// +// For example, the function index namespace starts with any ExternTypeFunc in the Module.ImportSection followed by +// the Module.FunctionSection +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-index +type Index = uint32 + +// FunctionType is a possibly empty function signature. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-types%E2%91%A0 +type FunctionType struct { + // Params are the possibly empty sequence of value types accepted by a function with this signature. + Params []ValueType + + // Results are the possibly empty sequence of value types returned by a function with this signature. + // + // Note: In WebAssembly 1.0 (20191205), there can be at most one result. + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#result-types%E2%91%A0 + Results []ValueType +} + +// EqualsSignature returns true if the function type has the same parameters and results. +func (f *FunctionType) EqualsSignature(params []ValueType, results []ValueType) bool { + return bytes.Equal(f.Params, params) && bytes.Equal(f.Results, results) +} + +// Import is the binary representation of an import indicated by Type +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-import +type Import struct { + Type ExternType + // Module is the possibly empty primary namespace of this import + Module string + // Module is the possibly empty secondary namespace of this import + Name string + // DescFunc is the index in Module.TypeSection when Type equals ExternTypeFunc + DescFunc Index + // DescTable is the inlined Table when Type equals ExternTypeTable + DescTable *Table + // DescMem is the inlined Memory when Type equals ExternTypeMemory + DescMem *Memory + // DescGlobal is the inlined GlobalType when Type equals ExternTypeGlobal + DescGlobal *GlobalType +} + +// Memory describes the limits of pages (64KB) in a memory. +type Memory struct { + Min, Max uint32 + // IsMaxEncoded true if the Max is encoded in the original source (binary or text). + IsMaxEncoded bool +} + +// Table describes the limits of elements and its type in a table. +type Table struct { + Min uint32 + Max *uint32 + Type RefType +} + +// RefType is either RefTypeFuncref or RefTypeExternref as of WebAssembly core 2.0. +type RefType = byte + +const ( + // RefTypeFuncref represents a reference to a function. + RefTypeFuncref = ValueTypeFuncref + // RefTypeExternref represents a reference to a host object, which is not currently supported in wazero. + RefTypeExternref = ValueTypeExternref +) + +func RefTypeName(t RefType) (ret string) { + switch t { + case RefTypeFuncref: + ret = "funcref" + case RefTypeExternref: + ret = "externref" + default: + ret = fmt.Sprintf("unknown(0x%x)", t) + } + return +} + +// ElementMode represents a mode of element segment which is either active, passive or declarative. +// +// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/syntax/modules.html#element-segments +type ElementMode = byte + +const ( + // ElementModeActive is the mode which requires the runtime to initialize table with the contents in .Init field combined with OffsetExpr. + ElementModeActive ElementMode = iota + // ElementModePassive is the mode which doesn't require the runtime to initialize table, and only used with OpcodeTableInitName. + ElementModePassive + // ElementModeDeclarative is introduced in reference-types proposal which can be used to declare function indexes used by OpcodeRefFunc. + ElementModeDeclarative +) + +// ElementSegment are initialization instructions for a TableInstance +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-elem +type ElementSegment struct { + // OffsetExpr returns the table element offset to apply to Init indices. + // Note: This can be validated prior to instantiation unless it includes OpcodeGlobalGet (an imported global). + // Note: This is only set when Mode is active. + OffsetExpr *ConstantExpression + + // TableIndex is the table's index to which this element segment is applied. + // Note: This is used if and only if the Mode is active. + TableIndex Index + + // Followings are set/used regardless of the Mode. + + // Init indices are (nullable) table elements where each index is the function index by which the module initialize the table. + Init []*Index + + // Type holds the type of this element segment, which is the RefType in WebAssembly 2.0. + Type RefType + + // Mode is the mode of this element segment. + Mode ElementMode +} + +// TableInstance represents a table of (RefTypeFuncref) elements in a module. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#table-instances%E2%91%A0 +type TableInstance struct { + // References holds references whose type is either RefTypeFuncref or RefTypeExternref (unsupported). + // + // Currently, only function references are supported. + References []Reference + + // Min is the minimum (function) elements in this table and cannot grow to accommodate ElementSegment. + Min uint32 + + // Max if present is the maximum (function) elements in this table, or nil if unbounded. + Max *uint32 + + // Type is either RefTypeFuncref or RefTypeExternRef. + Type RefType +} + +// ElementInstance represents an element instance in a module. +// +// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/runtime.html#element-instances +type ElementInstance struct { + // References holds references whose type is either RefTypeFuncref or RefTypeExternref (unsupported). + References []Reference + // Type is the RefType of the references in this instance's References. + Type RefType +} + +// Reference is the runtime representation of RefType which is either RefTypeFuncref or RefTypeExternref. +type Reference = uintptr + +type GlobalType struct { + ValType ValueType + Mutable bool +} + +type Global struct { + Type *GlobalType + Init *ConstantExpression +} + +type ConstantExpression struct { + Opcode Opcode + Data []byte +} + +// Export is the binary representation of an export indicated by Type +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-export +type Export struct { + Type ExternType + + // Name is what the host refers to this definition as. + Name string + + // Index is the index of the definition to export, the index namespace is by Type + // Ex. If ExternTypeFunc, this is a position in the function index namespace. + Index Index +} + +// Code is an entry in the Module.CodeSection containing the locals and body of the function. +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-code +type Code struct { + // LocalTypes are any function-scoped variables in insertion order. + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-local + LocalTypes []ValueType + + // Body is a sequence of expressions ending in OpcodeEnd + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-expr + Body []byte +} + +type DataSegment struct { + OffsetExpression *ConstantExpression + Init []byte +} + +// NameSection represent the known custom name subsections defined in the WebAssembly Binary Format +// +// Note: This can be nil if no names were decoded for any reason including configuration. +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0 +type NameSection struct { + // ModuleName is the symbolic identifier for a module. Ex. math + // + // Note: This can be empty for any reason including configuration. + ModuleName string + + // FunctionNames is an association of a function index to its symbolic identifier. Ex. add + // + // * the key (idx) is in the function namespace, where module defined functions are preceded by imported ones. + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#functions%E2%91%A7 + // + // Ex. Assuming the below text format is the second import, you would expect FunctionNames[1] = "mul" + // (import "Math" "Mul" (func $mul (param $x f32) (param $y f32) (result f32))) + // + // Note: FunctionNames are only used for debugging. At runtime, functions are called based on raw numeric index. + // Note: This can be nil for any reason including configuration. + FunctionNames NameMap + + // LocalNames contains symbolic names for function parameters or locals that have one. + // + // Note: In the Text Format, function local names can inherit parameter names from their type. Ex. + // * (module (import (func (param $x i32) (param i32))) (func (type 0))) = [{0, {x,0}}] + // * (module (import (func (param i32) (param $y i32))) (func (type 0) (local $z i32))) = [0, [{y,1},{z,2}]] + // * (module (func (param $x i32) (local $y i32) (local $z i32))) = [{x,0},{y,1},{z,2}] + // + // Note: LocalNames are only used for debugging. At runtime, locals are called based on raw numeric index. + // Note: This can be nil for any reason including configuration. + LocalNames IndirectNameMap +} + +// CustomSection contains the name and raw data of a custom section. +type CustomSection struct { + Name string + Data []byte +} + +// NameMap associates an index with any associated names. +// +// Note: Often the index namespace bridges multiple sections. For example, the function index namespace starts with any +// ExternTypeFunc in the Module.ImportSection followed by the Module.FunctionSection +// +// Note: NameMap is unique by NameAssoc.Index, but NameAssoc.Name needn't be unique. +// Note: When encoding in the Binary format, this must be ordered by NameAssoc.Index +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namemap +type NameMap []*NameAssoc + +type NameAssoc struct { + Index Index + Name string +} + +// IndirectNameMap associates an index with an association of names. +// +// Note: IndirectNameMap is unique by NameMapAssoc.Index, but NameMapAssoc.NameMap needn't be unique. +// Note: When encoding in the Binary format, this must be ordered by NameMapAssoc.Index +// https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-indirectnamemap +type IndirectNameMap []*NameMapAssoc + +type NameMapAssoc struct { + Index Index + NameMap NameMap +} + +// SectionID identifies the sections of a Module in the WebAssembly Binary Format. +// +// Note: these are defined in the wasm package, instead of the binary package, as a key per section is needed regardless +// of format, and deferring to the binary type avoids confusion. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0 +type SectionID = byte + +const ( + // SectionIDCustom includes the standard defined NameSection and possibly others not defined in the standard. + SectionIDCustom SectionID = iota // don't add anything not in https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0 + SectionIDType + SectionIDImport + SectionIDFunction + SectionIDTable + SectionIDMemory + SectionIDGlobal + SectionIDExport + SectionIDStart + SectionIDElement + SectionIDCode + SectionIDData + + // SectionIDDataCount may exist in WebAssembly 2.0 or WebAssembly 1.0 with FeatureBulkMemoryOperations enabled. + // + // See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-count-section + // See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#bulk-memory-and-table-instructions + SectionIDDataCount +) + +// SectionIDName returns the canonical name of a module section. +// https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0 +func SectionIDName(sectionID SectionID) string { + switch sectionID { + case SectionIDCustom: + return "custom" + case SectionIDType: + return "type" + case SectionIDImport: + return "import" + case SectionIDFunction: + return "function" + case SectionIDTable: + return "table" + case SectionIDMemory: + return "memory" + case SectionIDGlobal: + return "global" + case SectionIDExport: + return "export" + case SectionIDStart: + return "start" + case SectionIDElement: + return "element" + case SectionIDCode: + return "code" + case SectionIDData: + return "data" + case SectionIDDataCount: + return "data_count" + } + return "unknown" +} diff --git a/vendor/github.com/tetratelabs/wabin/wasm/types.go b/vendor/github.com/tetratelabs/wabin/wasm/types.go new file mode 100644 index 00000000000..9bfffd1481a --- /dev/null +++ b/vendor/github.com/tetratelabs/wabin/wasm/types.go @@ -0,0 +1,180 @@ +package wasm + +import ( + "fmt" + "math" +) + +// ValueType describes a numeric type used in Web Assembly 1.0 (20191205). For example, Function parameters and results are +// only definable as a value type. +// +// The following describes how to convert between Wasm and Golang types: +// +// - ValueTypeI32 - uint64(uint32,int32) +// - ValueTypeI64 - uint64(int64) +// - ValueTypeF32 - EncodeF32 DecodeF32 from float32 +// - ValueTypeF64 - EncodeF64 DecodeF64 from float64 +// - ValueTypeExternref - uintptr(unsafe.Pointer(p)) where p is any pointer type in Go (e.g. *string) +// +// Ex. Given a Text Format type use (param i64) (result i64), no conversion is necessary. +// +// results, _ := fn(ctx, input) +// result := result[0] +// +// Ex. Given a Text Format type use (param f64) (result f64), conversion is necessary. +// +// results, _ := fn(ctx, api.EncodeF64(input)) +// result := api.DecodeF64(result[0]) +// +// Note: This is a type alias as it is easier to encode and decode in the binary format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-valtype +type ValueType = byte + +const ( + // ValueTypeI32 is a 32-bit integer. + ValueTypeI32 ValueType = 0x7f + // ValueTypeI64 is a 64-bit integer. + ValueTypeI64 ValueType = 0x7e + // ValueTypeF32 is a 32-bit floating point number. + ValueTypeF32 ValueType = 0x7d + // ValueTypeF64 is a 64-bit floating point number. + ValueTypeF64 ValueType = 0x7c + + // ValueTypeExternref is an externref type. + // + // Note: in wazero, externref type value are opaque raw 64-bit pointers, + // and the ValueTypeExternref type in the signature will be translated as + // uintptr in wazero's API level. + // + // For example, given the import function: + // (func (import "env" "f") (param externref) (result externref)) + // + // This can be defined in Go as: + // r.NewModuleBuilder("env").ExportFunctions(map[string]interface{}{ + // "f": func(externref uintptr) (resultExternRef uintptr) { return }, + // }) + // + // Note: The usage of this type is toggled with WithFeatureBulkMemoryOperations. + ValueTypeExternref ValueType = 0x6f + + ValueTypeV128 ValueType = 0x7b + ValueTypeFuncref ValueType = 0x70 +) + +// ValueTypeName returns the type name of the given ValueType as a string. +// These type names match the names used in the WebAssembly text format. +// +// Note: This returns "unknown", if an undefined ValueType value is passed. +func ValueTypeName(t ValueType) string { + switch t { + case ValueTypeI32: + return "i32" + case ValueTypeI64: + return "i64" + case ValueTypeF32: + return "f32" + case ValueTypeF64: + return "f64" + case ValueTypeExternref: + return "externref" + case ValueTypeFuncref: + return "funcref" + case ValueTypeV128: + return "v128" + } + return "unknown" +} + +// ExternType classifies imports and exports with their respective types. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#external-types%E2%91%A0 +type ExternType = byte + +const ( + ExternTypeFunc ExternType = 0x00 + ExternTypeTable ExternType = 0x01 + ExternTypeMemory ExternType = 0x02 + ExternTypeGlobal ExternType = 0x03 +) + +// The below are exported to consolidate parsing behavior for external types. +const ( + // ExternTypeFuncName is the name of the WebAssembly Text Format field for ExternTypeFunc. + ExternTypeFuncName = "func" + // ExternTypeTableName is the name of the WebAssembly Text Format field for ExternTypeTable. + ExternTypeTableName = "table" + // ExternTypeMemoryName is the name of the WebAssembly Text Format field for ExternTypeMemory. + ExternTypeMemoryName = "memory" + // ExternTypeGlobalName is the name of the WebAssembly Text Format field for ExternTypeGlobal. + ExternTypeGlobalName = "global" +) + +// ExternTypeName returns the name of the WebAssembly Text Format field of the given type. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A4 +func ExternTypeName(et ExternType) string { + switch et { + case ExternTypeFunc: + return ExternTypeFuncName + case ExternTypeTable: + return ExternTypeTableName + case ExternTypeMemory: + return ExternTypeMemoryName + case ExternTypeGlobal: + return ExternTypeGlobalName + } + return fmt.Sprintf("%#x", et) +} + +// EncodeI32 encodes the input as a ValueTypeI32. +func EncodeI32(input int32) uint64 { + return uint64(uint32(input)) +} + +// EncodeI64 encodes the input as a ValueTypeI64. +func EncodeI64(input int64) uint64 { + return uint64(input) +} + +// EncodeF32 encodes the input as a ValueTypeF32. +// +// See DecodeF32 +func EncodeF32(input float32) uint64 { + return uint64(math.Float32bits(input)) +} + +// DecodeF32 decodes the input as a ValueTypeF32. +// +// See EncodeF32 +func DecodeF32(input uint64) float32 { + return math.Float32frombits(uint32(input)) +} + +// EncodeF64 encodes the input as a ValueTypeF64. +// +// See EncodeF32 +func EncodeF64(input float64) uint64 { + return math.Float64bits(input) +} + +// DecodeF64 decodes the input as a ValueTypeF64. +// +// See EncodeF64 +func DecodeF64(input uint64) float64 { + return math.Float64frombits(input) +} + +// EncodeExternref encodes the input as a ValueTypeExternref. +// +// See DecodeExternref +func EncodeExternref(input uintptr) uint64 { + return uint64(input) +} + +// DecodeExternref decodes the input as a ValueTypeExternref. +// +// See EncodeExternref +func DecodeExternref(input uint64) uintptr { + return uintptr(input) +} diff --git a/vendor/github.com/tetratelabs/wazero/.editorconfig b/vendor/github.com/tetratelabs/wazero/.editorconfig new file mode 100644 index 00000000000..f999431de51 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/vendor/github.com/tetratelabs/wazero/.gitattributes b/vendor/github.com/tetratelabs/wazero/.gitattributes new file mode 100644 index 00000000000..3a08bc389b7 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/.gitattributes @@ -0,0 +1,2 @@ +# Improves experience of commands like `make format` on Windows +* text=auto eol=lf diff --git a/vendor/github.com/tetratelabs/wazero/.gitignore b/vendor/github.com/tetratelabs/wazero/.gitignore new file mode 100644 index 00000000000..e9dd94b3e0b --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/.gitignore @@ -0,0 +1,46 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +/wazero +build +dist + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +# Goland +.idea + +# AssemblyScript +node_modules +package-lock.json + +# codecov.io +/coverage.txt + +.vagrant + +zig-cache/ +.zig-cache/ +zig-out/ + +.DS_Store + +# Ignore compiled stdlib test cases. +/internal/integration_test/stdlibs/testdata +/internal/integration_test/libsodium/testdata diff --git a/vendor/github.com/tetratelabs/wazero/.gitmodules b/vendor/github.com/tetratelabs/wazero/.gitmodules new file mode 100644 index 00000000000..410c91f44fc --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/.gitmodules @@ -0,0 +1,3 @@ +[submodule "site/themes/hello-friend"] + path = site/themes/hello-friend + url = https://github.com/panr/hugo-theme-hello-friend.git diff --git a/vendor/github.com/tetratelabs/wazero/CONTRIBUTING.md b/vendor/github.com/tetratelabs/wazero/CONTRIBUTING.md new file mode 100644 index 00000000000..8ab866f0e08 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/CONTRIBUTING.md @@ -0,0 +1,75 @@ +# Contributing + +We welcome contributions from the community. Please read the following guidelines carefully to maximize the chances of your PR being merged. + +## Coding Style + +- To ensure your change passes format checks, run `make check`. To format your files, you can run `make format`. +- We follow standard Go table-driven tests and use an internal [testing library](./internal/testing/require) to assert correctness. To verify all tests pass, you can run `make test`. + +## DCO + +We require DCO signoff line in every commit to this repo. + +The sign-off is a simple line at the end of the explanation for the +patch, which certifies that you wrote it or otherwise have the right to +pass it on as an open-source patch. The rules are pretty simple: if you +can certify the below (from +[developercertificate.org](https://developercertificate.org/)): + +``` +Developer Certificate of Origin +Version 1.1 +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. +Developer's Certificate of Origin 1.1 +By making a contribution to this project, I certify that: +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +then you just add a line to every git commit message: + + Signed-off-by: Joe Smith + +using your real name (sorry, no pseudonyms or anonymous contributions.) + +You can add the sign off when creating the git commit via `git commit -s`. + +## Code Reviews + +* The pull request title should describe what the change does and not embed issue numbers. +The pull request should only be blank when the change is minor. Any feature should include +a description of the change and what motivated it. If the change or design changes through +review, please keep the title and description updated accordingly. +* A single approval is sufficient to merge. If a reviewer asks for +changes in a PR they should be addressed before the PR is merged, +even if another reviewer has already approved the PR. +* During the review, address the comments and commit the changes +_without_ squashing the commits. This facilitates incremental reviews +since the reviewer does not go through all the code again to find out +what has changed since the last review. When a change goes out of sync with main, +please rebase and force push, keeping the original commits where practical. +* Commits are squashed prior to merging a pull request, using the title +as commit message by default. Maintainers may request contributors to +edit the pull request tite to ensure that it remains descriptive as a +commit message. Alternatively, maintainers may change the commit message directly. diff --git a/vendor/github.com/tetratelabs/wazero/LICENSE b/vendor/github.com/tetratelabs/wazero/LICENSE new file mode 100644 index 00000000000..e21d6995828 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020-2023 wazero authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/tetratelabs/wazero/Makefile b/vendor/github.com/tetratelabs/wazero/Makefile new file mode 100644 index 00000000000..5281e7cd82c --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/Makefile @@ -0,0 +1,336 @@ + +gofumpt := mvdan.cc/gofumpt@v0.6.0 +gosimports := github.com/rinchsan/gosimports/cmd/gosimports@v0.3.8 +golangci_lint := github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.5 +asmfmt := github.com/klauspost/asmfmt/cmd/asmfmt@v1.3.2 +# sync this with netlify.toml! +hugo := github.com/gohugoio/hugo@v0.115.2 + +# Make 3.81 doesn't support '**' globbing: Set explicitly instead of recursion. +all_sources := $(wildcard *.go */*.go */*/*.go */*/*/*.go */*/*/*.go */*/*/*/*.go) +all_testdata := $(wildcard testdata/* */testdata/* */*/testdata/* */*/testdata/*/* */*/*/testdata/*) +all_testing := $(wildcard internal/testing/* internal/testing/*/* internal/testing/*/*/*) +all_examples := $(wildcard examples/* examples/*/* examples/*/*/* */*/example/* */*/example/*/* */*/example/*/*/*) +all_it := $(wildcard internal/integration_test/* internal/integration_test/*/* internal/integration_test/*/*/*) +# main_sources exclude any test or example related code +main_sources := $(wildcard $(filter-out %_test.go $(all_testdata) $(all_testing) $(all_examples) $(all_it), $(all_sources))) +# main_packages collect the unique main source directories (sort will dedupe). +# Paths need to all start with ./, so we do that manually vs foreach which strips it. +main_packages := $(sort $(foreach f,$(dir $(main_sources)),$(if $(findstring ./,$(f)),./,./$(f)))) + +go_test_options ?= -timeout 300s + +.PHONY: test.examples +test.examples: + @go test $(go_test_options) ./examples/... ./imports/assemblyscript/example/... ./imports/emscripten/... ./imports/wasi_snapshot_preview1/example/... + +.PHONY: build.examples.as +build.examples.as: + @cd ./imports/assemblyscript/example/testdata && npm install && npm run build + +%.wasm: %.zig + @(cd $(@D); zig build -Doptimize=ReleaseSmall) + @mv $(@D)/zig-out/*/$(@F) $(@D) + +.PHONY: build.examples.zig +build.examples.zig: examples/allocation/zig/testdata/greet.wasm imports/wasi_snapshot_preview1/example/testdata/zig/cat.wasm imports/wasi_snapshot_preview1/testdata/zig/wasi.wasm + @cd internal/testing/dwarftestdata/testdata/zig; zig build; mv zig-out/*/main.wasm ./ # Need DWARF custom sections. + +tinygo_reactor_sources_reactor := examples/basic/testdata/add.go examples/allocation/tinygo/testdata/greet.go +.PHONY: build.examples.tinygo_reactor +build.examples.tinygo_reactor: $(tinygo_sources_reactor) + @for f in $^; do \ + tinygo build -o $$(echo $$f | sed -e 's/\.go/\.wasm/') -scheduler=none --no-debug --target=wasip1 -buildmode=c-shared $$f; \ + done + +tinygo_sources_clis := examples/cli/testdata/cli.go imports/wasi_snapshot_preview1/example/testdata/tinygo/cat.go imports/wasi_snapshot_preview1/testdata/tinygo/wasi.go cmd/wazero/testdata/cat/cat.go +.PHONY: build.examples.tinygo_clis +build.examples.tinygo_clis: $(tinygo_sources_clis) + @for f in $^; do \ + tinygo build -o $$(echo $$f | sed -e 's/\.go/\.wasm/') -scheduler=none --no-debug --target=wasip1 $$f; \ + done + @mv cmd/wazero/testdata/cat/cat.wasm cmd/wazero/testdata/cat/cat-tinygo.wasm + +.PHONY: build.examples.tinygo +build.examples.tinygo: build.examples.tinygo_reactor build.examples.tinygo_clis + +# We use zig to build C as it is easy to install and embeds a copy of zig-cc. +# Note: Don't use "-Oz" as that breaks our wasi sock example. +c_sources := imports/wasi_snapshot_preview1/example/testdata/zig-cc/cat.c imports/wasi_snapshot_preview1/testdata/zig-cc/wasi.c internal/testing/dwarftestdata/testdata/zig-cc/main.c +.PHONY: build.examples.zig-cc +build.examples.zig-cc: $(c_sources) + @for f in $^; do \ + zig cc --target=wasm32-wasi -o $$(echo $$f | sed -e 's/\.c/\.wasm/') $$f; \ + done + +# Here are the emcc args we use: +# +# * `-Oz` - most optimization for code size. +# * `--profiling` - adds the name section. +# * `-s STANDALONE_WASM` - ensures wasm is built for a non-js runtime. +# * `-s EXPORTED_FUNCTIONS=_malloc,_free` - export allocation functions so that +# they can be used externally as "malloc" and "free". +# * `-s WARN_ON_UNDEFINED_SYMBOLS=0` - imports not defined in JavaScript error +# otherwise. See https://github.com/emscripten-core/emscripten/issues/13641 +# * `-s TOTAL_STACK=8KB -s TOTAL_MEMORY=64KB` - reduce memory default from 16MB +# to one page (64KB). To do this, we have to reduce the stack size. +# * `-s ALLOW_MEMORY_GROWTH` - allows "memory.grow" instructions to succeed, but +# requires a function import "emscripten_notify_memory_growth". +emscripten_sources := $(wildcard imports/emscripten/testdata/*.cc) +.PHONY: build.examples.emscripten +build.examples.emscripten: $(emscripten_sources) + @for f in $^; do \ + em++ -Oz --profiling \ + -s STANDALONE_WASM \ + -s EXPORTED_FUNCTIONS=_malloc,_free \ + -s WARN_ON_UNDEFINED_SYMBOLS=0 \ + -s TOTAL_STACK=8KB -s TOTAL_MEMORY=64KB \ + -s ALLOW_MEMORY_GROWTH \ + --std=c++17 -o $$(echo $$f | sed -e 's/\.cc/\.wasm/') $$f; \ + done + +%/greet.wasm : cargo_target := wasm32-unknown-unknown +%/cat.wasm : cargo_target := wasm32-wasip1 +%/wasi.wasm : cargo_target := wasm32-wasip1 + +.PHONY: build.examples.rust +build.examples.rust: examples/allocation/rust/testdata/greet.wasm imports/wasi_snapshot_preview1/example/testdata/cargo-wasi/cat.wasm imports/wasi_snapshot_preview1/testdata/cargo-wasi/wasi.wasm internal/testing/dwarftestdata/testdata/rust/main.wasm.xz + +# Normally, we build release because it is smaller. Testing dwarf requires the debug build. +internal/testing/dwarftestdata/testdata/rust/main.wasm.xz: + cd $(@D) && cargo build --target wasm32-wasip1 + mv $(@D)/target/wasm32-wasip1/debug/main.wasm $(@D) + cd $(@D) && xz -k -f ./main.wasm # Rust's DWARF section is huge, so compress it. + +# Builds rust using cargo normally +%.wasm: %.rs + @(cd $(@D); cargo build --target $(cargo_target) --release) + @mv $(@D)/target/$(cargo_target)/release/$(@F) $(@D) + +spectest_base_dir := internal/integration_test/spectest + +spectest_v1_dir := $(spectest_base_dir)/v1 +spectest_v1_testdata_dir := $(spectest_v1_dir)/testdata +spec_version_v1 := wg-1.0 + +spectest_v2_dir := $(spectest_base_dir)/v2 +spectest_v2_testdata_dir := $(spectest_v2_dir)/testdata +spec_version_v2 := wg-2.0 + +spectest_threads_dir := $(spectest_base_dir)/threads +spectest_threads_testdata_dir := $(spectest_threads_dir)/testdata +spec_version_threads := ff17701446d8e2086142423ef77ae947a025e26f + +spectest_tail_call_dir := $(spectest_base_dir)/tail-call +spectest_tail_call_testdata_dir := $(spectest_tail_call_dir)/testdata +spec_version_tail_call := 88e97b0f742f4c3ee01fea683da130f344dd7b02 + +.PHONY: build.spectest +build.spectest: + @$(MAKE) build.spectest.v1 + @$(MAKE) build.spectest.v2 + @$(MAKE) build.spectest.threads + @$(MAKE) build.spectest.tail_call + +.PHONY: build.spectest.v1 +build.spectest.v1: # Note: wabt by default uses >1.0 features, so wast2json flags might drift as they include more. See WebAssembly/wabt#1878 + @rm -rf $(spectest_v1_testdata_dir) + @mkdir -p $(spectest_v1_testdata_dir) + @cd $(spectest_v1_testdata_dir) \ + && curl -sSL 'https://api.github.com/repos/WebAssembly/spec/contents/test/core?ref=$(spec_version_v1)' | jq -r '.[]| .download_url' | grep -E ".wast" | xargs -Iurl curl -sJL url -O + @cd $(spectest_v1_testdata_dir) && for f in `find . -name '*.wast'`; do \ + perl -pi -e 's/\(assert_return_canonical_nan\s(\(invoke\s"f32.demote_f64"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\))\)/\(assert_return $$1 \(f32.const nan:canonical\)\)/g' $$f; \ + perl -pi -e 's/\(assert_return_arithmetic_nan\s(\(invoke\s"f32.demote_f64"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\))\)/\(assert_return $$1 \(f32.const nan:arithmetic\)\)/g' $$f; \ + perl -pi -e 's/\(assert_return_canonical_nan\s(\(invoke\s"f64\.promote_f32"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\))\)/\(assert_return $$1 \(f64.const nan:canonical\)\)/g' $$f; \ + perl -pi -e 's/\(assert_return_arithmetic_nan\s(\(invoke\s"f64\.promote_f32"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\))\)/\(assert_return $$1 \(f64.const nan:arithmetic\)\)/g' $$f; \ + perl -pi -e 's/\(assert_return_canonical_nan\s(\(invoke\s"[a-z._0-9]+"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\))\)/\(assert_return $$1 \($$2.const nan:canonical\)\)/g' $$f; \ + perl -pi -e 's/\(assert_return_arithmetic_nan\s(\(invoke\s"[a-z._0-9]+"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\))\)/\(assert_return $$1 \($$2.const nan:arithmetic\)\)/g' $$f; \ + perl -pi -e 's/\(assert_return_canonical_nan\s(\(invoke\s"[a-z._0-9]+"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\s\([a-z0-9.\s+-:]+\)\))\)/\(assert_return $$1 \($$2.const nan:canonical\)\)/g' $$f; \ + perl -pi -e 's/\(assert_return_arithmetic_nan\s(\(invoke\s"[a-z._0-9]+"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\s\([a-z0-9.\s+-:]+\)\))\)/\(assert_return $$1 \($$2.const nan:arithmetic\)\)/g' $$f; \ + perl -pi -e 's/\(assert_return_canonical_nan\s(\(invoke\s"[a-z._0-9]+"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\))\)/\(assert_return $$1 \($$2.const nan:canonical\)\)/g' $$f; \ + perl -pi -e 's/\(assert_return_arithmetic_nan\s(\(invoke\s"[a-z._0-9]+"\s\((f[0-9]{2})\.const\s[a-z0-9.+:-]+\)\))\)/\(assert_return $$1 \($$2.const nan:arithmetic\)\)/g' $$f; \ + wast2json \ + --disable-saturating-float-to-int \ + --disable-sign-extension \ + --disable-simd \ + --disable-multi-value \ + --disable-bulk-memory \ + --disable-reference-types \ + --debug-names $$f; \ + done + +.PHONY: build.spectest.v2 +build.spectest.v2: # Note: SIMD cases are placed in the "simd" subdirectory. + @rm -rf $(spectest_v2_testdata_dir) + @mkdir -p $(spectest_v2_testdata_dir) + @cd $(spectest_v2_testdata_dir) \ + && curl -sSL 'https://api.github.com/repos/WebAssembly/spec/contents/test/core?ref=$(spec_version_v2)' | jq -r '.[]| .download_url' | grep -E ".wast" | xargs -Iurl curl -sJL url -O + @cd $(spectest_v2_testdata_dir) \ + && curl -sSL 'https://api.github.com/repos/WebAssembly/spec/contents/test/core/simd?ref=$(spec_version_v2)' | jq -r '.[]| .download_url' | grep -E ".wast" | xargs -Iurl curl -sJL url -O + @cd $(spectest_v2_testdata_dir) && for f in `find . -name '*.wast'`; do \ + wast2json --debug-names --no-check $$f || true; \ + done # Ignore the error here as some tests (e.g. comments.wast right now) are not supported by wast2json yet. + +# Note: We currently cannot build the "threads" subdirectory that spawns threads due to missing support in wast2json. +# https://github.com/WebAssembly/wabt/issues/2348#issuecomment-1878003959 +.PHONY: build.spectest.threads +build.spectest.threads: + @rm -rf $(spectest_threads_testdata_dir) + @mkdir -p $(spectest_threads_testdata_dir) + @cd $(spectest_threads_testdata_dir) \ + && curl -sSL 'https://api.github.com/repos/WebAssembly/threads/contents/test/core/threads?ref=$(spec_version_threads)' | jq -r '.[]| .download_url' | grep -E "/atomic.wast" | xargs -Iurl curl -sJL url -O + @cd $(spectest_threads_testdata_dir) && for f in `find . -name '*.wast'`; do \ + wast2json --enable-threads --debug-names $$f; \ + done + +.PHONY: build.spectest.tail_call +build.spectest.tail_call: + @rm -rf $(spectest_tail_call_testdata_dir) + @mkdir -p $(spectest_tail_call_testdata_dir) + @cd $(spectest_tail_call_testdata_dir) \ + && curl -sSL 'https://api.github.com/repos/WebAssembly/testsuite/contents/proposals/tail-call?ref=$(spec_version_tail_call)' | jq -r '.[]| .download_url' | grep -E ".wast" | xargs -Iurl curl -sJL url -O + @cd $(spectest_tail_call_testdata_dir) && for f in `find . -name '*.wast'`; do \ + wast2json --enable-tail-call --debug-names $$f; \ + done + +.PHONY: test +test: + @go test $(go_test_options) ./... + @cd internal/version/testdata && go test $(go_test_options) ./... + @cd internal/integration_test/fuzz/wazerolib && CGO_ENABLED=0 WASM_BINARY_PATH=testdata/test.wasm go test ./... + +.PHONY: coverage +# replace spaces with commas +coverpkg = $(shell echo $(main_packages) | tr ' ' ',') +coverage: ## Generate test coverage + @go test -coverprofile=coverage.txt -covermode=atomic --coverpkg=$(coverpkg) $(main_packages) + @go tool cover -func coverage.txt + +golangci_lint_path := $(shell go env GOPATH)/bin/golangci-lint + +$(golangci_lint_path): + @go install $(golangci_lint) + +golangci_lint_goarch ?= $(shell go env GOARCH) + +.PHONY: lint +lint: $(golangci_lint_path) + @GOARCH=$(golangci_lint_goarch) CGO_ENABLED=0 $(golangci_lint_path) run --timeout 5m -E testableexamples + +.PHONY: format +format: + @go run $(gofumpt) -l -w . + @go run $(gosimports) -local github.com/tetratelabs/ -w $(shell find . -name '*.go' -type f) + @go run $(asmfmt) -w $(shell find . -name '*.s' -type f) + +.PHONY: check # Pre-flight check for pull requests +check: +# The following checks help ensure our platform-specific code used for system +# calls safely falls back on a platform unsupported by the compiler engine. +# This makes sure the intepreter can be used. Most often the package that can +# drift here is "platform" or "sysfs": +# +# Ensure we build on plan9. See #1578 + @GOARCH=amd64 GOOS=plan9 go build ./... +# Ensure we build on gojs. See #1526. + @GOARCH=wasm GOOS=js go build ./... +# Ensure we build on wasip1. See #1526. + @GOARCH=wasm GOOS=wasip1 go build ./... +# Ensure we build on aix. See #1723 + @GOARCH=ppc64 GOOS=aix go build ./... +# Ensure we build on linux s390x. See #2412 + @GOARCH=s390x GOOS=linux go build ./... +# Ensure we build on linux ppc64le. See #2412 + @GOARCH=ppc64le GOOS=linux go build ./... +# Ensure we build on linux arm for Dapr: +# gh release view -R dapr/dapr --json assets --jq 'first(.assets[] | select(.name = "daprd_linux_arm.tar.gz") | {url, downloadCount})' + @GOARCH=arm GOOS=linux go build ./... +# Ensure we build on linux 386 for Trivy: +# gh release view -R aquasecurity/trivy --json assets --jq 'first(.assets[] | select(.name| test("Linux-32bit.*tar.gz")) | {url, downloadCount})' + @GOARCH=386 GOOS=linux go build ./... +# Ensure we build on FreeBSD amd64 for Trivy: +# gh release view -R aquasecurity/trivy --json assets --jq 'first(.assets[] | select(.name| test("FreeBSD-64bit.*tar.gz")) | {url, downloadCount})' + @GOARCH=amd64 GOOS=freebsd go build ./... + @$(MAKE) lint golangci_lint_goarch=arm64 + @$(MAKE) lint golangci_lint_goarch=amd64 + @$(MAKE) format + @go mod tidy + @if [ ! -z "`git status -s`" ]; then \ + echo "The following differences will fail CI until committed:"; \ + git diff --exit-code; \ + fi + +.PHONY: site +site: ## Serve website content + @git submodule update --init + @cd site && go run $(hugo) server --minify --disableFastRender --baseURL localhost:1313 --cleanDestinationDir -D + +.PHONY: clean +clean: ## Ensure a clean build + @rm -rf dist build coverage.txt + @go clean -testcache + +fuzz_default_flags := --no-trace-compares --sanitizer=none -- -rss_limit_mb=8192 + +fuzz_timeout_seconds ?= 10 +.PHONY: fuzz +fuzz: + @cd internal/integration_test/fuzz && cargo test + @cd internal/integration_test/fuzz && cargo fuzz run logging_no_diff $(fuzz_default_flags) -max_total_time=$(fuzz_timeout_seconds) + @cd internal/integration_test/fuzz && cargo fuzz run no_diff $(fuzz_default_flags) -max_total_time=$(fuzz_timeout_seconds) + @cd internal/integration_test/fuzz && cargo fuzz run memory_no_diff $(fuzz_default_flags) -max_total_time=$(fuzz_timeout_seconds) + @cd internal/integration_test/fuzz && cargo fuzz run validation $(fuzz_default_flags) -max_total_time=$(fuzz_timeout_seconds) + +libsodium: + cd ./internal/integration_test/libsodium/testdata && \ + curl -s "https://api.github.com/repos/jedisct1/webassembly-benchmarks/contents/2022-12/wasm?ref=7e86d68e99e60130899fbe3b3ab6e9dce9187a7c" \ + | jq -r '.[] | .download_url' | xargs -n 1 curl -LO + +#### CLI release related #### + +VERSION ?= dev +non_windows_platforms := darwin_amd64 darwin_arm64 linux_amd64 linux_arm64 +non_windows_archives := $(non_windows_platforms:%=dist/wazero_$(VERSION)_%.tar.gz) +windows_platforms := windows_amd64 # TODO: add arm64 windows once we start testing on it. +windows_archives := $(windows_platforms:%=dist/wazero_$(VERSION)_%.zip) +checksum_txt := dist/wazero_$(VERSION)_checksums.txt + +# define macros for multi-platform builds. these parse the filename being built +go-arch = $(if $(findstring amd64,$1),amd64,arm64) +go-os = $(if $(findstring .exe,$1),windows,$(if $(findstring linux,$1),linux,darwin)) + +build/wazero_%/wazero: + $(call go-build,$@,$<) + +build/wazero_%/wazero.exe: + $(call go-build,$@,$<) + +dist/wazero_$(VERSION)_%.tar.gz: build/wazero_%/wazero + @echo tar.gz "tarring $@" + @mkdir -p $(@D) +# On Windows, we pass the special flag `--mode='+rx' to ensure that we set the executable flag. +# This is only supported by GNU Tar, so we set it conditionally. + @tar -C $( $(@F) + +dist: $(non_windows_archives) $(windows_archives) $(checksum_txt) diff --git a/vendor/github.com/tetratelabs/wazero/NOTICE b/vendor/github.com/tetratelabs/wazero/NOTICE new file mode 100644 index 00000000000..2f5ea8ebf4d --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/NOTICE @@ -0,0 +1,2 @@ +wazero +Copyright 2020-2023 wazero authors diff --git a/vendor/github.com/tetratelabs/wazero/RATIONALE.md b/vendor/github.com/tetratelabs/wazero/RATIONALE.md new file mode 100644 index 00000000000..27c955c8fef --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/RATIONALE.md @@ -0,0 +1,1577 @@ +# Notable rationale of wazero + +## Zero dependencies + +Wazero has zero dependencies to differentiate itself from other runtimes which +have heavy impact usually due to CGO. By avoiding CGO, wazero avoids +prerequisites such as shared libraries or libc, and lets users keep features +like cross compilation. + +Avoiding most `go.mod` dependencies reduces interference on Go version support, +and size of a statically compiled binary. However, doing so brings some +responsibility into the project. + +Go's native platform support is good: we don't need platform-specific code to +get monotonic time, nor do we need much work to implement certain features +needed by our compiler such as `mmap`. That said, Go does not support all +common operating systems to the same degree. + +The general tradeoff the project takes from a strict dependency policy is more +explicit support of platforms (in the compiler runtime), as well a larger and +more technically difficult codebase. + +### Why do we use CGO to implement system calls on darwin? + +wazero is dependency and CGO free by design. In some cases, we have code that +can optionally use CGO, but retain a fallback for when that's disabled. The only +operating system (`GOOS`) we use CGO by default in is `darwin`. + +Unlike other operating systems, regardless of `CGO_ENABLED`, Go always uses +"CGO" mechanisms in the runtime layer of `darwin`. This is explained in +[Statically linked binaries on Mac OS X](https://developer.apple.com/library/archive/qa/qa1118/_index.html#//apple_ref/doc/uid/DTS10001666): + +> Apple does not support statically linked binaries on Mac OS X. A statically +> linked binary assumes binary compatibility at the kernel system call +> interface, and we do not make any guarantees on that front. Rather, we strive +> to ensure binary compatibility in each dynamically linked system library and +> framework. + +This plays to our advantage for system calls that aren't yet exposed in the Go +standard library, notably `futimens` for nanosecond-precision timestamp +manipulation. + +### Why x/sys + +The [x/sys library](https://pkg.go.dev/golang.org/x/sys/unix) is currently +our only `go.mod` dependency. + +That module is maintained by the Go authors, and covers OSes that the syscall +package neglects. + +After [heavy consideration](https://github.com/wazero/wazero/issues/2434) we +decided to it as a dependency. + +Using was shown to improve the experience of using wazero on older, +or less common, OSes without increasing the maintenance work, or creating +deployment issues for users of wazero. + +## Project structure + +wazero uses internal packages extensively to balance API compatibility desires for end users with the need to safely +share internals between compilers. + +End-user packages include `wazero`, with `Config` structs, `api`, with shared types, and the built-in `wasi` library. +Everything else is internal. + +We put the main program for wazero into a directory of the same name to match conventions used in `go install`, +notably the name of the folder becomes the binary name. We chose to use `cmd/wazero` as it is common practice +and less surprising than `wazero/wazero`. + +### Internal packages + +Most code in wazero is internal, and it is acknowledged that this prevents external implementation of facets such as +compilers or decoding. It also prevents splitting this code into separate repositories, resulting in a larger monorepo. +This also adds work as more code needs to be centrally reviewed. + +However, the alternative is neither secure nor viable. To allow external implementation would require exporting symbols +public, such as the `CodeSection`, which can easily create bugs. Moreover, there's a high drift risk for any attempt at +external implementations, compounded not just by wazero's code organization, but also the fast moving Wasm and WASI +specifications. + +For example, implementing a compiler correctly requires expertise in Wasm, Golang and assembly. This requires deep +insight into how internals are meant to be structured and the various tiers of testing required for `wazero` to result +in a high quality experience. Even if someone had these skills, supporting external code would introduce variables which +are constants in the central one. Supporting an external codebase is harder on the project team, and could starve time +from the already large burden on the central codebase. + +The tradeoffs of internal packages are a larger codebase and responsibility to implement all standard features. It also +implies thinking about extension more as forking is not viable for reasons above also. The primary mitigation of these +realities are friendly OSS licensing, high rigor and a collaborative spirit which aim to make contribution in the shared +codebase productive. + +### Avoiding cyclic dependencies + +wazero shares constants and interfaces with internal code by a sharing pattern described below: +* shared interfaces and constants go in one package under root: `api`. +* user APIs and structs depend on `api` and go into the root package `wazero`. + * e.g. `InstantiateModule` -> `/wasm.go` depends on the type `api.Module`. +* implementation code can also depend on `api` in a corresponding package under `/internal`. + * Ex package `wasm` -> `/internal/wasm/*.go` and can depend on the type `api.Module`. + +The above guarantees no cyclic dependencies at the cost of having to re-define symbols that exist in both packages. +For example, if `wasm.Store` is a type the user needs access to, it is narrowed by a cover type in the `wazero`: + +```go +type runtime struct { + s *wasm.Store +} +``` + +This is not as bad as it sounds as mutations are only available via configuration. This means exported functions are +limited to only a few functions. + +### Avoiding security bugs + +In order to avoid security flaws such as code insertion, nothing in the public API is permitted to write directly to any +mutable symbol in the internal package. For example, the package `api` is shared with internal code. To ensure +immutability, the `api` package cannot contain any mutable public symbol, such as a slice or a struct with an exported +field. + +In practice, this means shared functionality like memory mutation need to be implemented by interfaces. + +Here are some examples: +* `api.Memory` protects access by exposing functions like `WriteFloat64Le` instead of exporting a buffer (`[]byte`). +* There is no exported symbol for the `[]byte` representing the `CodeSection` + +Besides security, this practice prevents other bugs and allows centralization of validation logic such as decoding Wasm. + +## API Design + +### Why is `context.Context` inconsistent? + +It may seem strange that only certain API have an initial `context.Context` +parameter. We originally had a `context.Context` for anything that might be +traced, but it turned out to be only useful for lifecycle and host functions. + +For instruction-scoped aspects like memory updates, a context parameter is too +fine-grained and also invisible in practice. For example, most users will use +the compiler engine, and its memory, global or table access will never use go's +context. + +### Why does `api.ValueType` map to uint64? + +WebAssembly allows functions to be defined either by the guest or the host, +with signatures expressed as WebAssembly types. For example, `i32` is a 32-bit +type which might be interpreted as signed. Function signatures can have zero or +more parameters or results even if WebAssembly 1.0 allows up to one result. + +The guest can export functions, so that the host can call it. In the case of +wazero, the host is Go and an exported function can be called via +`api.Function`. `api.Function` allows users to supply parameters and read +results as a slice of uint64. For example, if there are no results, an empty +slice is returned. The user can learn the signature via `FunctionDescription`, +which returns the `api.ValueType` corresponding to each parameter or result. +`api.ValueType` defines the mapping of WebAssembly types to `uint64` values for +reason described in this section. The special case of `v128` is also mentioned +below. + +wazero maps each value type to a uint64 values because it holds the largest +type in WebAssembly 1.0 (i64). A slice allows you to express empty (e.g. a +nullary signature), for example a start function. + +Here's an example of calling a function, noting this syntax works for both a +signature `(param i32 i32) (result i32)` and `(param i64 i64) (result i64)` +```go +x, y := uint64(1), uint64(2) +results, err := mod.ExportedFunction("add").Call(ctx, x, y) +if err != nil { + log.Panicln(err) +} +fmt.Printf("%d + %d = %d\n", x, y, results[0]) +``` + +WebAssembly does not define an encoding strategy for host defined parameters or +results. This means the encoding rules above are defined by wazero instead. To +address this, we clarified mapping both in `api.ValueType` and added helper +functions like `api.EncodeF64`. This allows users conversions typical in Go +programming, and utilities to avoid ambiguity and edge cases around casting. + +Alternatively, we could have defined a byte buffer based approach and a binary +encoding of value types in and out. For example, an empty byte slice would mean +no values, while a non-empty could use a binary encoding for supported values. +This could work, but it is more difficult for the normal case of i32 and i64. +It also shares a struggle with the current approach, which is that value types +were added after WebAssembly 1.0 and not all of them have an encoding. More on +this below. + +In summary, wazero chose an approach for signature mapping because there was +none, and the one we chose biases towards simplicity with integers and handles +the rest with documentation and utilities. + +#### Post 1.0 value types + +Value types added after WebAssembly 1.0 stressed the current model, as some +have no encoding or are larger than 64 bits. While problematic, these value +types are not commonly used in exported (extern) functions. However, some +decisions were made and detailed below. + +For example `externref` has no guest representation. wazero chose to map +references to uint64 as that's the largest value needed to encode a pointer on +supported platforms. While there are two reference types, `externref` and +`functype`, the latter is an internal detail of function tables, and the former +is rarely if ever used in function signatures as of the end of 2022. + +The only value larger than 64 bits is used for SIMD (`v128`). Vectorizing via +host functions is not used as of the end of 2022. Even if it were, it would be +inefficient vs guest vectorization due to host function overhead. In other +words, the `v128` value type is unlikely to be in an exported function +signature. That it requires two uint64 values to encode is an internal detail +and not worth changing the exported function interface `api.Function`, as doing +so would break all users. + +### Interfaces, not structs + +All exported types in public packages, regardless of configuration vs runtime, are interfaces. The primary benefits are +internal flexibility and avoiding people accidentally mis-initializing by instantiating the types on their own vs using +the `NewXxx` constructor functions. In other words, there's less support load when things can't be done incorrectly. + +Here's an example: +```go +rt := &RuntimeConfig{} // not initialized properly (fields are nil which shouldn't be) +rt := RuntimeConfig{} // not initialized properly (should be a pointer) +rt := wazero.NewRuntimeConfig() // initialized properly +``` + +There are a few drawbacks to this, notably some work for maintainers. +* Interfaces are decoupled from the structs implementing them, which means the signature has to be repeated twice. +* Interfaces have to be documented and guarded at time of use, that 3rd party implementations aren't supported. +* As of Golang 1.21, interfaces are still [not well supported](https://github.com/golang/go/issues/5860) in godoc. + +## Config + +wazero configures scopes such as Runtime and Module using `XxxConfig` types. For example, `RuntimeConfig` configures +`Runtime` and `ModuleConfig` configure `Module` (instantiation). In all cases, config types begin defaults and can be +customized by a user, e.g., selecting features or a module name override. + +### Why don't we make each configuration setting return an error? +No config types create resources that would need to be closed, nor do they return errors on use. This helps reduce +resource leaks, and makes chaining easier. It makes it possible to parse configuration (ex by parsing yaml) independent +of validating it. + +Instead of: +``` +cfg, err = cfg.WithFS(fs) +if err != nil { + return err +} +cfg, err = cfg.WithName(name) +if err != nil { + return err +} +mod, err = rt.InstantiateModuleWithConfig(ctx, code, cfg) +if err != nil { + return err +} +``` + +There's only one call site to handle errors: +``` +cfg = cfg.WithFS(fs).WithName(name) +mod, err = rt.InstantiateModuleWithConfig(ctx, code, cfg) +if err != nil { + return err +} +``` + +This allows users one place to look for errors, and also the benefit that if anything internally opens a resource, but +errs, there's nothing they need to close. In other words, users don't need to track which resources need closing on +partial error, as that is handled internally by the only code that can read configuration fields. + +### Why are configuration immutable? +While it seems certain scopes like `Runtime` won't repeat within a process, they do, possibly in different goroutines. +For example, some users create a new runtime for each module, and some re-use the same base module configuration with +only small updates (ex the name) for each instantiation. Making configuration immutable allows them to be safely used in +any goroutine. + +Since config are immutable, changes apply via return val, similar to `append` in a slice. + +For example, both of these are the same sort of error: +```go +append(slice, element) // bug as only the return value has the updated slice. +cfg.WithName(next) // bug as only the return value has the updated name. +``` + +Here's an example of correct use: re-assigning explicitly or via chaining. +```go +cfg = cfg.WithName(name) // explicit + +mod, err = rt.InstantiateModuleWithConfig(ctx, code, cfg.WithName(name)) // implicit +if err != nil { + return err +} +``` + +### Why aren't configuration assigned with option types? +The option pattern is a familiar one in Go. For example, someone defines a type `func (x X) err` and uses it to update +the target. For example, you could imagine wazero could choose to make `ModuleConfig` from options vs chaining fields. + +Ex instead of: +```go +type ModuleConfig interface { + WithName(string) ModuleConfig + WithFS(fs.FS) ModuleConfig +} + +struct moduleConfig { + name string + fs fs.FS +} + +func (c *moduleConfig) WithName(name string) ModuleConfig { + ret := *c // copy + ret.name = name + return &ret +} + +func (c *moduleConfig) WithFS(fs fs.FS) ModuleConfig { + ret := *c // copy + ret.setFS("/", fs) + return &ret +} + +config := r.NewModuleConfig().WithFS(fs) +configDerived := config.WithName("name") +``` + +An option function could be defined, then refactor each config method into an name prefixed option function: +```go +type ModuleConfig interface { +} +struct moduleConfig { + name string + fs fs.FS +} + +type ModuleConfigOption func(c *moduleConfig) + +func ModuleConfigName(name string) ModuleConfigOption { + return func(c *moduleConfig) { + c.name = name + } +} + +func ModuleConfigFS(fs fs.FS) ModuleConfigOption { + return func(c *moduleConfig) { + c.fs = fs + } +} + +func (r *runtime) NewModuleConfig(opts ...ModuleConfigOption) ModuleConfig { + ret := newModuleConfig() // defaults + for _, opt := range opts { + opt(&ret.config) + } + return ret +} + +func (c *moduleConfig) WithOptions(opts ...ModuleConfigOption) ModuleConfig { + ret := *c // copy base config + for _, opt := range opts { + opt(&ret.config) + } + return ret +} + +config := r.NewModuleConfig(ModuleConfigFS(fs)) +configDerived := config.WithOptions(ModuleConfigName("name")) +``` + +wazero took the path of the former design primarily due to: +* interfaces provide natural namespaces for their methods, which is more direct than functions with name prefixes. +* parsing config into function callbacks is more direct vs parsing config into a slice of functions to do the same. +* in either case derived config is needed and the options pattern is more awkward to achieve that. + +There are other reasons such as test and debug being simpler without options: the above list is constrained to conserve +space. It is accepted that the options pattern is common in Go, which is the main reason for documenting this decision. + +### Why aren't config types deeply structured? +wazero's configuration types cover the two main scopes of WebAssembly use: +* `RuntimeConfig`: This is the broadest scope, so applies also to compilation + and instantiation. e.g. This controls the WebAssembly Specification Version. +* `ModuleConfig`: This affects modules instantiated after compilation and what + resources are allowed. e.g. This defines how or if STDOUT is captured. This + also allows sub-configuration of `FSConfig`. + +These default to a flat definition each, with lazy sub-configuration only after +proven to be necessary. A flat structure is easier to work with and is also +easy to discover. Unlike the option pattern described earlier, more +configuration in the interface doesn't taint the package namespace, only +`ModuleConfig`. + +We default to a flat structure to encourage simplicity. If we eagerly broke out +all possible configurations into sub-types (e.g. ClockConfig), it would be hard +to notice configuration sprawl. By keeping the config flat, it is easy to see +the cognitive load we may be adding to our users. + +In other words, discomfort adding more configuration is a feature, not a bug. +We should only add new configuration rarely, and before doing so, ensure it +will be used. In fact, this is why we support using context fields for +experimental configuration. By letting users practice, we can find out if a +configuration was a good idea or not before committing to it, and potentially +sprawling our types. + +In reflection, this approach worked well for the nearly 1.5 year period leading +to version 1.0. We've only had to create a single sub-configuration, `FSConfig`, +and it was well understood why when it occurred. + +## Why does `ModuleConfig.WithStartFunctions` default to `_start`? + +We formerly had functions like `StartWASICommand` that would verify +preconditions and start WASI's `_start` command. However, this caused confusion +because both many languages compiled a WASI dependency, and many did so +inconsistently. + +The conflict is that exported functions need to use features the language +runtime provides, such as garbage collection. There's a "chicken-egg problem" +where `_start` needs to complete in order for exported behavior to work. + +For example, unlike `GOOS=wasip1` in Go 1.21, TinyGo's "wasi" target supports +function exports. So, the only way to use FFI style is via the "wasi" target. +Not explicitly calling `_start` before an ABI such as wapc-go, would crash, due +to setup not happening (e.g. to implement `panic`). Other embedders such as +Envoy also called `_start` for the same reason. To avoid a common problem for +users unaware of WASI, and also to simplify normal use of WASI (e.g. `main`), +we added `_start` to `ModuleConfig.WithStartFunctions`. + +In cases of multiple initializers, such as in wapc-go, users can override this +to add the others *after* `_start`. Users who want to explicitly control +`_start`, such as some of our unit tests, can clear the start functions and +remove it. + +This decision was made in 2022, and holds true in 2023, even with the +introduction of "wasix". It holds because "wasix" is backwards compatible with +"wasip1". In the future, there will be other ways to start applications, and +may not be backwards compatible with "wasip1". + +Most notably WASI "Preview 2" is not implemented in a way compatible with +wasip1. Its start function is likely to be different, and defined in the +wasi-cli "world". When the design settles, and it is implemented by compilers, +wazero will attempt to support "wasip2". However, it won't do so in a way that +breaks existing compilers. + +In other words, we won't remove `_start` if "wasip2" continues a path of an +alternate function name. If we did, we'd break existing users despite our +compatibility promise saying we don't. The most likely case is that when we +build-in something incompatible with "wasip1", that start function will be +added to the start functions list in addition to `_start`. + +See http://wasix.org +See https://github.com/WebAssembly/wasi-cli + +## Runtime == Engine+Store +wazero defines a single user-type which combines the specification concept of `Store` with the unspecified `Engine` +which manages them. + +### Why not multi-store? +Multi-store isn't supported as the extra tier complicates lifecycle and locking. Moreover, in practice it is unusual for +there to be an engine that has multiple stores which have multiple modules. More often, it is the case that there is +either 1 engine with 1 store and multiple modules, or 1 engine with many stores, each having 1 non-host module. In worst +case, a user can use multiple runtimes until "multi-store" is better understood. + +If later, we have demand for multiple stores, that can be accomplished by overload. e.g. `Runtime.InstantiateInStore` or +`Runtime.Store(name) Store`. + +## Exit + +### Why do we only return a `sys.ExitError` on a non-zero exit code? + +It is reasonable to think an exit error should be returned, even if the code is +success (zero). Even on success, the module is no longer functional. For +example, function exports would error later. However, wazero does not. The only +time `sys.ExitError` is on error (non-zero). + +This decision was to improve performance and ergonomics for guests that both +use WASI (have a `_start` function), and also allow custom exports. +Specifically, Rust, TinyGo and normal wasi-libc, don't exit the module during +`_start`. If they did, it would invalidate their function exports. This means +it is unlikely most compilers will change this behavior. + +`GOOS=waspi1` from Go 1.21 does exit during `_start`. However, it doesn't +support other exports besides `_start`, and `_start` is not defined to be +called multiple times anyway. + +Since `sys.ExitError` is not always returned, we added `Module.IsClosed` for +defensive checks. This helps integrators avoid calling functions which will +always fail. + +### Why panic with `sys.ExitError` after a host function exits? + +Currently, the only portable way to stop processing code is via panic. For +example, WebAssembly "trap" instructions, such as divide by zero, are +implemented via panic. This ensures code isn't executed after it. + +When code reaches the WASI `proc_exit` instruction, we need to stop processing. +Regardless of the exit code, any code invoked after exit would be in an +inconsistent state. This is likely why unreachable instructions are sometimes +inserted after exit: https://github.com/emscripten-core/emscripten/issues/12322 + +## WASI + +Unfortunately, [WASI Snapshot Preview 1](https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md) is not formally defined enough, and has APIs with ambiguous semantics. +This section describes how Wazero interprets and implements the semantics of several WASI APIs that may be interpreted differently by different wasm runtimes. +Those APIs may affect the portability of a WASI application. + +### Why don't we attempt to pass wasi-testsuite on user-defined `fs.FS`? + +While most cases work fine on an `os.File` based implementation, we won't +promise wasi-testsuite compatibility on user defined wrappers of `os.DirFS`. +The only option for real systems is to use our `sysfs.FS`. + +There are a lot of areas where windows behaves differently, despite the +`os.File` abstraction. This goes well beyond file locking concerns (e.g. +`EBUSY` errors on open files). For example, errors like `ACCESS_DENIED` aren't +properly mapped to `EPERM`. There are trickier parts too. `FileInfo.Sys()` +doesn't return enough information to build inodes needed for WASI. To rebuild +them requires the full path to the underlying file, not just its directory +name, and there's no way for us to get that information. At one point we tried, +but in practice things became tangled and functionality such as read-only +wrappers became untenable. Finally, there are version-specific behaviors which +are difficult to maintain even in our own code. For example, go 1.20 opens +files in a different way than versions before it. + +### Why aren't WASI rules enforced? + +The [snapshot-01](https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md) version of WASI has a +number of rules for a "command module", but only the memory export rule is enforced. If a "_start" function exists, it +is enforced to be the correct signature and succeed, but the export itself isn't enforced. It follows that this means +exports are not required to be contained to a "_start" function invocation. Finally, the "__indirect_function_table" +export is also not enforced. + +The reason for the exceptions are that implementations aren't following the rules. For example, TinyGo doesn't export +"__indirect_function_table", so crashing on this would make wazero unable to run TinyGo modules. Similarly, modules +loaded by wapc-go don't always define a "_start" function. Since "snapshot-01" is not a proper version, and certainly +not a W3C recommendation, there's no sense in breaking users over matters like this. + +### Why is I/O configuration not coupled to WASI? + +WebAssembly System Interfaces (WASI) is a formalization of a practice that can be done anyway: Define a host function to +access a system interface, such as writing to STDOUT. WASI stalled at snapshot-01 and as of early 2023, is being +rewritten entirely. + +This instability implies a need to transition between WASI specs, which places wazero in a position that requires +decoupling. For example, if code uses two different functions to call `fd_write`, the underlying configuration must be +centralized and decoupled. Otherwise, calls using the same file descriptor number will end up writing to different +places. + +In short, wazero defined system configuration in `ModuleConfig`, not a WASI type. This allows end-users to switch from +one spec to another with minimal impact. This has other helpful benefits, as centralized resources are simpler to close +coherently (ex via `Module.Close`). + +In reflection, this worked well as more ABI became usable in wazero. + +### Background on `ModuleConfig` design + +WebAssembly 1.0 (20191205) specifies some aspects to control isolation between modules ([sandboxing](https://en.wikipedia.org/wiki/Sandbox_(computer_security))). +For example, `wasm.Memory` has size constraints and each instance of it is isolated from each other. While `wasm.Memory` +can be shared, by exporting it, it is not exported by default. In fact a WebAssembly Module (Wasm) has no memory by +default. + +While memory is defined in WebAssembly 1.0 (20191205), many aspects are not. Let's use an example of `exec.Cmd` as for +example, a WebAssembly System Interfaces (WASI) command is implemented as a module with a `_start` function, and in many +ways acts similar to a process with a `main` function. + +To capture "hello world" written to the console (stdout a.k.a. file descriptor 1) in `exec.Cmd`, you would set the +`Stdout` field accordingly, perhaps to a buffer. In WebAssembly 1.0 (20191205), the only way to perform something like +this is via a host function (ex `HostModuleFunctionBuilder`) and internally copy memory corresponding to that string +to a buffer. + +WASI implements system interfaces with host functions. Concretely, to write to console, a WASI command `Module` imports +"fd_write" from "wasi_snapshot_preview1" and calls it with the `fd` parameter set to 1 (STDOUT). + +The [snapshot-01](https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md) version of WASI has no +means to declare configuration, although its function definitions imply configuration for example if fd 1 should exist, +and if so where should it write. Moreover, snapshot-01 was last updated in late 2020 and the specification is being +completely rewritten as of early 2022. This means WASI as defined by "snapshot-01" will not clarify aspects like which +file descriptors are required. While it is possible a subsequent version may, it is too early to tell as no version of +WASI has reached a stage near W3C recommendation. Even if it did, module authors are not required to only use WASI to +write to console, as they can define their own host functions, such as they did before WASI existed. + +wazero aims to serve Go developers as a primary function, and help them transition between WASI specifications. In +order to do this, we have to allow top-level configuration. To ensure isolation by default, `ModuleConfig` has WithXXX +that override defaults to no-op or empty. One `ModuleConfig` instance is used regardless of how many times the same WASI +functions are imported. The nil defaults allow safe concurrency in these situations, as well lower the cost when they +are never used. Finally, a one-to-one mapping with `Module` allows the module to close the `ModuleConfig` instead of +confusing users with another API to close. + +Naming, defaults and validation rules of aspects like `STDIN` and `Environ` are intentionally similar to other Go +libraries such as `exec.Cmd` or `syscall.SetEnv`, and differences called out where helpful. For example, there's no goal +to emulate any operating system primitive specific to Windows (such as a 'c:\' drive). Moreover, certain defaults +working with real system calls are neither relevant nor safe to inherit: For example, `exec.Cmd` defaults to read STDIN +from a real file descriptor ("/dev/null"). Defaulting to this, vs reading `io.EOF`, would be unsafe as it can exhaust +file descriptors if resources aren't managed properly. In other words, blind copying of defaults isn't wise as it can +violate isolation or endanger the embedding process. In summary, we try to be similar to normal Go code, but often need +act differently and document `ModuleConfig` is more about emulating, not necessarily performing real system calls. + +## File systems + +### Motivation on `sys.FS` + +The `sys.FS` abstraction in wazero was created because of limitations in +`fs.FS`, and `fs.File` in Go. Compilers targeting `wasip1` may access +functionality that writes new files. The ability to overcome this was requested +even before wazero was named this, via issue #21 in March 2021. + +A month later, golang/go#45757 was raised by someone else on the same topic. As +of July 2023, this has not resolved to a writeable file system abstraction. + +Over the next year more use cases accumulated, consolidated in March 2022 into +#390. This closed in January 2023 with a milestone of providing more +functionality, limited to users giving a real directory. This didn't yet expose +a file abstraction for general purpose use. Internally, this used `os.File`. +However, a wasm module instance is a virtual machine. Only supporting `os.File` +breaks sand-boxing use cases. Moreover, `os.File` is not an interface. Even +though this abstracts functionality, it does allow interception use cases. + +Hence, a few days later in January 2023, we had more issues asking to expose an +abstraction, #1013 and later #1532, on use cases like masking access to files. +In other words, the use case requests never stopped, and aren't solved by +exposing only real files. + +In summary, the primary motivation for exposing a replacement for `fs.FS` and +`fs.File` was around repetitive use case requests for years, around +interception and the ability to create new files, both virtual and real files. +While some use cases are solved with real files, not all are. Regardless, an +interface approach is necessary to ensure users can intercept I/O operations. + +### Why doesn't `sys.File` have a `Fd()` method? + +There are many features we could expose. We could make File expose underlying +file descriptors in case they are supported, for integration of system calls +that accept multiple ones, namely `poll` for multiplexing. This special case is +described in a subsequent section. + +As noted above, users have been asking for a file abstraction for over two +years, and a common answer was to wait. Making users wait is a problem, +especially so long. Good reasons to make people wait are stabilization. Edge +case features are not a great reason to hold abstractions from users. + +Another reason is implementation difficulty. Go did not attempt to abstract +file descriptors. For example, unlike `fs.ReadFile` there is no `fs.FdFile` +interface. Most likely, this is because file descriptors are an implementation +detail of common features. Programming languages, including Go, do not require +end users to know about file descriptors. Types such as `fs.File` can be used +without any knowledge of them. Implementations may or may not have file +descriptors. For example, in Go, `os.DirFS` has underlying file descriptors +while `embed.FS` does not. + +Despite this, some may want to expose a non-standard interface because +`os.File` has `Fd() uintptr` to return a file descriptor. Mainly, this is +handy to integrate with `syscall` package functions (on `GOOS` values that +declare them). Notice, though that `uintptr` is unsafe and not an abstraction. +Close inspection will find some `os.File` types internally use `poll.FD` +instead, yet this is not possible to use abstractly because that type is not +exposed. For example, `plan9` uses a different type than `poll.FD`. In other +words, even in real files, `Fd()` is not wholly portable, despite it being +useful on many operating systems with the `syscall` package. + +The reasons above, why Go doesn't abstract `FdFile` interface are a subset of +reasons why `sys.File` does not. If we exposed `File.Fd()` we not only would +have to declare all the edge cases that Go describes including impact of +finalizers, we would have to describe these in terms of virtualized files. +Then, we would have to reason with this value vs our existing virtualized +`sys.FileTable`, mapping whatever type we return to keys in that table, also +in consideration of garbage collection impact. The combination of issues like +this could lead down a path of not implementing a file system abstraction at +all, and instead a weak key mapped abstraction of the `syscall` package. Once +we finished with all the edge cases, we would have lost context of the original +reason why we started.. simply to allow file write access! + +When wazero attempts to do more than what the Go programming language team, it +has to be carefully evaluated, to: +* Be possible to implement at least for `os.File` backed files +* Not be confusing or cognitively hard for virtual file systems and normal use. +* Affordable: custom code is solely the responsible by the core team, a much + smaller group of individuals than who maintain the Go programming language. + +Due to problems well known in Go, consideration of the end users who constantly +ask for basic file system functionality, and the difficulty virtualizing file +descriptors at multiple levels, we don't expose `Fd()` and likely won't ever +expose `Fd()` on `sys.File`. + +### Why does `sys.File` have a `Poll()` method, while `sys.FS` does not? + +wazero exposes `File.Poll` which allows one-at-a-time poll use cases, +requested by multiple users. This not only includes abstract tests such as +Go 1.21 `GOOS=wasip1`, but real use cases including python and container2wasm +repls, as well listen sockets. The main use cases is non-blocking poll on a +single file. Being a single file, this has no risk of problems such as +head-of-line blocking, even when emulated. + +The main use case of multi-poll are bidirectional network services, something +not used in `GOOS=wasip1` standard libraries, but could be in the future. +Moving forward without a multi-poller allows wazero to expose its file system +abstraction instead of continuing to hold back it back for edge cases. We'll +continue discussion below regardless, as rationale was requested. + +You can loop through multiple `sys.File`, using `File.Poll` to see if an event +is ready, but there is a head-of-line blocking problem. If a long timeout is +used, bad luck could have a file that has nothing to read or write before one +that does. This could cause more blocking than necessary, even if you could +poll the others just after with a zero timeout. What's worse than this is if +unlimited blocking was used (`timeout=-1`). The host implementations could use +goroutines to avoid this, but interrupting a "forever" poll is problematic. All +of these are reasons to consider a multi-poll API, but do not require exporting +`File.Fd()`. + +Should multi-poll becomes critical, `sys.FS` could expose a `Poll` function +like below, despite it being the non-portable, complicated if possible to +implement on all platforms and virtual file systems. +```go +ready, errno := fs.Poll([]sys.PollFile{{f1, sys.POLLIN}, {f2, sys.POLLOUT}}, timeoutMillis) +``` + +A real filesystem could handle this by using an approach like the internal +`unix.Poll` function in Go, passing file descriptors on unix platforms, or +returning `sys.ENOSYS` for unsupported operating systems. Implementation for +virtual files could have a strategy around timeout to avoid the worst case of +head-of-line blocking (unlimited timeout). + +Let's remember that when designing abstractions, it is not best to add an +interface for everything. Certainly, Go doesn't, as evidenced by them not +exposing `poll.FD` in `os.File`! Such a multi-poll could be limited to +built-in filesystems in the wazero repository, avoiding complexity of trying to +support and test this abstractly. This would still permit multiplexing for CLI +users, and also permit single file polling as exists now. + +### Why doesn't wazero implement the working directory? + +An early design of wazero's API included a `WithWorkDirFS` which allowed +control over which file a relative path such as "./config.yml" resolved to, +independent of the root file system. This intended to help separate concerns +like mutability of files, but it didn't work and was removed. + +Compilers that target wasm act differently with regard to the working +directory. For example, wasi-libc, used by TinyGo, +tracks working directory changes in compiled wasm instead: initially "/" until +code calls `chdir`. Zig assumes the first pre-opened file descriptor is the +working directory. + +The only place wazero can standardize a layered concern is via a host function. +Since WASI doesn't use host functions to track the working directory, we can't +standardize the storage and initial value of it. + +Meanwhile, code may be able to affect the working directory by compiling +`chdir` into their main function, using an argument or ENV for the initial +value (possibly `PWD`). Those unable to control the compiled code should only +use absolute paths in configuration. + +See +* https://github.com/golang/go/blob/go1.24.0/src/syscall/fs_js.go#L341 +* https://github.com/WebAssembly/wasi-libc/pull/214#issue-673090117 +* https://github.com/ziglang/zig/blob/53a9ee699a35a3d245ab6d1dac1f0687a4dcb42c/src/main.zig#L32 + +### Why ignore the error returned by io.Reader when n > 1? + +Per https://pkg.go.dev/io#Reader, if we receive an error, any bytes read should +be processed first. At the syscall abstraction (`fd_read`), the caller is the +processor, so we can't process the bytes inline and also return the error (as +`EIO`). + +Let's assume we want to return the bytes read on error to the caller. This +implies we at least temporarily ignore the error alongside them. The choice +remaining is whether to persist the error returned with the read until a +possible next call, or ignore the error. + +If we persist an error returned, it would be coupled to a file descriptor, but +effectively it is boolean as this case coerces to `EIO`. If we track a "last +error" on a file descriptor, it could be complicated for a couple reasons +including whether the error is transient or permanent, or if the error would +apply to any FD operation, or just read. Finally, there may never be a +subsequent read as perhaps the bytes leading up to the error are enough to +satisfy the processor. + +This decision boils down to whether or not to track an error bit per file +descriptor or not. If not, the assumption is that a subsequent operation would +also error, this time without reading any bytes. + +The current opinion is to go with the simplest path, which is to return the +bytes read and ignore the error the there were any. Assume a subsequent +operation will err if it needs to. This helps reduce the complexity of the code +in wazero and also accommodates the scenario where the bytes read are enough to +satisfy its processor. + +### File descriptor allocation strategy + +File descriptor allocation currently uses a strategy similar the one implemented +by unix systems: when opening a file, the lowest unused number is picked. + +The WASI standard documents that programs cannot expect that file descriptor +numbers will be allocated with a lowest-first strategy, and they should instead +assume the values will be random. Since _random_ is a very imprecise concept in +computers, we technically satisfying the implementation with the descriptor +allocation strategy we use in Wazero. We could imagine adding more _randomness_ +to the descriptor selection process, however this should never be used as a +security measure to prevent applications from guessing the next file number so +there are no strong incentives to complicate the logic. + +### Why does `FSConfig.WithDirMount` not match behaviour with `os.DirFS`? + +It may seem that we should require any feature that seems like a standard +library in Go, to behave the same way as the standard library. Doing so would +present least surprise to Go developers. In the case of how we handle +filesystems, we break from that as it is incompatible with the expectations of +WASI, the most commonly implemented filesystem ABI. + +The main reason is that `os.DirFS` is a virtual filesystem abstraction while +WASI is an abstraction over syscalls. For example, the signature of `fs.Open` +does not permit use of flags. This creates conflict on what default behaviors +to take when Go implemented `os.DirFS`. On the other hand, `path_open` can pass +flags, and in fact tests require them to be honored in specific ways. + +This conflict requires us to choose what to be more compatible with, and which +type of user to surprise the least. We assume there will be more developers +compiling code to wasm than developers of custom filesystem plugins, and those +compiling code to wasm will be better served if we are compatible with WASI. +Hence on conflict, we prefer WASI behavior vs the behavior of `os.DirFS`. + +See https://github.com/WebAssembly/wasi-testsuite +See https://github.com/golang/go/issues/58141 + +## Why is our `Readdir` function more like Go's `os.File` than POSIX `readdir`? + +At one point we attempted to move from a bulk `Readdir` function to something +more like the POSIX `DIR` struct, exposing functions like `telldir`, `seekdir` +and `readdir`. However, we chose the design more like `os.File.Readdir`, +because it performs and fits wasip1 better. + +### wasip1/wasix + +`fd_readdir` in wasip1 (and so also wasix) is like `getdents` in Linux, not +`readdir` in POSIX. `getdents` is more like Go's `os.File.Readdir`. + +We currently have an internal type `sys.DirentCache` which only is used by +wasip1 or wasix. When `HostModuleBuilder` adds support for instantiation state, +we could move this to the `wasi_snapshot_preview1` package. Meanwhile, all +filesystem code is internal anyway, so this special-case is acceptable. + +### wasip2 + +`directory-entry-stream` in wasi-filesystem preview2 is defined in component +model, not an ABI, but in wasmtime it is a consuming iterator. A consuming +iterator is easy to support with anything (like `Readdir(1)`), even if it is +inefficient as you can neither bulk read nor skip. The implementation of the +preview1 adapter (uses preview2) confirms this. They use a dirent cache similar +in some ways to our `sysfs.DirentCache`. As there is no seek concept in +preview2, they interpret the cookie as numeric and read on repeat entries when +a cache wasn't available. Note: we currently do not skip-read like this as it +risks buffering large directories, and no user has requested entries before the +cache, yet. + +Regardless, wasip2 is not complete until the end of 2023. We can defer design +discussion until after it is stable and after the reference impl wasmtime +implements it. + +See + * https://github.com/WebAssembly/wasi-filesystem/blob/ef9fc87c07323a6827632edeb6a7388b31266c8e/example-world.md#directory_entry_stream + * https://github.com/bytecodealliance/wasmtime/blob/b741f7c79d72492d17ab8a29c8ffe4687715938e/crates/wasi/src/preview2/preview2/filesystem.rs#L286-L296 + * https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L2131-L2137 + * https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L936 + +### wasip3 + +`directory-entry-stream` is documented to change significantly in wasip3 moving +from synchronous to synchronous streams. This is dramatically different than +POSIX `readdir` which is synchronous. + +Regardless, wasip3 is not complete until after wasip2, which means 2024 or +later. We can defer design discussion until after it is stable and after the +reference impl wasmtime implements it. + +See + * https://github.com/WebAssembly/WASI/blob/ddfe3d1dda5d1473f37ecebc552ae20ce5fd319a/docs/WitInWasi.md#Streams + * https://docs.google.com/presentation/d/1MNVOZ8hdofO3tI0szg_i-Yoy0N2QPU2C--LzVuoGSlE/edit#slide=id.g1270ef7d5b6_0_662 + +### How do we implement `Pread` with an `fs.File`? + +`ReadAt` is the Go equivalent to `pread`: it does not affect, and is not +affected by, the underlying file offset. Unfortunately, `io.ReaderAt` is not +implemented by all `fs.File`. For example, as of Go 1.19, `embed.openFile` does +not. + +The initial implementation of `fd_pread` instead used `Seek`. To avoid a +regression, we fall back to `io.Seeker` when `io.ReaderAt` is not supported. + +This requires obtaining the initial file offset, seeking to the intended read +offset, and resetting the file offset the initial state. If this final seek +fails, the file offset is left in an undefined state. This is not thread-safe. + +While seeking per read seems expensive, the common case of `embed.openFile` is +only accessing a single int64 field, which is cheap. + +### Pre-opened files + +WASI includes `fd_prestat_get` and `fd_prestat_dir_name` functions used to +learn any directory paths for file descriptors open at initialization time. + +For example, `__wasilibc_register_preopened_fd` scans any file descriptors past +STDERR (1) and invokes `fd_prestat_dir_name` to learn any path prefixes they +correspond to. Zig's `preopensAlloc` does similar. These pre-open functions are +not used again after initialization. + +wazero supports stdio pre-opens followed by any mounts e.g `.:/`. The guest +path is a directory and its name, e.g. "/" is returned by `fd_prestat_dir_name` +for file descriptor 3 (STDERR+1). The first longest match wins on multiple +pre-opens, which allows a path like "/tmp" to match regardless of order vs "/". + +See + * https://github.com/WebAssembly/wasi-libc/blob/a02298043ff551ce1157bc2ee7ab74c3bffe7144/libc-bottom-half/sources/preopens.c + * https://github.com/ziglang/zig/blob/9cb06f3b8bf9ea6b5e5307711bc97328762d6a1d/lib/std/fs/wasi.zig#L50-L53 + +### fd_prestat_dir_name + +`fd_prestat_dir_name` is a WASI function to return the path of the pre-opened +directory of a file descriptor. It has the following three parameters, and the +third `path_len` has ambiguous semantics. + +* `fd`: a file descriptor +* `path`: the offset for the result path +* `path_len`: In wazero, `FdPrestatDirName` writes the result path string to + `path` offset for the exact length of `path_len`. + +Wasmer considers `path_len` to be the maximum length instead of the exact +length that should be written. +See https://github.com/wasmerio/wasmer/blob/3463c51268ed551933392a4063bd4f8e7498b0f6/lib/wasi/src/syscalls/mod.rs#L764 + +The semantics in wazero follows that of wasmtime. +See https://github.com/bytecodealliance/wasmtime/blob/2ca01ae9478f199337cf743a6ab543e8c3f3b238/crates/wasi-common/src/snapshots/preview_1.rs#L578-L582 + +Their semantics match when `path_len` == the length of `path`, so in practice +this difference won't matter match. + +## fd_readdir + +### Why does "wasi_snapshot_preview1" require dot entries when POSIX does not? + +In October 2019, WASI project knew requiring dot entries ("." and "..") was not +documented in preview1, not required by POSIX and problematic to synthesize. +For example, Windows runtimes backed by `FindNextFileW` could not return these. +A year later, the tag representing WASI preview 1 (`snapshot-01`) was made. +This did not include the requested change of making dot entries optional. + +The `phases/snapshot/docs.md` document was altered in subsequent years in +significant ways, often in lock-step with wasmtime or wasi-libc. In January +2022, `sock_accept` was added to `phases/snapshot/docs.md`, a document later +renamed to later renamed to `legacy/preview1/docs.md`. + +As a result, the ABI and behavior remained unstable: The `snapshot-01` tag was +not an effective basis of portability. A test suite was requested well before +this tag, in April 2019. Meanwhile, compliance had no meaning. Developers had +to track changes to the latest doc, while clarifying with wasi-libc or wasmtime +behavior. This lack of stability could have permitted a fix to the dot entries +problem, just as it permitted changes desired by other users. + +In November 2022, the wasi-testsuite project began and started solidifying +expectations. This quickly led to changes in runtimes and the spec doc. WASI +began importing tests from wasmtime as required behaviors for all runtimes. +Some changes implied changes to wasi-libc. For example, `readdir` began to +imply inode fan-outs, which caused performance regressions. Most notably a +test merged in January required dot entries. Tests were merged without running +against any runtime, and even when run ad-hoc only against Linux. Hence, +portability issues mentioned over three years earlier did not trigger any +failure until wazero (which tests Windows) noticed. + +In the same month, wazero requested to revert this change primarily because +Go does not return them from `os.ReadDir`, and materializing them is +complicated due to tests also requiring inodes. Moreover, they are discarded by +not just Go, but other common programming languages. This was rejected by the +WASI lead for preview1, but considered for the completely different ABI named +preview2. + +In February 2023, the WASI chair declared that new rule requiring preview1 to +return dot entries "was decided by the subgroup as a whole", citing meeting +notes. According to these notes, the WASI lead stated incorrectly that POSIX +conformance required returning dot entries, something it explicitly says are +optional. In other words, he said filtering them out would make Preview1 +non-conforming, and asked if anyone objects to this. The co-chair was noted to +say "Because there are existing P1 programs, we shouldn’t make changes like +this." No other were recorded to say anything. + +In summary, preview1 was changed retrospectively to require dot entries and +preview2 was changed to require their absence. This rule was reverse engineered +from wasmtime tests, and affirmed on two false premises: + +* POSIX compliance requires dot entries + * POSIX literally says these are optional +* WASI cannot make changes because there are existing P1 programs. + * Changes to Preview 1 happened before and after this topic. + +As of June 2023, wasi-testsuite still only runs on Linux, so compliance of this +rule on Windows is left to runtimes to decide to validate. The preview2 adapter +uses fake cookies zero and one to refer to dot dirents, uses a real inode for +the dot(".") entry and zero inode for dot-dot(".."). + +See https://github.com/WebAssembly/wasi-filesystem/issues/3 +See https://github.com/WebAssembly/WASI/tree/snapshot-01 +See https://github.com/WebAssembly/WASI/issues/9 +See https://github.com/WebAssembly/WASI/pull/458 +See https://github.com/WebAssembly/wasi-testsuite/pull/32 +See https://github.com/WebAssembly/wasi-libc/pull/345 +See https://github.com/WebAssembly/wasi-testsuite/issues/52 +See https://github.com/WebAssembly/WASI/pull/516 +See https://github.com/WebAssembly/meetings/blob/main/wasi/2023/WASI-02-09.md#should-preview1-fd_readdir-filter-out--and- +See https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L1026-L1041 + +### Why are dot (".") and dot-dot ("..") entries problematic? + +When reading a directory, dot (".") and dot-dot ("..") entries are problematic. +For example, Go does not return them from `os.ReadDir`, and materializing them +is complicated (at least dot-dot is). + +A directory entry has stat information in it. The stat information includes +inode which is used for comparing file equivalence. In the simple case of dot, +we could materialize a special entry to expose the same info as stat on the fd +would return. However, doing this and not doing dot-dot would cause confusion, +and dot-dot is far more tricky. To back-fill inode information about a parent +directory would be costly and subtle. For example, the pre-open (mount) of the +directory may be different than its logical parent. This is easy to understand +when considering the common case of mounting "/" and "/tmp" as pre-opens. To +implement ".." from "/tmp" requires information from a separate pre-open, this +includes state to even know the difference. There are easier edge cases as +well, such as the decision to not return ".." from a root path. In any case, +this should start to explain that faking entries when underlying stdlib doesn't +return them is tricky and requires quite a lot of state. + +Another issue is around the `Dirent.Off` value of a directory entry, sometimes +called a "cookie" in Linux man pagers. When the host operating system or +library function does not return dot entries, to support functions such as +`seekdir`, you still need a value for `Dirent.Off`. Naively, you can synthesize +these by choosing sequential offsets zero and one. However, POSIX strictly says +offsets should be treated opaquely. The backing filesystem could use these to +represent real entries. For example, a directory with one entry could use zero +as the `Dirent.Off` value. If you also used zero for the "." dirent, there +would be a clash. This means if you synthesize `Dirent.Off` for any entry, you +need to synthesize this value for all entries. In practice, the simplest way is +using an incrementing number, such as done in the WASI preview2 adapter. + +Working around these issues causes expense to all users of wazero, so we'd +then look to see if that would be justified or not. However, the most common +compilers involved in end user questions, as of early 2023 are TinyGo, Rust and +Zig. All of these compile code which ignores dot and dot-dot entries. In other +words, faking these entries would not only cost our codebase with complexity, +but it would also add unnecessary overhead as the values aren't commonly used. + +The final reason why we might do this, is an end users or a specification +requiring us to. As of early 2023, no end user has raised concern over Go and +by extension wazero not returning dot and dot-dot. The snapshot-01 spec of WASI +does not mention anything on this point. Also, POSIX has the following to say, +which summarizes to "these are optional" + +> The readdir() function shall not return directory entries containing empty names. If entries for dot or dot-dot exist, one entry shall be returned for dot and one entry shall be returned for dot-dot; otherwise, they shall not be returned. + +Unfortunately, as described above, the WASI project decided in early 2023 to +require dot entries in both the spec and the wasi-testsuite. For only this +reason, wazero adds overhead to synthesize dot entries despite it being +unnecessary for most users. + +See https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html +See https://github.com/golang/go/blob/go1.24.0/src/os/dir_unix.go#L122-L124 +See https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L1026-L1041 + +### Why don't we pre-populate an inode for the dot-dot ("..") entry? + +We only populate an inode for dot (".") because wasi-testsuite requires it, and +we likely already have it (because we cache it). We could attempt to populate +one for dot-dot (".."), but chose not to. + +Firstly, wasi-testsuite does not require the inode of dot-dot, possibly because +the wasip2 adapter doesn't populate it (but we don't really know why). + +The only other reason to populate it would be to avoid wasi-libc's stat fanout +when it is missing. However, wasi-libc explicitly doesn't fan-out to lstat on +the ".." entry on a zero ino. + +Fetching dot-dot's inode despite the above not only doesn't help wasi-libc, but +it also hurts languages that don't use it, such as Go. These languages would +pay a stat syscall penalty even if they don't need the inode. In fact, Go +discards both dot entries! + +In summary, there are no significant upsides in attempting to pre-fetch +dot-dot's inode, and there are downsides to doing it anyway. + +See + * https://github.com/WebAssembly/wasi-libc/blob/bd950eb128bff337153de217b11270f948d04bb4/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c#L87-L94 + * https://github.com/WebAssembly/wasi-testsuite/blob/main/tests/rust/src/bin/fd_readdir.rs#L108 + * https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L1037 + +### Why don't we require inodes to be non-zero? + +We don't require a non-zero value for `Dirent.Ino` because doing so can prevent +a real one from resolving later via `Stat_t.Ino`. + +We define `Ino` like `d_ino` in POSIX which doesn't special-case zero. It can +be zero for a few reasons: + +* The file is not a regular file or directory. +* The underlying filesystem does not support inodes. e.g. embed:fs +* A directory doesn't include inodes, but a later stat can. e.g. Windows +* The backend is based on wasi-filesystem (a.k.a wasip2), which has + `directory_entry.inode` optional, and might remove it entirely. + +There are other downsides to returning a zero inode in widely used compilers: + +* File equivalence utilities, like `os.SameFile` will not work. +* wasi-libc's `wasip1` mode will call `lstat` and attempt to retrieve a + non-zero value (unless the entry is named ".."). + +A new compiler may accidentally skip a `Dirent` with a zero `Ino` if emulating +a non-POSIX function and re-using `Dirent.Ino` for `d_fileno`. + +* Linux `getdents` doesn't define `d_fileno` must be non-zero +* BSD `getdirentries` is implementation specific. For example, OpenBSD will + return dirents with a zero `d_fileno`, but Darwin will skip them. + +The above shouldn't be a problem, even in the case of BSD, because `wasip1` is +defined more in terms of `getdents` than `getdirentries`. The bottom half of +either should treat `wasip1` (or any similar ABI such as wasix or wasip2) as a +different operating system and either use different logic that doesn't skip, or +synthesize a fake non-zero `d_fileno` when `d_ino` is zero. + +However, this has been a problem. Go's `syscall.ParseDirent` utility is shared +for all `GOOS=unix`. For simplicity, this abstracts `direntIno` with data from +`d_fileno` or `d_ino`, and drops if either are zero, even if `d_fileno` is the +only field with zero explicitly defined. This led to a change to special case +`GOOS=wasip1` as otherwise virtual files would be unconditionally skipped. + +In practice, this problem is rather unique due to so many compilers relying on +wasi-libc, which tolerates a zero inode. For example, while issues were +reported about the performance regression when wasi-libc began doing a fan-out +on zero `Dirent.Ino`, no issues were reported about dirents being dropped as a +result. + +In summary, rather than complicating implementation and forcing non-zero inodes +for a rare case, we permit zero. We instead document this topic thoroughly, so +that emerging compilers can re-use the research and reference it on conflict. +We also document that `Ino` should be non-zero, so that users implementing that +field will attempt to get it. + +See + * https://github.com/WebAssembly/wasi-filesystem/pull/81 + * https://github.com/WebAssembly/wasi-libc/blob/bd950eb128bff337153de217b11270f948d04bb4/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c#L87-L94 + * https://linux.die.net/man/3/getdents + * https://www.unix.com/man-page/osx/2/getdirentries/ + * https://man.openbsd.org/OpenBSD-5.4/getdirentries.2 + * https://github.com/golang/go/blob/go1.24.0/src/syscall/dirent.go#L57-L101 + * https://go-review.googlesource.com/c/go/+/507915 + +## sys.Walltime and Nanotime + +The `sys` package has two function types, `Walltime` and `Nanotime` for real +and monotonic clock exports. The naming matches conventions used in Go. + +```go +func time_now() (sec int64, nsec int32, mono int64) { + sec, nsec = walltime() + return sec, nsec, nanotime() +} +``` + +Splitting functions for wall and clock time allow implementations to choose +whether to implement the clock once (as in Go), or split them out. + +Each can be configured with a `ClockResolution`, although is it usually +incorrect as detailed in a sub-heading below. The only reason for exposing this +is to satisfy WASI: + +See https://github.com/WebAssembly/wasi-clocks + +### Why default to fake time? + +WebAssembly has an implicit design pattern of capabilities based security. By +defaulting to a fake time, we reduce the chance of timing attacks, at the cost +of requiring configuration to opt-into real clocks. + +See https://gruss.cc/files/fantastictimers.pdf for an example attacks. + +### Why does fake time increase on reading? + +Both the fake nanotime and walltime increase by 1ms on reading. Particularly in +the case of nanotime, this prevents spinning. + +### Why not `time.Clock`? + +wazero can't use `time.Clock` as a plugin for clock implementation as it is +only substitutable with build flags (`faketime`) and conflates wall and +monotonic time in the same call. + +Go's `time.Clock` was added monotonic time after the fact. For portability with +prior APIs, a decision was made to combine readings into the same API call. + +See https://go.googlesource.com/proposal/+/master/design/12914-monotonic.md + +WebAssembly time imports do not have the same concern. In fact even Go's +imports for clocks split walltime from nanotime readings. + +See https://github.com/golang/go/blob/go1.24.0/lib/wasm/wasm_exec.js#L258-L268 + +Finally, Go's clock is not an interface. WebAssembly users who want determinism +or security need to be able to substitute an alternative clock implementation +from the host process one. + +### `ClockResolution` + +A clock's resolution is hardware and OS dependent so requires a system call to retrieve an accurate value. +Go does not provide a function for getting resolution, so without CGO we don't have an easy way to get an actual +value. For now, we return fixed values of 1us for realtime and 1ns for monotonic, assuming that realtime clocks are +often lower precision than monotonic clocks. In the future, this could be improved by having OS+arch specific assembly +to make syscalls. + +For example, Go implements time.Now for linux-amd64 with this [assembly](https://github.com/golang/go/blob/go1.24.0/src/runtime/time_linux_amd64.s). +Because retrieving resolution is not generally called often, unlike getting time, it could be appropriate to only +implement the fallback logic that does not use VDSO (executing syscalls in user mode). The syscall for clock_getres +is 229 and should be usable. https://pkg.go.dev/syscall#pkg-constants. + +If implementing similar for Windows, [mingw](https://github.com/mirror/mingw-w64/blob/v12.0.0/mingw-w64-libraries/winpthreads/src/clock.c#L54) +is often a good source to find the Windows API calls that correspond +to a POSIX method. + +Writing assembly would allow making syscalls without CGO, but comes with the cost that it will require implementations +across many combinations of OS and architecture. + +## sys.Nanosleep + +All major programming languages have a `sleep` mechanism to block for a +duration. Sleep is typically implemented by a WASI `poll_oneoff` relative clock +subscription. + +For example, the below ends up calling `wasi_snapshot_preview1.poll_oneoff`: + +```zig +const std = @import("std"); +pub fn main() !void { + std.time.sleep(std.time.ns_per_s * 5); +} +``` + +Besides Zig, this is also the case with TinyGo (`-target=wasi`) and Rust +(`--target wasm32-wasi`). + +We decided to expose `sys.Nanosleep` to allow overriding the implementation +used in the common case, even if it isn't used by Go, because this gives an +easy and efficient closure over a common program function. We also documented +`sys.Nanotime` to warn users that some compilers don't optimize sleep. + +## sys.Osyield + +We expose `sys.Osyield`, to allow users to control the behavior of WASI's +`sched_yield` without a new build of wazero. This is mainly for parity with +all other related features which we allow users to implement, including +`sys.Nanosleep`. Unlike others, we don't provide an out-of-box implementation +primarily because it will cause performance problems when accessed. + +For example, the below implementation uses CGO, which might result in a 1us +delay per invocation depending on the platform. + +See https://github.com/golang/go/issues/19409#issuecomment-284788196 +```go +//go:noescape +//go:linkname osyield runtime.osyield +func osyield() +``` + +In practice, a request to customize this is unlikely to happen until other +thread based functions are implemented. That said, as of early 2023, there are +a few signs of implementation interest and cross-referencing: + +See https://github.com/WebAssembly/stack-switching/discussions/38 +See https://github.com/WebAssembly/wasi-threads#what-can-be-skipped +See https://slinkydeveloper.com/Kubernetes-controllers-A-New-Hope/ + +## sys.Stat_t + +We expose `stat` information as `sys.Stat_t`, like `syscall.Stat_t` except +defined without build constraints. For example, you can use `sys.Stat_t` on +`GOOS=windows` which doesn't define `syscall.Stat_t`. + +The first use case of this is to return inodes from `fs.FileInfo` without +relying on platform-specifics. For example, a user could return `*sys.Stat_t` +from `info.Sys()` and define a non-zero inode for a virtual file, or map a +real inode to a virtual one. + +Notable choices per field are listed below, where `sys.Stat_t` is unlike +`syscall.Stat_t` on `GOOS=linux`, or needs clarification. One common issue +not repeated below is that numeric fields are 64-bit when at least one platform +defines it that large. Also, zero values are equivalent to nil or absent. + +* `Dev` and `Ino` (`Inode`) are both defined unsigned as they are defined + opaque, and most `syscall.Stat_t` also defined them unsigned. There are + separate sections in this document discussing the impact of zero in `Ino`. +* `Mode` is defined as a `fs.FileMode` even though that is not defined in POSIX + and will not map to all possible values. This is because the current use is + WASI, which doesn't define any types or features not already supported. By + using `fs.FileMode`, we can re-use routine experience in Go. +* `NLink` is unsigned because it is defined that way in `syscall.Stat_t`: there + can never be less than zero links to a file. We suggest defaulting to 1 in + conversions when information is not knowable because at least that many links + exist. +* `Size` is signed because it is defined that way in `syscall.Stat_t`: while + regular files and directories will always be non-negative, irregular files + are possibly negative or not defined. Notably sparse files are known to + return negative values. +* `Atim`, `Mtim` and `Ctim` are signed because they are defined that way in + `syscall.Stat_t`: Negative values are time before 1970. The resolution is + nanosecond because that's the maximum resolution currently supported in Go. + +### Why do we use `sys.EpochNanos` instead of `time.Time` or similar? + +To simplify documentation, we defined a type alias `sys.EpochNanos` for int64. +`time.Time` is a data structure, and we could have used this for +`syscall.Stat_t` time values. The most important reason we do not is conversion +penalty deriving time from common types. + +The most common ABI used in `wasip2`. This, and compatible ABI such as `wasix`, +encode timestamps in memory as a 64-bit number. If we used `time.Time`, we +would have to convert an underlying type like `syscall.Timespec` to `time.Time` +only to later have to call `.UnixNano()` to convert it back to a 64-bit number. + +In the future, the component model module "wasi-filesystem" may represent stat +timestamps with a type shared with "wasi-clocks", abstractly structured similar +to `time.Time`. However, component model intentionally does not define an ABI. +It is likely that the canonical ABI for timestamp will be in two parts, but it +is not required for it to be intermediately represented this way. A utility +like `syscall.NsecToTimespec` could split an int64 so that it could be written +to memory as 96 bytes (int64, int32), without allocating a struct. + +Finally, some may confuse epoch nanoseconds with 32-bit epoch seconds. While +32-bit epoch seconds has "The year 2038" problem, epoch nanoseconds has +"The Year 2262" problem, which is even less concerning for this library. If +the Go programming language and wazero exist in the 2200's, we can make a major +version increment to adjust the `sys.EpochNanos` approach. Meanwhile, we have +faster code. + +## poll_oneoff + +`poll_oneoff` is a WASI API for waiting for I/O events on multiple handles. +It is conceptually similar to the POSIX `poll(2)` syscall. +The name is not `poll`, because it references [“the fact that this function is not efficient +when used repeatedly with the same large set of handles”][poll_oneoff]. + +We chose to support this API in a handful of cases that work for regular files +and standard input. We currently do not support other types of file descriptors such +as socket handles. + +### Clock Subscriptions + +As detailed above in [sys.Nanosleep](#sysnanosleep), `poll_oneoff` handles +relative clock subscriptions. In our implementation we use `sys.Nanosleep()` +for this purpose in most cases, except when polling for interactive input +from `os.Stdin` (see more details below). + +### FdRead and FdWrite Subscriptions + +When subscribing a file descriptor (except `Stdin`) for reads or writes, +the implementation will generally return immediately with success, unless +the file descriptor is unknown. The file descriptor is not checked further +for new incoming data. Any timeout is cancelled, and the API call is able +to return, unless there are subscriptions to `Stdin`: these are handled +separately. + +### FdRead and FdWrite Subscription to Stdin + +Subscribing `Stdin` for reads (writes make no sense and cause an error), +requires extra care: wazero allows to configure a custom reader for `Stdin`. + +In general, if a custom reader is found, the behavior will be the same +as for regular file descriptors: data is assumed to be present and +a success is written back to the result buffer. + +However, if the reader is detected to read from `os.Stdin`, +a special code path is followed, invoking `sysfs.poll()`. + +`sysfs.poll()` is a wrapper for `poll(2)` on POSIX systems, +and it is emulated on Windows. + +### Poll on POSIX + +On POSIX systems, `poll(2)` allows to wait for incoming data on a file +descriptor, and block until either data becomes available or the timeout +expires. + +Usage of `syfs.poll()` is currently only reserved for standard input, because + +1. it is really only necessary to handle interactive input: otherwise, + there is no way in Go to peek from Standard Input without actually + reading (and thus consuming) from it; + +2. if `Stdin` is connected to a pipe, it is ok in most cases to return + with success immediately; + +3. `syfs.poll()` is currently a blocking call, irrespective of goroutines, + because the underlying syscall is; thus, it is better to limit its usage. + +So, if the subscription is for `os.Stdin` and the handle is detected +to correspond to an interactive session, then `sysfs.poll()` will be +invoked with a the `Stdin` handle *and* the timeout. + +This also means that in this specific case, the timeout is uninterruptible, +unless data becomes available on `Stdin` itself. + +### Select on Windows + +On Windows `sysfs.poll()` cannot be delegated to a single +syscall, because there is no single syscall to handle sockets, +pipes and regular files. + +Instead, we emulate its behavior for the cases that are currently +of interest. + +- For regular files, we _always_ report them as ready, as +[most operating systems do anyway][async-io-windows]. + +- For pipes, we invoke [`PeekNamedPipe`][peeknamedpipe] +for each file handle we detect is a pipe open for reading. +We currently ignore pipes open for writing. + +- Notably, we include also support for sockets using the [WinSock +implementation of `poll`][wsapoll], but instead +of relying on the timeout argument of the `WSAPoll` function, +we set a 0-duration timeout so that it behaves like a peek. + +This way, we can check for regular files all at once, +at the beginning of the function, then we poll pipes and +sockets periodically using a cancellable `time.Tick`, +which plays nicely with the rest of the Go runtime. + +### Impact of blocking + +Because this is a blocking syscall, it will also block the carrier thread of +the goroutine, preventing any means to support context cancellation directly. + +There are ways to obviate this issue. We outline here one idea, that is however +not currently implemented. A common approach to support context cancellation is +to add a signal file descriptor to the set, e.g. the read-end of a pipe or an +eventfd on Linux. When the context is canceled, we may unblock a Select call by +writing to the fd, causing it to return immediately. This however requires to +do a bit of housekeeping to hide the "special" FD from the end-user. + +[poll_oneoff]: https://github.com/WebAssembly/wasi-poll#why-is-the-function-called-poll_oneoff +[async-io-windows]: https://tinyclouds.org/iocp_links +[peeknamedpipe]: https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-peeknamedpipe +[wsapoll]: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsapoll + +## Signed encoding of integer global constant initializers + +wazero treats integer global constant initializers signed as their interpretation is not known at declaration time. For +example, there is no signed integer [value type](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#value-types%E2%91%A0). + +To get at the problem, let's use an example. +``` +(global (export "start_epoch") i64 (i64.const 1620216263544)) +``` + +In both signed and unsigned LEB128 encoding, this value is the same bit pattern. The problem is that some numbers are +not. For example, 16256 is `807f` encoded as unsigned, but `80ff00` encoded as signed. + +While the specification mentions uninterpreted integers are in abstract [unsigned values](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#integers%E2%91%A0), +the binary encoding is clear that they are encoded [signed](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#integers%E2%91%A4). + +For consistency, we go with signed encoding in the special case of global constant initializers. + +## Implementation limitations + +WebAssembly 1.0 (20191205) specification allows runtimes to [limit certain aspects of Wasm module or execution](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#a2-implementation-limitations). + +wazero limitations are imposed pragmatically and described below. + +### Number of functions in a module + +The possible number of function instances in [a module](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#module-instances%E2%91%A0) is not specified in the WebAssembly specifications since [`funcaddr`](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-funcaddr) corresponding to a function instance in a store can be arbitrary number. +wazero limits the maximum function instances to 2^27 as even that number would occupy 1GB in function pointers. + +That is because not only we _believe_ that all use cases are fine with the limitation, but also we have no way to test wazero runtimes under these unusual circumstances. + +### Number of function types in a store + +There's no limitation on the number of function types in [a store](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#store%E2%91%A0) according to the spec. In wazero implementation, we assign each function type to a unique ID, and choose to use `uint32` to represent the IDs. +Therefore the maximum number of function types a store can have is limited to 2^27 as even that number would occupy 512MB just to reference the function types. + +This is due to the same reason for the limitation on the number of functions above. + +### Number of values on the stack in a function + +While the the spec does not clarify a limitation of function stack values, wazero limits this to 2^27 = 134,217,728. +The reason is that we internally represent all the values as 64-bit integers regardless of its types (including f32, f64), and 2^27 values means +1 GiB = (2^30). 1 GiB is the reasonable for most applications [as we see a Goroutine has 250 MB as a limit on the stack for 32-bit arch](https://github.com/golang/go/blob/go1.24.0/src/runtime/proc.go#L154-L161), considering that WebAssembly is (currently) 32-bit environment. + +All the functions are statically analyzed at module instantiation phase, and if a function can potentially reach this limit, an error is returned. + +### Number of globals in a module + +Theoretically, a module can declare globals (including imports) up to 2^32 times. However, wazero limits this to 2^27(134,217,728) per module. +That is because internally we store globals in a slice with pointer types (meaning 8 bytes on 64-bit platforms), and therefore 2^27 globals +means that we have 1 GiB size of slice which seems large enough for most applications. + +### Number of tables in a module + +While the the spec says that a module can have up to 2^32 tables, wazero limits this to 2^27 = 134,217,728. +One of the reasons is even that number would occupy 1GB in the pointers tables alone. Not only that, we access tables slice by +table index by using 32-bit signed offset in the compiler implementation, which means that the table index of 2^27 can reach 2^27 * 8 (pointer size on 64-bit machines) = 2^30 offsets in bytes. + +We _believe_ that all use cases are fine with the limitation, but also note that we have no way to test wazero runtimes under these unusual circumstances. + +If a module reaches this limit, an error is returned at the compilation phase. + +## Compiler engine implementation + +### Why it's safe to execute runtime-generated machine codes against async Goroutine preemption + +Goroutine preemption is the mechanism of the Go runtime to switch goroutines contexts on an OS thread. +There are two types of preemption: cooperative preemption and async preemption. The former happens, for example, +when making a function call, and it is not an issue for our runtime-generated functions as they do not make +direct function calls to Go-implemented functions. On the other hand, the latter, async preemption, can be problematic +since it tries to interrupt the execution of Goroutine at any point of function, and manipulates CPU register states. + +Fortunately, our runtime-generated machine codes do not need to take the async preemption into account. +All the assembly codes are entered via the trampoline implemented as Go Assembler Function (e.g. [arch_amd64.s](./arch_amd64.s)), +and as of Go 1.20, these assembler functions are considered as _unsafe_ for async preemption: +- https://github.com/golang/go/blob/go1.24.0/src/runtime/preempt.go#L407-L408 +- https://github.com/golang/go/blob/go1.24.0/src/runtime/traceback.go#L350 + +From the Go runtime point of view, the execution of runtime-generated machine codes is considered as a part of +that trampoline function. Therefore, runtime-generated machine code is also correctly considered unsafe for async preemption. + +## Why context cancellation is handled in Go code rather than native code + +Since [wazero v1.0.0-pre.9](https://github.com/tetratelabs/wazero/releases/tag/v1.0.0-pre.9), the runtime +supports integration with Go contexts to interrupt execution after a timeout, or in response to explicit cancellation. +This support is internally implemented as a special opcode `builtinFunctionCheckExitCode` that triggers the execution of +a Go function (`ModuleInstance.FailIfClosed`) that atomically checks a sentinel value at strategic points in the code. + +[It _is indeed_ possible to check the sentinel value directly, without leaving the native world][native_check], thus sparing some cycles; +however, because native code never preempts (see section above), this may lead to a state where the other goroutines +never get the chance to run, and thus never get the chance to set the sentinel value; effectively preventing +cancellation from taking place. + +[native_check]: https://github.com/tetratelabs/wazero/issues/1409 + +## Golang patterns + +### Hammer tests +Code that uses concurrency primitives, such as locks or atomics, should include "hammer tests", which run large loops +inside a bounded amount of goroutines, run by half that many `GOMAXPROCS`. These are named consistently "hammer", so +they are easy to find. The name inherits from some existing tests in [golang/go](https://github.com/golang/go/search?q=hammer&type=code). + +Here is an annotated description of the key pieces of a hammer test: +1. `P` declares the count of goroutines to use, defaulting to 8 or 4 if `testing.Short`. + * Half this amount are the cores used, and 4 is less than a modern laptop's CPU. This allows multiple "hammer" tests to run in parallel. +2. `N` declares the scale of work (loop) per goroutine, defaulting to value that finishes in ~0.1s on a modern laptop. + * When in doubt, try 1000 or 100 if `testing.Short` + * Remember, there are multiple hammer tests and CI nodes are slow. Slower tests hurt feedback loops. +3. `defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(P/2))` makes goroutines switch cores, testing visibility of shared data. +4. To ensure goroutines execute at the same time, block them with `sync.WaitGroup`, initialized to `Add(P)`. + * `sync.WaitGroup` internally uses `runtime_Semacquire` not available in any other library. + * `sync.WaitGroup.Add` with a negative value can unblock many goroutines at the same time, e.g. without a for loop. +5. Track goroutines progress via `finished := make(chan int)` where each goroutine in `P` defers `finished <- 1`. + 1. Tests use `require.XXX`, so `recover()` into `t.Fail` in a `defer` function before `finished <- 1`. + * This makes it easier to spot larger concurrency problems as you see each failure, not just the first. + 2. After the `defer` function, await unblocked, then run the stateful function `N` times in a normal loop. + * This loop should trigger shared state problems as locks or atomics are contended by `P` goroutines. +6. After all `P` goroutines launch, atomically release all of them with `WaitGroup.Add(-P)`. +7. Block the runner on goroutine completion, by (`<-finished`) for each `P`. +8. When all goroutines complete, `return` if `t.Failed()`, otherwise perform follow-up state checks. + +This is implemented in wazero in [hammer.go](internal/testing/hammer/hammer.go) + +### Lock-free, cross-goroutine observations of updates + +How to achieve cross-goroutine reads of a variable are not explicitly defined in https://go.dev/ref/mem. wazero uses +atomics to implement this following unofficial practice. For example, a `Close` operation can be guarded to happen only +once via compare-and-swap (CAS) against a zero value. When we use this pattern, we consistently use atomics to both +read and update the same numeric field. + +In lieu of formal documentation, we infer this pattern works from other sources (besides tests): + * `sync.WaitGroup` by definition must support calling `Add` from other goroutines. Internally, it uses atomics. + * rsc in golang/go#5045 writes "atomics guarantee sequential consistency among the atomic variables". + +See https://github.com/golang/go/blob/go1.24.0/src/sync/waitgroup.go#L76 +See https://github.com/golang/go/issues/5045#issuecomment-252730563 +See https://www.youtube.com/watch?v=VmrEG-3bWyM diff --git a/vendor/github.com/tetratelabs/wazero/README.md b/vendor/github.com/tetratelabs/wazero/README.md new file mode 100644 index 00000000000..ceacae584a2 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/README.md @@ -0,0 +1,135 @@ +# wazero: the zero dependency WebAssembly runtime for Go developers + +[![Go Reference](https://pkg.go.dev/badge/github.com/tetratelabs/wazero.svg)](https://pkg.go.dev/github.com/tetratelabs/wazero) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +WebAssembly is a way to safely run code compiled in other languages. Runtimes +execute WebAssembly Modules (Wasm), which are most often binaries with a `.wasm` +extension. + +wazero is a WebAssembly Core Specification [1.0][1] and [2.0][2] compliant +runtime written in Go. It has *zero dependencies*, and doesn't rely on CGO. +This means you can run applications in other languages and still keep cross +compilation. + +Import wazero and extend your Go application with code written in any language! + +## Example + +The best way to learn wazero is by trying one of our [examples](examples/README.md). The +most [basic example](examples/basic) extends a Go application with an addition +function defined in WebAssembly. + +## Runtime + +There are two runtime configurations supported in wazero: _Compiler_ is default: + +By default, ex `wazero.NewRuntime(ctx)`, the Compiler is used if supported. You +can also force the interpreter like so: +```go +r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter()) +``` + +### Interpreter +Interpreter is a naive interpreter-based implementation of Wasm virtual +machine. Its implementation doesn't have any platform (GOARCH, GOOS) specific +code, therefore _interpreter_ can be used for any compilation target available +for Go (such as `riscv64`). + +### Compiler +Compiler compiles WebAssembly modules into machine code ahead of time (AOT), +during `Runtime.CompileModule`. This means your WebAssembly functions execute +natively at runtime. Compiler is faster than Interpreter, often by order of +magnitude (10x) or more. This is done without host-specific dependencies. + +### Conformance + +Both runtimes pass WebAssembly Core [1.0][3] and [2.0][4] specification tests +on supported platforms: + +| Runtime | Usage | amd64 | arm64 | others | +|:-----------:|:--------------------------------------:|:-----:|:-----:|:------:| +| Interpreter | `wazero.NewRuntimeConfigInterpreter()` | ✅ | ✅ | ✅ | +| Compiler | `wazero.NewRuntimeConfigCompiler()` | ✅ | ✅ | ❌ | + +## Support Policy + +The below support policy focuses on compatibility concerns of those embedding +wazero into their Go applications. + +### wazero + +wazero's [1.0 release][8] happened in March 2023, and is [in use][9] by many +projects and production sites. + +We offer an API stability promise with semantic versioning. In other words, we +promise to not break any exported function signature without incrementing the +major version. This does not mean no innovation: New features and behaviors +happen with a minor version increment, e.g. 1.0.11 to 1.2.0. We also fix bugs +or change internal details with a patch version, e.g. 1.0.0 to 1.0.1. + +You can get the latest version of wazero like this. +```bash +go get github.com/tetratelabs/wazero@latest +``` + +Please give us a [star][10] if you end up using wazero! + +### Go + +wazero has no dependencies except Go and [`x/sys`][12], so the only source of +conflict in your project's use of wazero is the Go version. + +wazero follows the same version policy as Go's [Release Policy][5]: two +versions. wazero will ensure these versions work and bugs are valid if there's +an issue with a current Go version. + +### Platform + +wazero has two runtime modes: Interpreter and Compiler. The only supported operating +systems are ones we test, but that doesn't necessarily mean other operating +system versions won't work. + +We currently test Linux (Ubuntu and scratch), MacOS and Windows as packaged by +[GitHub Actions][6], as well as nested VMs running on Linux for FreeBSD, NetBSD, +OpenBSD, DragonFly BSD, illumos and Solaris. + +We also test cross compilation for many `GOOS` and `GOARCH` combinations. + +* Interpreter + * Linux is tested on amd64 and arm64 (native) as well as riscv64 via emulation. + * Windows, FreeBSD, NetBSD, OpenBSD, DragonFly BSD, illumos and Solaris are + tested only on amd64. + * macOS is tested only on arm64. +* Compiler + * Linux is tested on amd64 and arm64. + * Windows, FreeBSD, NetBSD, DragonFly BSD, illumos and Solaris are + tested only on amd64. + * macOS is tested only on arm64. + +wazero has no dependencies and doesn't require CGO. This means it can also be +embedded in an application that doesn't use an operating system. This is a main +differentiator between wazero and alternatives. + +We verify zero dependencies by running tests in Docker's [scratch image][7]. +This approach ensures compatibility with any parent image. + +### macOS code-signing entitlements + +If you're developing for macOS and need to code-sign your application, +please read issue [#2393][11]. + +----- +wazero is a registered trademark of Tetrate.io, Inc. in the United States and/or other countries + +[1]: https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/ +[2]: https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/ +[3]: https://github.com/WebAssembly/spec/tree/wg-1.0/test/core +[4]: https://github.com/WebAssembly/spec/tree/d39195773112a22b245ffbe864bab6d1182ccb06/test/core +[5]: https://go.dev/doc/devel/release +[6]: https://github.com/actions/virtual-environments +[7]: https://docs.docker.com/develop/develop-images/baseimages/#create-a-simple-parent-image-using-scratch +[8]: https://tetrate.io/blog/introducing-wazero-from-tetrate/ +[9]: https://wazero.io/community/users/ +[10]: https://github.com/wazero/wazero/stargazers +[11]: https://github.com/wazero/wazero/issues/2393 +[12]: https://pkg.go.dev/golang.org/x/sys \ No newline at end of file diff --git a/vendor/github.com/tetratelabs/wazero/api/features.go b/vendor/github.com/tetratelabs/wazero/api/features.go new file mode 100644 index 00000000000..c739d3bf791 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/api/features.go @@ -0,0 +1,214 @@ +package api + +import ( + "fmt" + "strings" +) + +// CoreFeatures is a bit flag of WebAssembly Core specification features. See +// https://github.com/WebAssembly/proposals for proposals and their status. +// +// Constants define individual features, such as CoreFeatureMultiValue, or +// groups of "finished" features, assigned to a WebAssembly Core Specification +// version, e.g. CoreFeaturesV1 or CoreFeaturesV2. +// +// Note: Numeric values are not intended to be interpreted except as bit flags. +type CoreFeatures uint64 + +// CoreFeaturesV1 are features included in the WebAssembly Core Specification +// 1.0. As of late 2022, this is the only version that is a Web Standard (W3C +// Recommendation). +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/ +const CoreFeaturesV1 = CoreFeatureMutableGlobal + +// CoreFeaturesV2 are features included in the WebAssembly Core Specification +// 2.0 (20220419). As of late 2022, version 2.0 is a W3C working draft, not yet +// a Web Standard (W3C Recommendation). +// +// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#release-1-1 +const CoreFeaturesV2 = CoreFeaturesV1 | + CoreFeatureBulkMemoryOperations | + CoreFeatureMultiValue | + CoreFeatureNonTrappingFloatToIntConversion | + CoreFeatureReferenceTypes | + CoreFeatureSignExtensionOps | + CoreFeatureSIMD + +const ( + // CoreFeatureBulkMemoryOperations adds instructions modify ranges of + // memory or table entries ("bulk-memory-operations"). This is included in + // CoreFeaturesV2, but not CoreFeaturesV1. + // + // Here are the notable effects: + // - Adds `memory.fill`, `memory.init`, `memory.copy` and `data.drop` + // instructions. + // - Adds `table.init`, `table.copy` and `elem.drop` instructions. + // - Introduces a "passive" form of element and data segments. + // - Stops checking "active" element and data segment boundaries at + // compile-time, meaning they can error at runtime. + // + // Note: "bulk-memory-operations" is mixed with the "reference-types" + // proposal due to the WebAssembly Working Group merging them + // "mutually dependent". Therefore, enabling this feature requires enabling + // CoreFeatureReferenceTypes, and vice-versa. + // + // See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/bulk-memory-operations/Overview.md + // https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/reference-types/Overview.md and + // https://github.com/WebAssembly/spec/pull/1287 + CoreFeatureBulkMemoryOperations CoreFeatures = 1 << iota + + // CoreFeatureMultiValue enables multiple values ("multi-value"). This is + // included in CoreFeaturesV2, but not CoreFeaturesV1. + // + // Here are the notable effects: + // - Function (`func`) types allow more than one result. + // - Block types (`block`, `loop` and `if`) can be arbitrary function + // types. + // + // See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/multi-value/Overview.md + CoreFeatureMultiValue + + // CoreFeatureMutableGlobal allows globals to be mutable. This is included + // in both CoreFeaturesV1 and CoreFeaturesV2. + // + // When false, an api.Global can never be cast to an api.MutableGlobal, and + // any wasm that includes global vars will fail to parse. + CoreFeatureMutableGlobal + + // CoreFeatureNonTrappingFloatToIntConversion enables non-trapping + // float-to-int conversions ("nontrapping-float-to-int-conversion"). This + // is included in CoreFeaturesV2, but not CoreFeaturesV1. + // + // The only effect of enabling is allowing the following instructions, + // which return 0 on NaN instead of panicking. + // - `i32.trunc_sat_f32_s` + // - `i32.trunc_sat_f32_u` + // - `i32.trunc_sat_f64_s` + // - `i32.trunc_sat_f64_u` + // - `i64.trunc_sat_f32_s` + // - `i64.trunc_sat_f32_u` + // - `i64.trunc_sat_f64_s` + // - `i64.trunc_sat_f64_u` + // + // See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/nontrapping-float-to-int-conversion/Overview.md + CoreFeatureNonTrappingFloatToIntConversion + + // CoreFeatureReferenceTypes enables various instructions and features + // related to table and new reference types. This is included in + // CoreFeaturesV2, but not CoreFeaturesV1. + // + // - Introduction of new value types: `funcref` and `externref`. + // - Support for the following new instructions: + // - `ref.null` + // - `ref.func` + // - `ref.is_null` + // - `table.fill` + // - `table.get` + // - `table.grow` + // - `table.set` + // - `table.size` + // - Support for multiple tables per module: + // - `call_indirect`, `table.init`, `table.copy` and `elem.drop` + // - Support for instructions can take non-zero table index. + // - Element segments can take non-zero table index. + // + // Note: "reference-types" is mixed with the "bulk-memory-operations" + // proposal due to the WebAssembly Working Group merging them + // "mutually dependent". Therefore, enabling this feature requires enabling + // CoreFeatureBulkMemoryOperations, and vice-versa. + // + // See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/bulk-memory-operations/Overview.md + // https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/reference-types/Overview.md and + // https://github.com/WebAssembly/spec/pull/1287 + CoreFeatureReferenceTypes + + // CoreFeatureSignExtensionOps enables sign extension instructions + // ("sign-extension-ops"). This is included in CoreFeaturesV2, but not + // CoreFeaturesV1. + // + // Adds instructions: + // - `i32.extend8_s` + // - `i32.extend16_s` + // - `i64.extend8_s` + // - `i64.extend16_s` + // - `i64.extend32_s` + // + // See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/sign-extension-ops/Overview.md + CoreFeatureSignExtensionOps + + // CoreFeatureSIMD enables the vector value type and vector instructions + // (aka SIMD). This is included in CoreFeaturesV2, but not CoreFeaturesV1. + // + // Note: The instruction list is too long to enumerate in godoc. + // See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md + CoreFeatureSIMD + + // Update experimental/features.go when adding elements here. +) + +// SetEnabled enables or disables the feature or group of features. +func (f CoreFeatures) SetEnabled(feature CoreFeatures, val bool) CoreFeatures { + if val { + return f | feature + } + return f &^ feature +} + +// IsEnabled returns true if the feature (or group of features) is enabled. +func (f CoreFeatures) IsEnabled(feature CoreFeatures) bool { + return f&feature != 0 +} + +// RequireEnabled returns an error if the feature (or group of features) is not +// enabled. +func (f CoreFeatures) RequireEnabled(feature CoreFeatures) error { + if f&feature == 0 { + return fmt.Errorf("feature %q is disabled", feature) + } + return nil +} + +// String implements fmt.Stringer by returning each enabled feature. +func (f CoreFeatures) String() string { + var builder strings.Builder + for i := 0; i <= 63; i++ { // cycle through all bits to reduce code and maintenance + target := CoreFeatures(1 << i) + if f.IsEnabled(target) { + if name := featureName(target); name != "" { + if builder.Len() > 0 { + builder.WriteByte('|') + } + builder.WriteString(name) + } + } + } + return builder.String() +} + +func featureName(f CoreFeatures) string { + switch f { + case CoreFeatureMutableGlobal: + // match https://github.com/WebAssembly/mutable-global + return "mutable-global" + case CoreFeatureSignExtensionOps: + // match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/sign-extension-ops/Overview.md + return "sign-extension-ops" + case CoreFeatureMultiValue: + // match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/multi-value/Overview.md + return "multi-value" + case CoreFeatureNonTrappingFloatToIntConversion: + // match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/nontrapping-float-to-int-conversion/Overview.md + return "nontrapping-float-to-int-conversion" + case CoreFeatureBulkMemoryOperations: + // match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/bulk-memory-operations/Overview.md + return "bulk-memory-operations" + case CoreFeatureReferenceTypes: + // match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/reference-types/Overview.md + return "reference-types" + case CoreFeatureSIMD: + // match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md + return "simd" + } + return "" +} diff --git a/vendor/github.com/tetratelabs/wazero/api/wasm.go b/vendor/github.com/tetratelabs/wazero/api/wasm.go new file mode 100644 index 00000000000..d99c1a7569b --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/api/wasm.go @@ -0,0 +1,766 @@ +// Package api includes constants and interfaces used by both end-users and internal implementations. +package api + +import ( + "context" + "fmt" + "math" + + "github.com/tetratelabs/wazero/internal/internalapi" +) + +// ExternType classifies imports and exports with their respective types. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#external-types%E2%91%A0 +type ExternType = byte + +const ( + ExternTypeFunc ExternType = 0x00 + ExternTypeTable ExternType = 0x01 + ExternTypeMemory ExternType = 0x02 + ExternTypeGlobal ExternType = 0x03 +) + +// The below are exported to consolidate parsing behavior for external types. +const ( + // ExternTypeFuncName is the name of the WebAssembly 1.0 (20191205) Text Format field for ExternTypeFunc. + ExternTypeFuncName = "func" + // ExternTypeTableName is the name of the WebAssembly 1.0 (20191205) Text Format field for ExternTypeTable. + ExternTypeTableName = "table" + // ExternTypeMemoryName is the name of the WebAssembly 1.0 (20191205) Text Format field for ExternTypeMemory. + ExternTypeMemoryName = "memory" + // ExternTypeGlobalName is the name of the WebAssembly 1.0 (20191205) Text Format field for ExternTypeGlobal. + ExternTypeGlobalName = "global" +) + +// ExternTypeName returns the name of the WebAssembly 1.0 (20191205) Text Format field of the given type. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A4 +func ExternTypeName(et ExternType) string { + switch et { + case ExternTypeFunc: + return ExternTypeFuncName + case ExternTypeTable: + return ExternTypeTableName + case ExternTypeMemory: + return ExternTypeMemoryName + case ExternTypeGlobal: + return ExternTypeGlobalName + } + return fmt.Sprintf("%#x", et) +} + +// ValueType describes a parameter or result type mapped to a WebAssembly +// function signature. +// +// The following describes how to convert between Wasm and Golang types: +// +// - ValueTypeI32 - EncodeU32 DecodeU32 for uint32 / EncodeI32 DecodeI32 for int32 +// - ValueTypeI64 - uint64(int64) +// - ValueTypeF32 - EncodeF32 DecodeF32 from float32 +// - ValueTypeF64 - EncodeF64 DecodeF64 from float64 +// - ValueTypeExternref - unintptr(unsafe.Pointer(p)) where p is any pointer +// type in Go (e.g. *string) +// +// e.g. Given a Text Format type use (param i64) (result i64), no conversion is +// necessary. +// +// results, _ := fn(ctx, input) +// result := result[0] +// +// e.g. Given a Text Format type use (param f64) (result f64), conversion is +// necessary. +// +// results, _ := fn(ctx, api.EncodeF64(input)) +// result := api.DecodeF64(result[0]) +// +// Note: This is a type alias as it is easier to encode and decode in the +// binary format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-valtype +type ValueType = byte + +const ( + // ValueTypeI32 is a 32-bit integer. + ValueTypeI32 ValueType = 0x7f + // ValueTypeI64 is a 64-bit integer. + ValueTypeI64 ValueType = 0x7e + // ValueTypeF32 is a 32-bit floating point number. + ValueTypeF32 ValueType = 0x7d + // ValueTypeF64 is a 64-bit floating point number. + ValueTypeF64 ValueType = 0x7c + + // ValueTypeExternref is a externref type. + // + // Note: in wazero, externref type value are opaque raw 64-bit pointers, + // and the ValueTypeExternref type in the signature will be translated as + // uintptr in wazero's API level. + // + // For example, given the import function: + // (func (import "env" "f") (param externref) (result externref)) + // + // This can be defined in Go as: + // r.NewHostModuleBuilder("env"). + // NewFunctionBuilder(). + // WithFunc(func(context.Context, _ uintptr) (_ uintptr) { return }). + // Export("f") + // + // Note: The usage of this type is toggled with api.CoreFeatureBulkMemoryOperations. + ValueTypeExternref ValueType = 0x6f +) + +// ValueTypeName returns the type name of the given ValueType as a string. +// These type names match the names used in the WebAssembly text format. +// +// Note: This returns "unknown", if an undefined ValueType value is passed. +func ValueTypeName(t ValueType) string { + switch t { + case ValueTypeI32: + return "i32" + case ValueTypeI64: + return "i64" + case ValueTypeF32: + return "f32" + case ValueTypeF64: + return "f64" + case ValueTypeExternref: + return "externref" + } + return "unknown" +} + +// Module is a sandboxed, ready to execute Wasm module. This can be used to get exported functions, etc. +// +// In WebAssembly terminology, this corresponds to a "Module Instance", but wazero calls pre-instantiation module as +// "Compiled Module" as in wazero.CompiledModule, therefore we call this post-instantiation module simply "Module". +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#module-instances%E2%91%A0 +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +// - Closing the wazero.Runtime closes any Module it instantiated. +type Module interface { + fmt.Stringer + + // Name is the name this module was instantiated with. Exported functions can be imported with this name. + Name() string + + // Memory returns a memory defined in this module or nil if there are none wasn't. + Memory() Memory + + // ExportedFunction returns a function exported from this module or nil if it wasn't. + // + // # Notes + // - The default wazero.ModuleConfig attempts to invoke `_start`, which + // in rare cases can close the module. When in doubt, check IsClosed prior + // to invoking a function export after instantiation. + // - The semantics of host functions assumes the existence of an "importing module" because, for example, the host function needs access to + // the memory of the importing module. Therefore, direct use of ExportedFunction is forbidden for host modules. + // Practically speaking, it is usually meaningless to directly call a host function from Go code as it is already somewhere in Go code. + ExportedFunction(name string) Function + + // ExportedFunctionDefinitions returns all the exported function + // definitions in this module, keyed on export name. + ExportedFunctionDefinitions() map[string]FunctionDefinition + + // TODO: Table + + // ExportedMemory returns a memory exported from this module or nil if it wasn't. + // + // WASI modules require exporting a Memory named "memory". This means that a module successfully initialized + // as a WASI Command or Reactor will never return nil for this name. + // + // See https://github.com/WebAssembly/WASI/blob/snapshot-01/design/application-abi.md#current-unstable-abi + ExportedMemory(name string) Memory + + // ExportedMemoryDefinitions returns all the exported memory definitions + // in this module, keyed on export name. + // + // Note: As of WebAssembly Core Specification 2.0, there can be at most one + // memory. + ExportedMemoryDefinitions() map[string]MemoryDefinition + + // ExportedGlobal a global exported from this module or nil if it wasn't. + ExportedGlobal(name string) Global + + // CloseWithExitCode releases resources allocated for this Module. Use a non-zero exitCode parameter to indicate a + // failure to ExportedFunction callers. + // + // The error returned here, if present, is about resource de-allocation (such as I/O errors). Only the last error is + // returned, so a non-nil return means at least one error happened. Regardless of error, this Module will + // be removed, making its name available again. + // + // Calling this inside a host function is safe, and may cause ExportedFunction callers to receive a sys.ExitError + // with the exitCode. + CloseWithExitCode(ctx context.Context, exitCode uint32) error + + // Closer closes this module by delegating to CloseWithExitCode with an exit code of zero. + Closer + + // IsClosed returns true if the module is closed, so no longer usable. + // + // This can happen for the following reasons: + // - Closer was called directly. + // - A guest function called Closer indirectly, such as `_start` calling + // `proc_exit`, which internally closed the module. + // - wazero.RuntimeConfig `WithCloseOnContextDone` was enabled and a + // context completion closed the module. + // + // Where any of the above are possible, check this value before calling an + // ExportedFunction, even if you didn't formerly receive a sys.ExitError. + // sys.ExitError is only returned on non-zero code, something that closes + // the module successfully will not result it one. + IsClosed() bool + + internalapi.WazeroOnly +} + +// Closer closes a resource. +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +type Closer interface { + // Close closes the resource. + // + // Note: The context parameter is used for value lookup, such as for + // logging. A canceled or otherwise done context will not prevent Close + // from succeeding. + Close(context.Context) error +} + +// ExportDefinition is a WebAssembly type exported in a module +// (wazero.CompiledModule). +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A0 +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +type ExportDefinition interface { + // ModuleName is the possibly empty name of the module defining this + // export. + // + // Note: This may be different from Module.Name, because a compiled module + // can be instantiated multiple times as different names. + ModuleName() string + + // Index is the position in the module's index, imports first. + Index() uint32 + + // Import returns true with the module and name when this was imported. + // Otherwise, it returns false. + // + // Note: Empty string is valid for both names in the WebAssembly Core + // Specification, so "" "" is possible. + Import() (moduleName, name string, isImport bool) + + // ExportNames include all exported names. + // + // Note: The empty name is allowed in the WebAssembly Core Specification, + // so "" is possible. + ExportNames() []string + + internalapi.WazeroOnly +} + +// MemoryDefinition is a WebAssembly memory exported in a module +// (wazero.CompiledModule). Units are in pages (64KB). +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A0 +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +type MemoryDefinition interface { + ExportDefinition + + // Min returns the possibly zero initial count of 64KB pages. + Min() uint32 + + // Max returns the possibly zero max count of 64KB pages, or false if + // unbounded. + Max() (uint32, bool) + + internalapi.WazeroOnly +} + +// FunctionDefinition is a WebAssembly function exported in a module +// (wazero.CompiledModule). +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A0 +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +type FunctionDefinition interface { + ExportDefinition + + // Name is the module-defined name of the function, which is not necessarily + // the same as its export name. + Name() string + + // DebugName identifies this function based on its Index or Name in the + // module. This is used for errors and stack traces. e.g. "env.abort". + // + // When the function name is empty, a substitute name is generated by + // prefixing '$' to its position in the index. Ex ".$0" is the + // first function (possibly imported) in an unnamed module. + // + // The format is dot-delimited module and function name, but there are no + // restrictions on the module and function name. This means either can be + // empty or include dots. e.g. "x.x.x" could mean module "x" and name "x.x", + // or it could mean module "x.x" and name "x". + // + // Note: This name is stable regardless of import or export. For example, + // if Import returns true, the value is still based on the Name or Index + // and not the imported function name. + DebugName() string + + // GoFunction is non-nil when implemented by the embedder instead of a wasm + // binary, e.g. via wazero.HostModuleBuilder + // + // The expected results are nil, GoFunction or GoModuleFunction. + GoFunction() interface{} + + // ParamTypes are the possibly empty sequence of value types accepted by a + // function with this signature. + // + // See ValueType documentation for encoding rules. + ParamTypes() []ValueType + + // ParamNames are index-correlated with ParamTypes or nil if not available + // for one or more parameters. + ParamNames() []string + + // ResultTypes are the results of the function. + // + // When WebAssembly 1.0 (20191205), there can be at most one result. + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#result-types%E2%91%A0 + // + // See ValueType documentation for encoding rules. + ResultTypes() []ValueType + + // ResultNames are index-correlated with ResultTypes or nil if not + // available for one or more results. + ResultNames() []string + + internalapi.WazeroOnly +} + +// Function is a WebAssembly function exported from an instantiated module +// (wazero.Runtime InstantiateModule). +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-func +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +type Function interface { + // Definition is metadata about this function from its defining module. + Definition() FunctionDefinition + + // Call invokes the function with the given parameters and returns any + // results or an error for any failure looking up or invoking the function. + // + // Encoding is described in Definition, and supplying an incorrect count of + // parameters vs FunctionDefinition.ParamTypes is an error. + // + // If the exporting Module was closed during this call, the error returned + // may be a sys.ExitError. See Module.CloseWithExitCode for details. + // + // Call is not goroutine-safe, therefore it is recommended to create + // another Function if you want to invoke the same function concurrently. + // On the other hand, sequential invocations of Call is allowed. + // However, this should not be called multiple times until the previous Call returns. + // + // To safely encode/decode params/results expressed as uint64, users are encouraged to + // use api.EncodeXXX or DecodeXXX functions. See the docs on api.ValueType. + // + // When RuntimeConfig.WithCloseOnContextDone is toggled, the invocation of this Call method is ensured to be closed + // whenever one of the three conditions is met. In the event of close, sys.ExitError will be returned and + // the api.Module from which this api.Function is derived will be made closed. See the documentation of + // WithCloseOnContextDone on wazero.RuntimeConfig for detail. See examples in context_done_example_test.go for + // the end-to-end demonstrations of how these terminations can be performed. + Call(ctx context.Context, params ...uint64) ([]uint64, error) + + // CallWithStack is an optimized variation of Call that saves memory + // allocations when the stack slice is reused across calls. + // + // Stack length must be at least the max of parameter or result length. + // The caller adds parameters in order to the stack, and reads any results + // in order from the stack, except in the error case. + // + // For example, the following reuses the same stack slice to call searchFn + // repeatedly saving one allocation per iteration: + // + // stack := make([]uint64, 4) + // for i, search := range searchParams { + // // copy the next params to the stack + // copy(stack, search) + // if err := searchFn.CallWithStack(ctx, stack); err != nil { + // return err + // } else if stack[0] == 1 { // found + // return i // searchParams[i] matched! + // } + // } + // + // # Notes + // + // - This is similar to GoModuleFunction, except for using calling functions + // instead of implementing them. Moreover, this is used regardless of + // whether the callee is a host or wasm defined function. + CallWithStack(ctx context.Context, stack []uint64) error + + internalapi.WazeroOnly +} + +// GoModuleFunction is a Function implemented in Go instead of a wasm binary. +// The Module parameter is the calling module, used to access memory or +// exported functions. See GoModuleFunc for an example. +// +// The stack is includes any parameters encoded according to their ValueType. +// Its length is the max of parameter or result length. When there are results, +// write them in order beginning at index zero. Do not use the stack after the +// function returns. +// +// Here's a typical way to read three parameters and write back one. +// +// // read parameters off the stack in index order +// argv, argvBuf := api.DecodeU32(stack[0]), api.DecodeU32(stack[1]) +// +// // write results back to the stack in index order +// stack[0] = api.EncodeU32(ErrnoSuccess) +// +// This function can be non-deterministic or cause side effects. It also +// has special properties not defined in the WebAssembly Core specification. +// Notably, this uses the caller's memory (via Module.Memory). See +// https://www.w3.org/TR/wasm-core-1/#host-functions%E2%91%A0 +// +// Most end users will not define functions directly with this, as they will +// use reflection or code generators instead. These approaches are more +// idiomatic as they can map go types to ValueType. This type is exposed for +// those willing to trade usability and safety for performance. +// +// To safely decode/encode values from/to the uint64 stack, users are encouraged to use +// api.EncodeXXX or api.DecodeXXX functions. See the docs on api.ValueType. +type GoModuleFunction interface { + Call(ctx context.Context, mod Module, stack []uint64) +} + +// GoModuleFunc is a convenience for defining an inlined function. +// +// For example, the following returns an uint32 value read from parameter zero: +// +// api.GoModuleFunc(func(ctx context.Context, mod api.Module, stack []uint64) { +// offset := api.DecodeU32(stack[0]) // read the parameter from the stack +// +// ret, ok := mod.Memory().ReadUint32Le(offset) +// if !ok { +// panic("out of memory") +// } +// +// stack[0] = api.EncodeU32(ret) // add the result back to the stack. +// }) +type GoModuleFunc func(ctx context.Context, mod Module, stack []uint64) + +// Call implements GoModuleFunction.Call. +func (f GoModuleFunc) Call(ctx context.Context, mod Module, stack []uint64) { + f(ctx, mod, stack) +} + +// GoFunction is an optimized form of GoModuleFunction which doesn't require +// the Module parameter. See GoFunc for an example. +// +// For example, this function does not need to use the importing module's +// memory or exported functions. +type GoFunction interface { + Call(ctx context.Context, stack []uint64) +} + +// GoFunc is a convenience for defining an inlined function. +// +// For example, the following returns the sum of two uint32 parameters: +// +// api.GoFunc(func(ctx context.Context, stack []uint64) { +// x, y := api.DecodeU32(stack[0]), api.DecodeU32(stack[1]) +// stack[0] = api.EncodeU32(x + y) +// }) +type GoFunc func(ctx context.Context, stack []uint64) + +// Call implements GoFunction.Call. +func (f GoFunc) Call(ctx context.Context, stack []uint64) { + f(ctx, stack) +} + +// Global is a WebAssembly 1.0 (20191205) global exported from an instantiated module (wazero.Runtime InstantiateModule). +// +// For example, if the value is not mutable, you can read it once: +// +// offset := module.ExportedGlobal("memory.offset").Get() +// +// Globals are allowed by specification to be mutable. However, this can be disabled by configuration. When in doubt, +// safe cast to find out if the value can change. Here's an example: +// +// offset := module.ExportedGlobal("memory.offset") +// if _, ok := offset.(api.MutableGlobal); ok { +// // value can change +// } else { +// // value is constant +// } +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#globals%E2%91%A0 +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +type Global interface { + fmt.Stringer + + // Type describes the numeric type of the global. + Type() ValueType + + // Get returns the last known value of this global. + // + // See Type for how to decode this value to a Go type. + Get() uint64 +} + +// MutableGlobal is a Global whose value can be updated at runtime (variable). +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +type MutableGlobal interface { + Global + + // Set updates the value of this global. + // + // See Global.Type for how to encode this value from a Go type. + Set(v uint64) + + internalapi.WazeroOnly +} + +// Memory allows restricted access to a module's memory. Notably, this does not allow growing. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#storage%E2%91%A0 +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +// - This includes all value types available in WebAssembly 1.0 (20191205) and all are encoded little-endian. +type Memory interface { + // Definition is metadata about this memory from its defining module. + Definition() MemoryDefinition + + // Size returns the memory size in bytes available. + // e.g. If the underlying memory has 1 page: 65536 + // + // # Notes + // + // - This overflows (returns zero) if the memory has the maximum 65536 pages. + // As a workaround until wazero v2 to fix the return type, use Grow(0) to obtain the current pages and + // multiply by 65536. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#-hrefsyntax-instr-memorymathsfmemorysize%E2%91%A0 + Size() uint32 + + // Grow increases memory by the delta in pages (65536 bytes per page). + // The return val is the previous memory size in pages, or false if the + // delta was ignored as it exceeds MemoryDefinition.Max. + // + // # Notes + // + // - This is the same as the "memory.grow" instruction defined in the + // WebAssembly Core Specification, except returns false instead of -1. + // - When this returns true, any shared views via Read must be refreshed. + // + // See MemorySizer Read and https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem + Grow(deltaPages uint32) (previousPages uint32, ok bool) + + // ReadByte reads a single byte from the underlying buffer at the offset or returns false if out of range. + ReadByte(offset uint32) (byte, bool) + + // ReadUint16Le reads a uint16 in little-endian encoding from the underlying buffer at the offset in or returns + // false if out of range. + ReadUint16Le(offset uint32) (uint16, bool) + + // ReadUint32Le reads a uint32 in little-endian encoding from the underlying buffer at the offset in or returns + // false if out of range. + ReadUint32Le(offset uint32) (uint32, bool) + + // ReadFloat32Le reads a float32 from 32 IEEE 754 little-endian encoded bits in the underlying buffer at the offset + // or returns false if out of range. + // See math.Float32bits + ReadFloat32Le(offset uint32) (float32, bool) + + // ReadUint64Le reads a uint64 in little-endian encoding from the underlying buffer at the offset or returns false + // if out of range. + ReadUint64Le(offset uint32) (uint64, bool) + + // ReadFloat64Le reads a float64 from 64 IEEE 754 little-endian encoded bits in the underlying buffer at the offset + // or returns false if out of range. + // + // See math.Float64bits + ReadFloat64Le(offset uint32) (float64, bool) + + // Read reads byteCount bytes from the underlying buffer at the offset or + // returns false if out of range. + // + // For example, to search for a NUL-terminated string: + // buf, _ = memory.Read(offset, byteCount) + // n := bytes.IndexByte(buf, 0) + // if n < 0 { + // // Not found! + // } + // + // Write-through + // + // This returns a view of the underlying memory, not a copy. This means any + // writes to the slice returned are visible to Wasm, and any updates from + // Wasm are visible reading the returned slice. + // + // For example: + // buf, _ = memory.Read(offset, byteCount) + // buf[1] = 'a' // writes through to memory, meaning Wasm code see 'a'. + // + // If you don't intend-write through, make a copy of the returned slice. + // + // When to refresh Read + // + // The returned slice disconnects on any capacity change. For example, + // `buf = append(buf, 'a')` might result in a slice that is no longer + // shared. The same exists Wasm side. For example, if Wasm changes its + // memory capacity, ex via "memory.grow"), the host slice is no longer + // shared. Those who need a stable view must set Wasm memory min=max, or + // use wazero.RuntimeConfig WithMemoryCapacityPages to ensure max is always + // allocated. + Read(offset, byteCount uint32) ([]byte, bool) + + // WriteByte writes a single byte to the underlying buffer at the offset in or returns false if out of range. + WriteByte(offset uint32, v byte) bool + + // WriteUint16Le writes the value in little-endian encoding to the underlying buffer at the offset in or returns + // false if out of range. + WriteUint16Le(offset uint32, v uint16) bool + + // WriteUint32Le writes the value in little-endian encoding to the underlying buffer at the offset in or returns + // false if out of range. + WriteUint32Le(offset, v uint32) bool + + // WriteFloat32Le writes the value in 32 IEEE 754 little-endian encoded bits to the underlying buffer at the offset + // or returns false if out of range. + // + // See math.Float32bits + WriteFloat32Le(offset uint32, v float32) bool + + // WriteUint64Le writes the value in little-endian encoding to the underlying buffer at the offset in or returns + // false if out of range. + WriteUint64Le(offset uint32, v uint64) bool + + // WriteFloat64Le writes the value in 64 IEEE 754 little-endian encoded bits to the underlying buffer at the offset + // or returns false if out of range. + // + // See math.Float64bits + WriteFloat64Le(offset uint32, v float64) bool + + // Write writes the slice to the underlying buffer at the offset or returns false if out of range. + Write(offset uint32, v []byte) bool + + // WriteString writes the string to the underlying buffer at the offset or returns false if out of range. + WriteString(offset uint32, v string) bool + + internalapi.WazeroOnly +} + +// CustomSection contains the name and raw data of a custom section. +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +type CustomSection interface { + // Name is the name of the custom section + Name() string + // Data is the raw data of the custom section + Data() []byte + + internalapi.WazeroOnly +} + +// EncodeExternref encodes the input as a ValueTypeExternref. +// +// See DecodeExternref +func EncodeExternref(input uintptr) uint64 { + return uint64(input) +} + +// DecodeExternref decodes the input as a ValueTypeExternref. +// +// See EncodeExternref +func DecodeExternref(input uint64) uintptr { + return uintptr(input) +} + +// EncodeI32 encodes the input as a ValueTypeI32. +func EncodeI32(input int32) uint64 { + return uint64(uint32(input)) +} + +// DecodeI32 decodes the input as a ValueTypeI32. +func DecodeI32(input uint64) int32 { + return int32(input) +} + +// EncodeU32 encodes the input as a ValueTypeI32. +func EncodeU32(input uint32) uint64 { + return uint64(input) +} + +// DecodeU32 decodes the input as a ValueTypeI32. +func DecodeU32(input uint64) uint32 { + return uint32(input) +} + +// EncodeI64 encodes the input as a ValueTypeI64. +func EncodeI64(input int64) uint64 { + return uint64(input) +} + +// EncodeF32 encodes the input as a ValueTypeF32. +// +// See DecodeF32 +func EncodeF32(input float32) uint64 { + return uint64(math.Float32bits(input)) +} + +// DecodeF32 decodes the input as a ValueTypeF32. +// +// See EncodeF32 +func DecodeF32(input uint64) float32 { + return math.Float32frombits(uint32(input)) +} + +// EncodeF64 encodes the input as a ValueTypeF64. +// +// See EncodeF32 +func EncodeF64(input float64) uint64 { + return math.Float64bits(input) +} + +// DecodeF64 decodes the input as a ValueTypeF64. +// +// See EncodeF64 +func DecodeF64(input uint64) float64 { + return math.Float64frombits(input) +} diff --git a/vendor/github.com/tetratelabs/wazero/builder.go b/vendor/github.com/tetratelabs/wazero/builder.go new file mode 100644 index 00000000000..b60a9e09783 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/builder.go @@ -0,0 +1,367 @@ +package wazero + +import ( + "context" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/wasm" +) + +// HostFunctionBuilder defines a host function (in Go), so that a +// WebAssembly binary (e.g. %.wasm file) can import and use it. +// +// Here's an example of an addition function: +// +// hostModuleBuilder.NewFunctionBuilder(). +// WithFunc(func(cxt context.Context, x, y uint32) uint32 { +// return x + y +// }). +// Export("add") +// +// # Memory +// +// All host functions act on the importing api.Module, including any memory +// exported in its binary (%.wasm file). If you are reading or writing memory, +// it is sand-boxed Wasm memory defined by the guest. +// +// Below, `m` is the importing module, defined in Wasm. `fn` is a host function +// added via Export. This means that `x` was read from memory defined in Wasm, +// not arbitrary memory in the process. +// +// fn := func(ctx context.Context, m api.Module, offset uint32) uint32 { +// x, _ := m.Memory().ReadUint32Le(ctx, offset) +// return x +// } +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +type HostFunctionBuilder interface { + // WithGoFunction is an advanced feature for those who need higher + // performance than WithFunc at the cost of more complexity. + // + // Here's an example addition function: + // + // builder.WithGoFunction(api.GoFunc(func(ctx context.Context, stack []uint64) { + // x, y := api.DecodeI32(stack[0]), api.DecodeI32(stack[1]) + // sum := x + y + // stack[0] = api.EncodeI32(sum) + // }), []api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}) + // + // As you can see above, defining in this way implies knowledge of which + // WebAssembly api.ValueType is appropriate for each parameter and result. + // + // See WithGoModuleFunction if you also need to access the calling module. + WithGoFunction(fn api.GoFunction, params, results []api.ValueType) HostFunctionBuilder + + // WithGoModuleFunction is an advanced feature for those who need higher + // performance than WithFunc at the cost of more complexity. + // + // Here's an example addition function that loads operands from memory: + // + // builder.WithGoModuleFunction(api.GoModuleFunc(func(ctx context.Context, m api.Module, stack []uint64) { + // mem := m.Memory() + // offset := api.DecodeU32(stack[0]) + // + // x, _ := mem.ReadUint32Le(ctx, offset) + // y, _ := mem.ReadUint32Le(ctx, offset + 4) // 32 bits == 4 bytes! + // sum := x + y + // + // stack[0] = api.EncodeU32(sum) + // }), []api.ValueType{api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}) + // + // As you can see above, defining in this way implies knowledge of which + // WebAssembly api.ValueType is appropriate for each parameter and result. + // + // See WithGoFunction if you don't need access to the calling module. + WithGoModuleFunction(fn api.GoModuleFunction, params, results []api.ValueType) HostFunctionBuilder + + // WithFunc uses reflect.Value to map a go `func` to a WebAssembly + // compatible Signature. An input that isn't a `func` will fail to + // instantiate. + // + // Here's an example of an addition function: + // + // builder.WithFunc(func(cxt context.Context, x, y uint32) uint32 { + // return x + y + // }) + // + // # Defining a function + // + // Except for the context.Context and optional api.Module, all parameters + // or result types must map to WebAssembly numeric value types. This means + // uint32, int32, uint64, int64, float32 or float64. + // + // api.Module may be specified as the second parameter, usually to access + // memory. This is important because there are only numeric types in Wasm. + // The only way to share other data is via writing memory and sharing + // offsets. + // + // builder.WithFunc(func(ctx context.Context, m api.Module, offset uint32) uint32 { + // mem := m.Memory() + // x, _ := mem.ReadUint32Le(ctx, offset) + // y, _ := mem.ReadUint32Le(ctx, offset + 4) // 32 bits == 4 bytes! + // return x + y + // }) + // + // This example propagates context properly when calling other functions + // exported in the api.Module: + // + // builder.WithFunc(func(ctx context.Context, m api.Module, offset, byteCount uint32) uint32 { + // fn = m.ExportedFunction("__read") + // results, err := fn(ctx, offset, byteCount) + // --snip-- + WithFunc(interface{}) HostFunctionBuilder + + // WithName defines the optional module-local name of this function, e.g. + // "random_get" + // + // Note: This is not required to match the Export name. + WithName(name string) HostFunctionBuilder + + // WithParameterNames defines optional parameter names of the function + // signature, e.x. "buf", "buf_len" + // + // Note: When defined, names must be provided for all parameters. + WithParameterNames(names ...string) HostFunctionBuilder + + // WithResultNames defines optional result names of the function + // signature, e.x. "errno" + // + // Note: When defined, names must be provided for all results. + WithResultNames(names ...string) HostFunctionBuilder + + // Export exports this to the HostModuleBuilder as the given name, e.g. + // "random_get" + Export(name string) HostModuleBuilder +} + +// HostModuleBuilder is a way to define host functions (in Go), so that a +// WebAssembly binary (e.g. %.wasm file) can import and use them. +// +// Specifically, this implements the host side of an Application Binary +// Interface (ABI) like WASI or AssemblyScript. +// +// For example, this defines and instantiates a module named "env" with one +// function: +// +// ctx := context.Background() +// r := wazero.NewRuntime(ctx) +// defer r.Close(ctx) // This closes everything this Runtime created. +// +// hello := func() { +// println("hello!") +// } +// env, _ := r.NewHostModuleBuilder("env"). +// NewFunctionBuilder().WithFunc(hello).Export("hello"). +// Instantiate(ctx) +// +// If the same module may be instantiated multiple times, it is more efficient +// to separate steps. Here's an example: +// +// compiled, _ := r.NewHostModuleBuilder("env"). +// NewFunctionBuilder().WithFunc(getRandomString).Export("get_random_string"). +// Compile(ctx) +// +// env1, _ := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithName("env.1")) +// env2, _ := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithName("env.2")) +// +// See HostFunctionBuilder for valid host function signatures and other details. +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +// - HostModuleBuilder is mutable: each method returns the same instance for +// chaining. +// - methods do not return errors, to allow chaining. Any validation errors +// are deferred until Compile. +// - Functions are indexed in order of calls to NewFunctionBuilder as +// insertion ordering is needed by ABI such as Emscripten (invoke_*). +// - The semantics of host functions assumes the existence of an "importing module" because, for example, the host function needs access to +// the memory of the importing module. Therefore, direct use of ExportedFunction is forbidden for host modules. +// Practically speaking, it is usually meaningless to directly call a host function from Go code as it is already somewhere in Go code. +type HostModuleBuilder interface { + // Note: until golang/go#5860, we can't use example tests to embed code in interface godocs. + + // NewFunctionBuilder begins the definition of a host function. + NewFunctionBuilder() HostFunctionBuilder + + // Compile returns a CompiledModule that can be instantiated by Runtime. + Compile(context.Context) (CompiledModule, error) + + // Instantiate is a convenience that calls Compile, then Runtime.InstantiateModule. + // This can fail for reasons documented on Runtime.InstantiateModule. + // + // Here's an example: + // + // ctx := context.Background() + // r := wazero.NewRuntime(ctx) + // defer r.Close(ctx) // This closes everything this Runtime created. + // + // hello := func() { + // println("hello!") + // } + // env, _ := r.NewHostModuleBuilder("env"). + // NewFunctionBuilder().WithFunc(hello).Export("hello"). + // Instantiate(ctx) + // + // # Notes + // + // - Closing the Runtime has the same effect as closing the result. + // - Fields in the builder are copied during instantiation: Later changes do not affect the instantiated result. + // - To avoid using configuration defaults, use Compile instead. + Instantiate(context.Context) (api.Module, error) +} + +// hostModuleBuilder implements HostModuleBuilder +type hostModuleBuilder struct { + r *runtime + moduleName string + exportNames []string + nameToHostFunc map[string]*wasm.HostFunc +} + +// NewHostModuleBuilder implements Runtime.NewHostModuleBuilder +func (r *runtime) NewHostModuleBuilder(moduleName string) HostModuleBuilder { + return &hostModuleBuilder{ + r: r, + moduleName: moduleName, + nameToHostFunc: map[string]*wasm.HostFunc{}, + } +} + +// hostFunctionBuilder implements HostFunctionBuilder +type hostFunctionBuilder struct { + b *hostModuleBuilder + fn interface{} + name string + paramNames []string + resultNames []string +} + +// WithGoFunction implements HostFunctionBuilder.WithGoFunction +func (h *hostFunctionBuilder) WithGoFunction(fn api.GoFunction, params, results []api.ValueType) HostFunctionBuilder { + h.fn = &wasm.HostFunc{ParamTypes: params, ResultTypes: results, Code: wasm.Code{GoFunc: fn}} + return h +} + +// WithGoModuleFunction implements HostFunctionBuilder.WithGoModuleFunction +func (h *hostFunctionBuilder) WithGoModuleFunction(fn api.GoModuleFunction, params, results []api.ValueType) HostFunctionBuilder { + h.fn = &wasm.HostFunc{ParamTypes: params, ResultTypes: results, Code: wasm.Code{GoFunc: fn}} + return h +} + +// WithFunc implements HostFunctionBuilder.WithFunc +func (h *hostFunctionBuilder) WithFunc(fn interface{}) HostFunctionBuilder { + h.fn = fn + return h +} + +// WithName implements HostFunctionBuilder.WithName +func (h *hostFunctionBuilder) WithName(name string) HostFunctionBuilder { + h.name = name + return h +} + +// WithParameterNames implements HostFunctionBuilder.WithParameterNames +func (h *hostFunctionBuilder) WithParameterNames(names ...string) HostFunctionBuilder { + h.paramNames = names + return h +} + +// WithResultNames implements HostFunctionBuilder.WithResultNames +func (h *hostFunctionBuilder) WithResultNames(names ...string) HostFunctionBuilder { + h.resultNames = names + return h +} + +// Export implements HostFunctionBuilder.Export +func (h *hostFunctionBuilder) Export(exportName string) HostModuleBuilder { + var hostFn *wasm.HostFunc + if fn, ok := h.fn.(*wasm.HostFunc); ok { + hostFn = fn + } else { + hostFn = &wasm.HostFunc{Code: wasm.Code{GoFunc: h.fn}} + } + + // Assign any names from the builder + hostFn.ExportName = exportName + if h.name != "" { + hostFn.Name = h.name + } + if len(h.paramNames) != 0 { + hostFn.ParamNames = h.paramNames + } + if len(h.resultNames) != 0 { + hostFn.ResultNames = h.resultNames + } + + h.b.ExportHostFunc(hostFn) + return h.b +} + +// ExportHostFunc implements wasm.HostFuncExporter +func (b *hostModuleBuilder) ExportHostFunc(fn *wasm.HostFunc) { + if _, ok := b.nameToHostFunc[fn.ExportName]; !ok { // add a new name + b.exportNames = append(b.exportNames, fn.ExportName) + } + b.nameToHostFunc[fn.ExportName] = fn +} + +// NewFunctionBuilder implements HostModuleBuilder.NewFunctionBuilder +func (b *hostModuleBuilder) NewFunctionBuilder() HostFunctionBuilder { + return &hostFunctionBuilder{b: b} +} + +// Compile implements HostModuleBuilder.Compile +func (b *hostModuleBuilder) Compile(ctx context.Context) (CompiledModule, error) { + module, err := wasm.NewHostModule(b.moduleName, b.exportNames, b.nameToHostFunc, b.r.enabledFeatures) + if err != nil { + return nil, err + } else if err = module.Validate(b.r.enabledFeatures); err != nil { + return nil, err + } + + c := &compiledModule{module: module, compiledEngine: b.r.store.Engine} + listeners, err := buildFunctionListeners(ctx, module) + if err != nil { + return nil, err + } + + if err = b.r.store.Engine.CompileModule(ctx, module, listeners, false); err != nil { + return nil, err + } + + // typeIDs are static and compile-time known. + typeIDs, err := b.r.store.GetFunctionTypeIDs(module.TypeSection) + if err != nil { + return nil, err + } + c.typeIDs = typeIDs + + return c, nil +} + +// hostModuleInstance is a wrapper around api.Module that prevents calling ExportedFunction. +type hostModuleInstance struct{ api.Module } + +// ExportedFunction implements api.Module ExportedFunction. +func (h hostModuleInstance) ExportedFunction(name string) api.Function { + panic("calling ExportedFunction is forbidden on host modules. See the note on ExportedFunction interface") +} + +// Instantiate implements HostModuleBuilder.Instantiate +func (b *hostModuleBuilder) Instantiate(ctx context.Context) (api.Module, error) { + if compiled, err := b.Compile(ctx); err != nil { + return nil, err + } else { + compiled.(*compiledModule).closeWithModule = true + m, err := b.r.InstantiateModule(ctx, compiled, NewModuleConfig()) + if err != nil { + return nil, err + } + return hostModuleInstance{m}, nil + } +} diff --git a/vendor/github.com/tetratelabs/wazero/cache.go b/vendor/github.com/tetratelabs/wazero/cache.go new file mode 100644 index 00000000000..83cdb94ef31 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/cache.go @@ -0,0 +1,123 @@ +package wazero + +import ( + "context" + "errors" + "fmt" + "os" + "path" + "path/filepath" + goruntime "runtime" + "sync" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/filecache" + "github.com/tetratelabs/wazero/internal/version" + "github.com/tetratelabs/wazero/internal/wasm" +) + +// CompilationCache reduces time spent compiling (Runtime.CompileModule) the same wasm module. +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +// - Instances of this can be reused across multiple runtimes, if configured +// via RuntimeConfig. +// - The cache check happens before the compilation, so if multiple Goroutines are +// trying to compile the same module simultaneously, it is possible that they +// all compile the module. The design here is that the lock isn't held for the action "Compile" +// but only for checking and saving the compiled result. Therefore, we strongly recommend that the embedder +// does the centralized compilation in a single Goroutines (or multiple Goroutines per Wasm binary) to generate cache rather than +// trying to Compile in parallel for a single module. In other words, we always recommend to produce CompiledModule +// share it across multiple Goroutines to avoid trying to compile the same module simultaneously. +type CompilationCache interface{ api.Closer } + +// NewCompilationCache returns a new CompilationCache to be passed to RuntimeConfig. +// This configures only in-memory cache, and doesn't persist to the file system. See wazero.NewCompilationCacheWithDir for detail. +// +// The returned CompilationCache can be used to share the in-memory compilation results across multiple instances of wazero.Runtime. +func NewCompilationCache() CompilationCache { + return &cache{} +} + +// NewCompilationCacheWithDir is like wazero.NewCompilationCache except the result also writes +// state into the directory specified by `dirname` parameter. +// +// If the dirname doesn't exist, this creates it or returns an error. +// +// Those running wazero as a CLI or frequently restarting a process using the same wasm should +// use this feature to reduce time waiting to compile the same module a second time. +// +// The contents written into dirname are wazero-version specific, meaning different versions of +// wazero will duplicate entries for the same input wasm. +// +// Note: The embedder must safeguard this directory from external changes. +func NewCompilationCacheWithDir(dirname string) (CompilationCache, error) { + c := &cache{} + err := c.ensuresFileCache(dirname, version.GetWazeroVersion()) + return c, err +} + +// cache implements Cache interface. +type cache struct { + // eng is the engine for this cache. If the cache is configured, the engine is shared across multiple instances of + // Runtime, and its lifetime is not bound to them. Instead, the engine is alive until Cache.Close is called. + engs [engineKindCount]wasm.Engine + fileCache filecache.Cache + initOnces [engineKindCount]sync.Once +} + +func (c *cache) initEngine(ek engineKind, ne newEngine, ctx context.Context, features api.CoreFeatures) wasm.Engine { + c.initOnces[ek].Do(func() { c.engs[ek] = ne(ctx, features, c.fileCache) }) + return c.engs[ek] +} + +// Close implements the same method on the Cache interface. +func (c *cache) Close(_ context.Context) (err error) { + for _, eng := range c.engs { + if eng != nil { + if err = eng.Close(); err != nil { + return + } + } + } + return +} + +func (c *cache) ensuresFileCache(dir string, wazeroVersion string) error { + // Resolve a potentially relative directory into an absolute one. + var err error + dir, err = filepath.Abs(dir) + if err != nil { + return err + } + + // Ensure the user-supplied directory. + if err = mkdir(dir); err != nil { + return err + } + + // Create a version-specific directory to avoid conflicts. + dirname := path.Join(dir, "wazero-"+wazeroVersion+"-"+goruntime.GOARCH+"-"+goruntime.GOOS) + if err = mkdir(dirname); err != nil { + return err + } + + c.fileCache = filecache.New(dirname) + return nil +} + +func mkdir(dirname string) error { + if st, err := os.Stat(dirname); errors.Is(err, os.ErrNotExist) { + // If the directory not found, create the cache dir. + if err = os.MkdirAll(dirname, 0o700); err != nil { + return fmt.Errorf("create directory %s: %v", dirname, err) + } + } else if err != nil { + return err + } else if !st.IsDir() { + return fmt.Errorf("%s is not dir", dirname) + } + return nil +} diff --git a/vendor/github.com/tetratelabs/wazero/codecov.yml b/vendor/github.com/tetratelabs/wazero/codecov.yml new file mode 100644 index 00000000000..cf9d94df44b --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/codecov.yml @@ -0,0 +1,9 @@ +# Codecov for main is visible here https://app.codecov.io/gh/tetratelabs/wazero + +# We use codecov only as a UI, so we disable PR comments and commit status. +# See https://docs.codecov.com/docs/pull-request-comments +comment: false +coverage: + status: + project: off + patch: off diff --git a/vendor/github.com/tetratelabs/wazero/config.go b/vendor/github.com/tetratelabs/wazero/config.go new file mode 100644 index 00000000000..9fa2bada80d --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/config.go @@ -0,0 +1,899 @@ +package wazero + +import ( + "context" + "errors" + "fmt" + "io" + "io/fs" + "math" + "net" + "time" + + "github.com/tetratelabs/wazero/api" + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/filecache" + "github.com/tetratelabs/wazero/internal/internalapi" + "github.com/tetratelabs/wazero/internal/platform" + internalsock "github.com/tetratelabs/wazero/internal/sock" + internalsys "github.com/tetratelabs/wazero/internal/sys" + "github.com/tetratelabs/wazero/internal/wasm" + "github.com/tetratelabs/wazero/sys" +) + +// RuntimeConfig controls runtime behavior, with the default implementation as +// NewRuntimeConfig +// +// The example below explicitly limits to Wasm Core 1.0 features as opposed to +// relying on defaults: +// +// rConfig = wazero.NewRuntimeConfig().WithCoreFeatures(api.CoreFeaturesV1) +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +// - RuntimeConfig is immutable. Each WithXXX function returns a new instance +// including the corresponding change. +type RuntimeConfig interface { + // WithCoreFeatures sets the WebAssembly Core specification features this + // runtime supports. Defaults to api.CoreFeaturesV2. + // + // Example of disabling a specific feature: + // features := api.CoreFeaturesV2.SetEnabled(api.CoreFeatureMutableGlobal, false) + // rConfig = wazero.NewRuntimeConfig().WithCoreFeatures(features) + // + // # Why default to version 2.0? + // + // Many compilers that target WebAssembly require features after + // api.CoreFeaturesV1 by default. For example, TinyGo v0.24+ requires + // api.CoreFeatureBulkMemoryOperations. To avoid runtime errors, wazero + // defaults to api.CoreFeaturesV2, even though it is not yet a Web + // Standard (REC). + WithCoreFeatures(api.CoreFeatures) RuntimeConfig + + // WithMemoryLimitPages overrides the maximum pages allowed per memory. The + // default is 65536, allowing 4GB total memory per instance if the maximum is + // not encoded in a Wasm binary. Setting a value larger than default will panic. + // + // This example reduces the largest possible memory size from 4GB to 128KB: + // rConfig = wazero.NewRuntimeConfig().WithMemoryLimitPages(2) + // + // Note: Wasm has 32-bit memory and each page is 65536 (2^16) bytes. This + // implies a max of 65536 (2^16) addressable pages. + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem + WithMemoryLimitPages(memoryLimitPages uint32) RuntimeConfig + + // WithMemoryCapacityFromMax eagerly allocates max memory, unless max is + // not defined. The default is false, which means minimum memory is + // allocated and any call to grow memory results in re-allocations. + // + // This example ensures any memory.grow instruction will never re-allocate: + // rConfig = wazero.NewRuntimeConfig().WithMemoryCapacityFromMax(true) + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem + // + // Note: if the memory maximum is not encoded in a Wasm binary, this + // results in allocating 4GB. See the doc on WithMemoryLimitPages for detail. + WithMemoryCapacityFromMax(memoryCapacityFromMax bool) RuntimeConfig + + // WithDebugInfoEnabled toggles DWARF based stack traces in the face of + // runtime errors. Defaults to true. + // + // Those who wish to disable this, can like so: + // + // r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfig().WithDebugInfoEnabled(false) + // + // When disabled, a stack trace message looks like: + // + // wasm stack trace: + // .runtime._panic(i32) + // .myFunc() + // .main.main() + // .runtime.run() + // ._start() + // + // When enabled, the stack trace includes source code information: + // + // wasm stack trace: + // .runtime._panic(i32) + // 0x16e2: /opt/homebrew/Cellar/tinygo/0.26.0/src/runtime/runtime_tinygowasm.go:73:6 + // .myFunc() + // 0x190b: /Users/XXXXX/wazero/internal/testing/dwarftestdata/testdata/main.go:19:7 + // .main.main() + // 0x18ed: /Users/XXXXX/wazero/internal/testing/dwarftestdata/testdata/main.go:4:3 + // .runtime.run() + // 0x18cc: /opt/homebrew/Cellar/tinygo/0.26.0/src/runtime/scheduler_none.go:26:10 + // ._start() + // 0x18b6: /opt/homebrew/Cellar/tinygo/0.26.0/src/runtime/runtime_wasm_wasi.go:22:5 + // + // Note: This only takes into effect when the original Wasm binary has the + // DWARF "custom sections" that are often stripped, depending on + // optimization flags passed to the compiler. + WithDebugInfoEnabled(bool) RuntimeConfig + + // WithCompilationCache configures how runtime caches the compiled modules. In the default configuration, compilation results are + // only in-memory until Runtime.Close is closed, and not shareable by multiple Runtime. + // + // Below defines the shared cache across multiple instances of Runtime: + // + // // Creates the new Cache and the runtime configuration with it. + // cache := wazero.NewCompilationCache() + // defer cache.Close() + // config := wazero.NewRuntimeConfig().WithCompilationCache(c) + // + // // Creates two runtimes while sharing compilation caches. + // foo := wazero.NewRuntimeWithConfig(context.Background(), config) + // bar := wazero.NewRuntimeWithConfig(context.Background(), config) + // + // # Cache Key + // + // Cached files are keyed on the version of wazero. This is obtained from go.mod of your application, + // and we use it to verify the compatibility of caches against the currently-running wazero. + // However, if you use this in tests of a package not named as `main`, then wazero cannot obtain the correct + // version of wazero due to the known issue of debug.BuildInfo function: https://github.com/golang/go/issues/33976. + // As a consequence, your cache won't contain the correct version information and always be treated as `dev` version. + // To avoid this issue, you can pass -ldflags "-X github.com/tetratelabs/wazero/internal/version.version=foo" when running tests. + WithCompilationCache(CompilationCache) RuntimeConfig + + // WithCustomSections toggles parsing of "custom sections". Defaults to false. + // + // When enabled, it is possible to retrieve custom sections from a CompiledModule: + // + // config := wazero.NewRuntimeConfig().WithCustomSections(true) + // r := wazero.NewRuntimeWithConfig(ctx, config) + // c, err := r.CompileModule(ctx, wasm) + // customSections := c.CustomSections() + WithCustomSections(bool) RuntimeConfig + + // WithCloseOnContextDone ensures the executions of functions to be terminated under one of the following circumstances: + // + // - context.Context passed to the Call method of api.Function is canceled during execution. (i.e. ctx by context.WithCancel) + // - context.Context passed to the Call method of api.Function reaches timeout during execution. (i.e. ctx by context.WithTimeout or context.WithDeadline) + // - Close or CloseWithExitCode of api.Module is explicitly called during execution. + // + // This is especially useful when one wants to run untrusted Wasm binaries since otherwise, any invocation of + // api.Function can potentially block the corresponding Goroutine forever. Moreover, it might block the + // entire underlying OS thread which runs the api.Function call. See "Why it's safe to execute runtime-generated + // machine codes against async Goroutine preemption" section in RATIONALE.md for detail. + // + // Upon the termination of the function executions, api.Module is closed. + // + // Note that this comes with a bit of extra cost when enabled. The reason is that internally this forces + // interpreter and compiler runtimes to insert the periodical checks on the conditions above. For that reason, + // this is disabled by default. + // + // See examples in context_done_example_test.go for the end-to-end demonstrations. + // + // When the invocations of api.Function are closed due to this, sys.ExitError is raised to the callers and + // the api.Module from which the functions are derived is made closed. + WithCloseOnContextDone(bool) RuntimeConfig +} + +// NewRuntimeConfig returns a RuntimeConfig using the compiler if it is supported in this environment, +// or the interpreter otherwise. +func NewRuntimeConfig() RuntimeConfig { + ret := engineLessConfig.clone() + ret.engineKind = engineKindAuto + return ret +} + +type newEngine func(context.Context, api.CoreFeatures, filecache.Cache) wasm.Engine + +type runtimeConfig struct { + enabledFeatures api.CoreFeatures + memoryLimitPages uint32 + memoryCapacityFromMax bool + engineKind engineKind + dwarfDisabled bool // negative as defaults to enabled + newEngine newEngine + cache CompilationCache + storeCustomSections bool + ensureTermination bool +} + +// engineLessConfig helps avoid copy/pasting the wrong defaults. +var engineLessConfig = &runtimeConfig{ + enabledFeatures: api.CoreFeaturesV2, + memoryLimitPages: wasm.MemoryLimitPages, + memoryCapacityFromMax: false, + dwarfDisabled: false, +} + +type engineKind int + +const ( + engineKindAuto engineKind = iota - 1 + engineKindCompiler + engineKindInterpreter + engineKindCount +) + +// NewRuntimeConfigCompiler compiles WebAssembly modules into +// runtime.GOARCH-specific assembly for optimal performance. +// +// The default implementation is AOT (Ahead of Time) compilation, applied at +// Runtime.CompileModule. This allows consistent runtime performance, as well +// the ability to reduce any first request penalty. +// +// Note: While this is technically AOT, this does not imply any action on your +// part. wazero automatically performs ahead-of-time compilation as needed when +// Runtime.CompileModule is invoked. +// +// # Warning +// +// - This panics at runtime if the runtime.GOOS or runtime.GOARCH does not +// support compiler. Use NewRuntimeConfig to safely detect and fallback to +// NewRuntimeConfigInterpreter if needed. +// +// - If you are using wazero in buildmode=c-archive or c-shared, make sure that you set up the alternate signal stack +// by using, e.g. `sigaltstack` combined with `SA_ONSTACK` flag on `sigaction` on Linux, +// before calling any api.Function. This is because the Go runtime does not set up the alternate signal stack +// for c-archive or c-shared modes, and wazero uses the different stack than the calling Goroutine. +// Hence, the signal handler might get invoked on the wazero's stack, which may cause a stack overflow. +// https://github.com/tetratelabs/wazero/blob/2092c0a879f30d49d7b37f333f4547574b8afe0d/internal/integration_test/fuzz/fuzz/tests/sigstack.rs#L19-L36 +func NewRuntimeConfigCompiler() RuntimeConfig { + ret := engineLessConfig.clone() + ret.engineKind = engineKindCompiler + return ret +} + +// NewRuntimeConfigInterpreter interprets WebAssembly modules instead of compiling them into assembly. +func NewRuntimeConfigInterpreter() RuntimeConfig { + ret := engineLessConfig.clone() + ret.engineKind = engineKindInterpreter + return ret +} + +// clone makes a deep copy of this runtime config. +func (c *runtimeConfig) clone() *runtimeConfig { + ret := *c // copy except maps which share a ref + return &ret +} + +// WithCoreFeatures implements RuntimeConfig.WithCoreFeatures +func (c *runtimeConfig) WithCoreFeatures(features api.CoreFeatures) RuntimeConfig { + ret := c.clone() + ret.enabledFeatures = features + return ret +} + +// WithCloseOnContextDone implements RuntimeConfig.WithCloseOnContextDone +func (c *runtimeConfig) WithCloseOnContextDone(ensure bool) RuntimeConfig { + ret := c.clone() + ret.ensureTermination = ensure + return ret +} + +// WithMemoryLimitPages implements RuntimeConfig.WithMemoryLimitPages +func (c *runtimeConfig) WithMemoryLimitPages(memoryLimitPages uint32) RuntimeConfig { + ret := c.clone() + // This panics instead of returning an error as it is unlikely. + if memoryLimitPages > wasm.MemoryLimitPages { + panic(fmt.Errorf("memoryLimitPages invalid: %d > %d", memoryLimitPages, wasm.MemoryLimitPages)) + } + ret.memoryLimitPages = memoryLimitPages + return ret +} + +// WithCompilationCache implements RuntimeConfig.WithCompilationCache +func (c *runtimeConfig) WithCompilationCache(ca CompilationCache) RuntimeConfig { + ret := c.clone() + ret.cache = ca + return ret +} + +// WithMemoryCapacityFromMax implements RuntimeConfig.WithMemoryCapacityFromMax +func (c *runtimeConfig) WithMemoryCapacityFromMax(memoryCapacityFromMax bool) RuntimeConfig { + ret := c.clone() + ret.memoryCapacityFromMax = memoryCapacityFromMax + return ret +} + +// WithDebugInfoEnabled implements RuntimeConfig.WithDebugInfoEnabled +func (c *runtimeConfig) WithDebugInfoEnabled(dwarfEnabled bool) RuntimeConfig { + ret := c.clone() + ret.dwarfDisabled = !dwarfEnabled + return ret +} + +// WithCustomSections implements RuntimeConfig.WithCustomSections +func (c *runtimeConfig) WithCustomSections(storeCustomSections bool) RuntimeConfig { + ret := c.clone() + ret.storeCustomSections = storeCustomSections + return ret +} + +// CompiledModule is a WebAssembly module ready to be instantiated (Runtime.InstantiateModule) as an api.Module. +// +// In WebAssembly terminology, this is a decoded, validated, and possibly also compiled module. wazero avoids using +// the name "Module" for both before and after instantiation as the name conflation has caused confusion. +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#semantic-phases%E2%91%A0 +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +// - Closing the wazero.Runtime closes any CompiledModule it compiled. +type CompiledModule interface { + // Name returns the module name encoded into the binary or empty if not. + Name() string + + // ImportedFunctions returns all the imported functions + // (api.FunctionDefinition) in this module or nil if there are none. + // + // Note: Unlike ExportedFunctions, there is no unique constraint on + // imports. + ImportedFunctions() []api.FunctionDefinition + + // ExportedFunctions returns all the exported functions + // (api.FunctionDefinition) in this module keyed on export name. + ExportedFunctions() map[string]api.FunctionDefinition + + // ImportedMemories returns all the imported memories + // (api.MemoryDefinition) in this module or nil if there are none. + // + // ## Notes + // - As of WebAssembly Core Specification 2.0, there can be at most one + // memory. + // - Unlike ExportedMemories, there is no unique constraint on imports. + ImportedMemories() []api.MemoryDefinition + + // ExportedMemories returns all the exported memories + // (api.MemoryDefinition) in this module keyed on export name. + // + // Note: As of WebAssembly Core Specification 2.0, there can be at most one + // memory. + ExportedMemories() map[string]api.MemoryDefinition + + // CustomSections returns all the custom sections + // (api.CustomSection) in this module keyed on the section name. + CustomSections() []api.CustomSection + + // Close releases all the allocated resources for this CompiledModule. + // + // Note: It is safe to call Close while having outstanding calls from an + // api.Module instantiated from this. + Close(context.Context) error +} + +// compile-time check to ensure compiledModule implements CompiledModule +var _ CompiledModule = &compiledModule{} + +type compiledModule struct { + module *wasm.Module + // compiledEngine holds an engine on which `module` is compiled. + compiledEngine wasm.Engine + // closeWithModule prevents leaking compiled code when a module is compiled implicitly. + closeWithModule bool + typeIDs []wasm.FunctionTypeID +} + +// Name implements CompiledModule.Name +func (c *compiledModule) Name() (moduleName string) { + if ns := c.module.NameSection; ns != nil { + moduleName = ns.ModuleName + } + return +} + +// Close implements CompiledModule.Close +func (c *compiledModule) Close(context.Context) error { + c.compiledEngine.DeleteCompiledModule(c.module) + // It is possible the underlying may need to return an error later, but in any case this matches api.Module.Close. + return nil +} + +// ImportedFunctions implements CompiledModule.ImportedFunctions +func (c *compiledModule) ImportedFunctions() []api.FunctionDefinition { + return c.module.ImportedFunctions() +} + +// ExportedFunctions implements CompiledModule.ExportedFunctions +func (c *compiledModule) ExportedFunctions() map[string]api.FunctionDefinition { + return c.module.ExportedFunctions() +} + +// ImportedMemories implements CompiledModule.ImportedMemories +func (c *compiledModule) ImportedMemories() []api.MemoryDefinition { + return c.module.ImportedMemories() +} + +// ExportedMemories implements CompiledModule.ExportedMemories +func (c *compiledModule) ExportedMemories() map[string]api.MemoryDefinition { + return c.module.ExportedMemories() +} + +// CustomSections implements CompiledModule.CustomSections +func (c *compiledModule) CustomSections() []api.CustomSection { + ret := make([]api.CustomSection, len(c.module.CustomSections)) + for i, d := range c.module.CustomSections { + ret[i] = &customSection{data: d.Data, name: d.Name} + } + return ret +} + +// customSection implements wasm.CustomSection +type customSection struct { + internalapi.WazeroOnlyType + name string + data []byte +} + +// Name implements wasm.CustomSection.Name +func (c *customSection) Name() string { + return c.name +} + +// Data implements wasm.CustomSection.Data +func (c *customSection) Data() []byte { + return c.data +} + +// ModuleConfig configures resources needed by functions that have low-level interactions with the host operating +// system. Using this, resources such as STDIN can be isolated, so that the same module can be safely instantiated +// multiple times. +// +// Here's an example: +// +// // Initialize base configuration: +// config := wazero.NewModuleConfig().WithStdout(buf).WithSysNanotime() +// +// // Assign different configuration on each instantiation +// mod, _ := r.InstantiateModule(ctx, compiled, config.WithName("rotate").WithArgs("rotate", "angle=90", "dir=cw")) +// +// While wazero supports Windows as a platform, host functions using ModuleConfig follow a UNIX dialect. +// See RATIONALE.md for design background and relationship to WebAssembly System Interfaces (WASI). +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +// - ModuleConfig is immutable. Each WithXXX function returns a new instance +// including the corresponding change. +type ModuleConfig interface { + // WithArgs assigns command-line arguments visible to an imported function that reads an arg vector (argv). Defaults to + // none. Runtime.InstantiateModule errs if any arg is empty. + // + // These values are commonly read by the functions like "args_get" in "wasi_snapshot_preview1" although they could be + // read by functions imported from other modules. + // + // Similar to os.Args and exec.Cmd Env, many implementations would expect a program name to be argv[0]. However, neither + // WebAssembly nor WebAssembly System Interfaces (WASI) define this. Regardless, you may choose to set the first + // argument to the same value set via WithName. + // + // Note: This does not default to os.Args as that violates sandboxing. + // + // See https://linux.die.net/man/3/argv and https://en.wikipedia.org/wiki/Null-terminated_string + WithArgs(...string) ModuleConfig + + // WithEnv sets an environment variable visible to a Module that imports functions. Defaults to none. + // Runtime.InstantiateModule errs if the key is empty or contains a NULL(0) or equals("") character. + // + // Validation is the same as os.Setenv on Linux and replaces any existing value. Unlike exec.Cmd Env, this does not + // default to the current process environment as that would violate sandboxing. This also does not preserve order. + // + // Environment variables are commonly read by the functions like "environ_get" in "wasi_snapshot_preview1" although + // they could be read by functions imported from other modules. + // + // While similar to process configuration, there are no assumptions that can be made about anything OS-specific. For + // example, neither WebAssembly nor WebAssembly System Interfaces (WASI) define concerns processes have, such as + // case-sensitivity on environment keys. For portability, define entries with case-insensitively unique keys. + // + // See https://linux.die.net/man/3/environ and https://en.wikipedia.org/wiki/Null-terminated_string + WithEnv(key, value string) ModuleConfig + + // WithFS is a convenience that calls WithFSConfig with an FSConfig of the + // input for the root ("/") guest path. + WithFS(fs.FS) ModuleConfig + + // WithFSConfig configures the filesystem available to each guest + // instantiated with this configuration. By default, no file access is + // allowed, so functions like `path_open` result in unsupported errors + // (e.g. syscall.ENOSYS). + WithFSConfig(FSConfig) ModuleConfig + + // WithName configures the module name. Defaults to what was decoded from + // the name section. Duplicate names are not allowed in a single Runtime. + // + // Calling this with the empty string "" makes the module anonymous. + // That is useful when you want to instantiate the same CompiledModule multiple times like below: + // + // for i := 0; i < N; i++ { + // // Instantiate a new Wasm module from the already compiled `compiledWasm` anonymously without a name. + // instance, err := r.InstantiateModule(ctx, compiledWasm, wazero.NewModuleConfig().WithName("")) + // // .... + // } + // + // See the `concurrent-instantiation` example for a complete usage. + // + // Non-empty named modules are available for other modules to import by name. + WithName(string) ModuleConfig + + // WithStartFunctions configures the functions to call after the module is + // instantiated. Defaults to "_start". + // + // Clearing the default is supported, via `WithStartFunctions()`. + // + // # Notes + // + // - If a start function doesn't exist, it is skipped. However, any that + // do exist are called in order. + // - Start functions are not intended to be called multiple times. + // Functions that should be called multiple times should be invoked + // manually via api.Module's `ExportedFunction` method. + // - Start functions commonly exit the module during instantiation, + // preventing use of any functions later. This is the case in "wasip1", + // which defines the default value "_start". + // - See /RATIONALE.md for motivation of this feature. + WithStartFunctions(...string) ModuleConfig + + // WithStderr configures where standard error (file descriptor 2) is written. Defaults to io.Discard. + // + // This writer is most commonly used by the functions like "fd_write" in "wasi_snapshot_preview1" although it could + // be used by functions imported from other modules. + // + // # Notes + // + // - The caller is responsible to close any io.Writer they supply: It is not closed on api.Module Close. + // - This does not default to os.Stderr as that both violates sandboxing and prevents concurrent modules. + // + // See https://linux.die.net/man/3/stderr + WithStderr(io.Writer) ModuleConfig + + // WithStdin configures where standard input (file descriptor 0) is read. Defaults to return io.EOF. + // + // This reader is most commonly used by the functions like "fd_read" in "wasi_snapshot_preview1" although it could + // be used by functions imported from other modules. + // + // # Notes + // + // - The caller is responsible to close any io.Reader they supply: It is not closed on api.Module Close. + // - This does not default to os.Stdin as that both violates sandboxing and prevents concurrent modules. + // + // See https://linux.die.net/man/3/stdin + WithStdin(io.Reader) ModuleConfig + + // WithStdout configures where standard output (file descriptor 1) is written. Defaults to io.Discard. + // + // This writer is most commonly used by the functions like "fd_write" in "wasi_snapshot_preview1" although it could + // be used by functions imported from other modules. + // + // # Notes + // + // - The caller is responsible to close any io.Writer they supply: It is not closed on api.Module Close. + // - This does not default to os.Stdout as that both violates sandboxing and prevents concurrent modules. + // + // See https://linux.die.net/man/3/stdout + WithStdout(io.Writer) ModuleConfig + + // WithWalltime configures the wall clock, sometimes referred to as the + // real time clock. sys.Walltime returns the current unix/epoch time, + // seconds since midnight UTC 1 January 1970, with a nanosecond fraction. + // This defaults to a fake result that increases by 1ms on each reading. + // + // Here's an example that uses a custom clock: + // moduleConfig = moduleConfig. + // WithWalltime(func(context.Context) (sec int64, nsec int32) { + // return clock.walltime() + // }, sys.ClockResolution(time.Microsecond.Nanoseconds())) + // + // # Notes: + // - This does not default to time.Now as that violates sandboxing. + // - This is used to implement host functions such as WASI + // `clock_time_get` with the `realtime` clock ID. + // - Use WithSysWalltime for a usable implementation. + WithWalltime(sys.Walltime, sys.ClockResolution) ModuleConfig + + // WithSysWalltime uses time.Now for sys.Walltime with a resolution of 1us + // (1000ns). + // + // See WithWalltime + WithSysWalltime() ModuleConfig + + // WithNanotime configures the monotonic clock, used to measure elapsed + // time in nanoseconds. Defaults to a fake result that increases by 1ms + // on each reading. + // + // Here's an example that uses a custom clock: + // moduleConfig = moduleConfig. + // WithNanotime(func(context.Context) int64 { + // return clock.nanotime() + // }, sys.ClockResolution(time.Microsecond.Nanoseconds())) + // + // # Notes: + // - This does not default to time.Since as that violates sandboxing. + // - This is used to implement host functions such as WASI + // `clock_time_get` with the `monotonic` clock ID. + // - Some compilers implement sleep by looping on sys.Nanotime (e.g. Go). + // - If you set this, you should probably set WithNanosleep also. + // - Use WithSysNanotime for a usable implementation. + WithNanotime(sys.Nanotime, sys.ClockResolution) ModuleConfig + + // WithSysNanotime uses time.Now for sys.Nanotime with a resolution of 1us. + // + // See WithNanotime + WithSysNanotime() ModuleConfig + + // WithNanosleep configures the how to pause the current goroutine for at + // least the configured nanoseconds. Defaults to return immediately. + // + // This example uses a custom sleep function: + // moduleConfig = moduleConfig. + // WithNanosleep(func(ns int64) { + // rel := unix.NsecToTimespec(ns) + // remain := unix.Timespec{} + // for { // loop until no more time remaining + // err := unix.ClockNanosleep(unix.CLOCK_MONOTONIC, 0, &rel, &remain) + // --snip-- + // + // # Notes: + // - This does not default to time.Sleep as that violates sandboxing. + // - This is used to implement host functions such as WASI `poll_oneoff`. + // - Some compilers implement sleep by looping on sys.Nanotime (e.g. Go). + // - If you set this, you should probably set WithNanotime also. + // - Use WithSysNanosleep for a usable implementation. + WithNanosleep(sys.Nanosleep) ModuleConfig + + // WithOsyield yields the processor, typically to implement spin-wait + // loops. Defaults to return immediately. + // + // # Notes: + // - This primarily supports `sched_yield` in WASI + // - This does not default to runtime.osyield as that violates sandboxing. + WithOsyield(sys.Osyield) ModuleConfig + + // WithSysNanosleep uses time.Sleep for sys.Nanosleep. + // + // See WithNanosleep + WithSysNanosleep() ModuleConfig + + // WithRandSource configures a source of random bytes. Defaults to return a + // deterministic source. You might override this with crypto/rand.Reader + // + // This reader is most commonly used by the functions like "random_get" in + // "wasi_snapshot_preview1", "seed" in AssemblyScript standard "env", and + // "getRandomData" when runtime.GOOS is "js". + // + // Note: The caller is responsible to close any io.Reader they supply: It + // is not closed on api.Module Close. + WithRandSource(io.Reader) ModuleConfig +} + +type moduleConfig struct { + name string + nameSet bool + startFunctions []string + stdin io.Reader + stdout io.Writer + stderr io.Writer + randSource io.Reader + walltime sys.Walltime + walltimeResolution sys.ClockResolution + nanotime sys.Nanotime + nanotimeResolution sys.ClockResolution + nanosleep sys.Nanosleep + osyield sys.Osyield + args [][]byte + // environ is pair-indexed to retain order similar to os.Environ. + environ [][]byte + // environKeys allow overwriting of existing values. + environKeys map[string]int + // fsConfig is the file system configuration for ABI like WASI. + fsConfig FSConfig + // sockConfig is the network listener configuration for ABI like WASI. + sockConfig *internalsock.Config +} + +// NewModuleConfig returns a ModuleConfig that can be used for configuring module instantiation. +func NewModuleConfig() ModuleConfig { + return &moduleConfig{ + startFunctions: []string{"_start"}, + environKeys: map[string]int{}, + } +} + +// clone makes a deep copy of this module config. +func (c *moduleConfig) clone() *moduleConfig { + ret := *c // copy except maps which share a ref + ret.environKeys = make(map[string]int, len(c.environKeys)) + for key, value := range c.environKeys { + ret.environKeys[key] = value + } + return &ret +} + +// WithArgs implements ModuleConfig.WithArgs +func (c *moduleConfig) WithArgs(args ...string) ModuleConfig { + ret := c.clone() + ret.args = toByteSlices(args) + return ret +} + +func toByteSlices(strings []string) (result [][]byte) { + if len(strings) == 0 { + return + } + result = make([][]byte, len(strings)) + for i, a := range strings { + result[i] = []byte(a) + } + return +} + +// WithEnv implements ModuleConfig.WithEnv +func (c *moduleConfig) WithEnv(key, value string) ModuleConfig { + ret := c.clone() + // Check to see if this key already exists and update it. + if i, ok := ret.environKeys[key]; ok { + ret.environ[i+1] = []byte(value) // environ is pair-indexed, so the value is 1 after the key. + } else { + ret.environKeys[key] = len(ret.environ) + ret.environ = append(ret.environ, []byte(key), []byte(value)) + } + return ret +} + +// WithFS implements ModuleConfig.WithFS +func (c *moduleConfig) WithFS(fs fs.FS) ModuleConfig { + var config FSConfig + if fs != nil { + config = NewFSConfig().WithFSMount(fs, "") + } + return c.WithFSConfig(config) +} + +// WithFSConfig implements ModuleConfig.WithFSConfig +func (c *moduleConfig) WithFSConfig(config FSConfig) ModuleConfig { + ret := c.clone() + ret.fsConfig = config + return ret +} + +// WithName implements ModuleConfig.WithName +func (c *moduleConfig) WithName(name string) ModuleConfig { + ret := c.clone() + ret.nameSet = true + ret.name = name + return ret +} + +// WithStartFunctions implements ModuleConfig.WithStartFunctions +func (c *moduleConfig) WithStartFunctions(startFunctions ...string) ModuleConfig { + ret := c.clone() + ret.startFunctions = startFunctions + return ret +} + +// WithStderr implements ModuleConfig.WithStderr +func (c *moduleConfig) WithStderr(stderr io.Writer) ModuleConfig { + ret := c.clone() + ret.stderr = stderr + return ret +} + +// WithStdin implements ModuleConfig.WithStdin +func (c *moduleConfig) WithStdin(stdin io.Reader) ModuleConfig { + ret := c.clone() + ret.stdin = stdin + return ret +} + +// WithStdout implements ModuleConfig.WithStdout +func (c *moduleConfig) WithStdout(stdout io.Writer) ModuleConfig { + ret := c.clone() + ret.stdout = stdout + return ret +} + +// WithWalltime implements ModuleConfig.WithWalltime +func (c *moduleConfig) WithWalltime(walltime sys.Walltime, resolution sys.ClockResolution) ModuleConfig { + ret := c.clone() + ret.walltime = walltime + ret.walltimeResolution = resolution + return ret +} + +// We choose arbitrary resolutions here because there's no perfect alternative. For example, according to the +// source in time.go, windows monotonic resolution can be 15ms. This chooses arbitrarily 1us for wall time and +// 1ns for monotonic. See RATIONALE.md for more context. + +// WithSysWalltime implements ModuleConfig.WithSysWalltime +func (c *moduleConfig) WithSysWalltime() ModuleConfig { + return c.WithWalltime(platform.Walltime, sys.ClockResolution(time.Microsecond.Nanoseconds())) +} + +// WithNanotime implements ModuleConfig.WithNanotime +func (c *moduleConfig) WithNanotime(nanotime sys.Nanotime, resolution sys.ClockResolution) ModuleConfig { + ret := c.clone() + ret.nanotime = nanotime + ret.nanotimeResolution = resolution + return ret +} + +// WithSysNanotime implements ModuleConfig.WithSysNanotime +func (c *moduleConfig) WithSysNanotime() ModuleConfig { + return c.WithNanotime(platform.Nanotime, sys.ClockResolution(1)) +} + +// WithNanosleep implements ModuleConfig.WithNanosleep +func (c *moduleConfig) WithNanosleep(nanosleep sys.Nanosleep) ModuleConfig { + ret := *c // copy + ret.nanosleep = nanosleep + return &ret +} + +// WithOsyield implements ModuleConfig.WithOsyield +func (c *moduleConfig) WithOsyield(osyield sys.Osyield) ModuleConfig { + ret := *c // copy + ret.osyield = osyield + return &ret +} + +// WithSysNanosleep implements ModuleConfig.WithSysNanosleep +func (c *moduleConfig) WithSysNanosleep() ModuleConfig { + return c.WithNanosleep(platform.Nanosleep) +} + +// WithRandSource implements ModuleConfig.WithRandSource +func (c *moduleConfig) WithRandSource(source io.Reader) ModuleConfig { + ret := c.clone() + ret.randSource = source + return ret +} + +// toSysContext creates a baseline wasm.Context configured by ModuleConfig. +func (c *moduleConfig) toSysContext() (sysCtx *internalsys.Context, err error) { + var environ [][]byte // Intentionally doesn't pre-allocate to reduce logic to default to nil. + // Same validation as syscall.Setenv for Linux + for i := 0; i < len(c.environ); i += 2 { + key, value := c.environ[i], c.environ[i+1] + keyLen := len(key) + if keyLen == 0 { + err = errors.New("environ invalid: empty key") + return + } + valueLen := len(value) + result := make([]byte, keyLen+valueLen+1) + j := 0 + for ; j < keyLen; j++ { + if k := key[j]; k == '=' { // NUL enforced in NewContext + err = errors.New("environ invalid: key contains '=' character") + return + } else { + result[j] = k + } + } + result[j] = '=' + copy(result[j+1:], value) + environ = append(environ, result) + } + + var fs []experimentalsys.FS + var guestPaths []string + if f, ok := c.fsConfig.(*fsConfig); ok { + fs, guestPaths = f.preopens() + } + + var listeners []*net.TCPListener + if n := c.sockConfig; n != nil { + if listeners, err = n.BuildTCPListeners(); err != nil { + return + } + } + + return internalsys.NewContext( + math.MaxUint32, + c.args, + environ, + c.stdin, + c.stdout, + c.stderr, + c.randSource, + c.walltime, c.walltimeResolution, + c.nanotime, c.nanotimeResolution, + c.nanosleep, c.osyield, + fs, guestPaths, + listeners, + ) +} diff --git a/vendor/github.com/tetratelabs/wazero/experimental/checkpoint.go b/vendor/github.com/tetratelabs/wazero/experimental/checkpoint.go new file mode 100644 index 00000000000..c75db615e61 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/checkpoint.go @@ -0,0 +1,35 @@ +package experimental + +import ( + "context" + + "github.com/tetratelabs/wazero/internal/expctxkeys" +) + +// Snapshot holds the execution state at the time of a Snapshotter.Snapshot call. +type Snapshot interface { + // Restore sets the Wasm execution state to the capture. Because a host function + // calling this is resetting the pointer to the executation stack, the host function + // will not be able to return values in the normal way. ret is a slice of values the + // host function intends to return from the restored function. + Restore(ret []uint64) +} + +// Snapshotter allows host functions to snapshot the WebAssembly execution environment. +type Snapshotter interface { + // Snapshot captures the current execution state. + Snapshot() Snapshot +} + +// WithSnapshotter enables snapshots. +// Passing the returned context to a exported function invocation enables snapshots, +// and allows host functions to retrieve the Snapshotter using GetSnapshotter. +func WithSnapshotter(ctx context.Context) context.Context { + return context.WithValue(ctx, expctxkeys.EnableSnapshotterKey{}, struct{}{}) +} + +// GetSnapshotter gets the Snapshotter from a host function. +// It is only present if WithSnapshotter was called with the function invocation context. +func GetSnapshotter(ctx context.Context) Snapshotter { + return ctx.Value(expctxkeys.SnapshotterKey{}).(Snapshotter) +} diff --git a/vendor/github.com/tetratelabs/wazero/experimental/close.go b/vendor/github.com/tetratelabs/wazero/experimental/close.go new file mode 100644 index 00000000000..babecaec49f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/close.go @@ -0,0 +1,63 @@ +package experimental + +import ( + "context" + + "github.com/tetratelabs/wazero/internal/expctxkeys" +) + +// CloseNotifier is a notification hook, invoked when a module is closed. +// +// Note: This is experimental progress towards #1197, and likely to change. Do +// not expose this in shared libraries as it can cause version locks. +type CloseNotifier interface { + // CloseNotify is a notification that occurs *before* an api.Module is + // closed. `exitCode` is zero on success or in the case there was no exit + // code. + // + // Notes: + // - This does not return an error because the module will be closed + // unconditionally. + // - Do not panic from this function as it doing so could cause resource + // leaks. + // - While this is only called once per module, if configured for + // multiple modules, it will be called for each, e.g. on runtime close. + CloseNotify(ctx context.Context, exitCode uint32) +} + +// ^-- Note: This might need to be a part of the listener or become a part of +// host state implementation. For example, if this is used to implement state +// cleanup for host modules, possibly something like below would be better, as +// it could be implemented in a way that allows concurrent module use. +// +// // key is like a context key, stateFactory is invoked per instantiate and +// // is associated with the key (exposed as `Module.State` similar to go +// // context). Using a key is better than the module name because we can +// // de-dupe it for host modules that can be instantiated into different +// // names. Also, you can make the key package private. +// HostModuleBuilder.WithState(key any, stateFactory func() Cleanup)` +// +// Such a design could work to isolate state only needed for wasip1, for +// example the dirent cache. However, if end users use this for different +// things, we may need separate designs. +// +// In summary, the purpose of this iteration is to identify projects that +// would use something like this, and then we can figure out which way it +// should go. + +// CloseNotifyFunc is a convenience for defining inlining a CloseNotifier. +type CloseNotifyFunc func(ctx context.Context, exitCode uint32) + +// CloseNotify implements CloseNotifier.CloseNotify. +func (f CloseNotifyFunc) CloseNotify(ctx context.Context, exitCode uint32) { + f(ctx, exitCode) +} + +// WithCloseNotifier registers the given CloseNotifier into the given +// context.Context. +func WithCloseNotifier(ctx context.Context, notifier CloseNotifier) context.Context { + if notifier != nil { + return context.WithValue(ctx, expctxkeys.CloseNotifierKey{}, notifier) + } + return ctx +} diff --git a/vendor/github.com/tetratelabs/wazero/experimental/compilationworkers.go b/vendor/github.com/tetratelabs/wazero/experimental/compilationworkers.go new file mode 100644 index 00000000000..bb76e01d3fa --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/compilationworkers.go @@ -0,0 +1,19 @@ +package experimental + +import ( + "context" + + "github.com/tetratelabs/wazero/internal/expctxkeys" +) + +// WithCompilationWorkers sets the desired number of compilation workers. +func WithCompilationWorkers(ctx context.Context, workers int) context.Context { + return context.WithValue(ctx, expctxkeys.CompilationWorkers{}, workers) +} + +// GetCompilationWorkers returns the desired number of compilation workers. +// The minimum value returned is 1. +func GetCompilationWorkers(ctx context.Context) int { + workers, _ := ctx.Value(expctxkeys.CompilationWorkers{}).(int) + return max(workers, 1) +} diff --git a/vendor/github.com/tetratelabs/wazero/experimental/experimental.go b/vendor/github.com/tetratelabs/wazero/experimental/experimental.go new file mode 100644 index 00000000000..63fd564da38 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/experimental.go @@ -0,0 +1,41 @@ +// Package experimental includes features we aren't yet sure about. These are enabled with context.Context keys. +// +// Note: All features here may be changed or deleted at any time, so use with caution! +package experimental + +import ( + "github.com/tetratelabs/wazero/api" +) + +// InternalModule is an api.Module that exposes additional +// information. +type InternalModule interface { + api.Module + + // NumGlobal returns the count of all globals in the module. + NumGlobal() int + + // Global provides a read-only view for a given global index. + // + // The methods panics if i is out of bounds. + Global(i int) api.Global +} + +// ProgramCounter is an opaque value representing a specific execution point in +// a module. It is meant to be used with Function.SourceOffsetForPC and +// StackIterator. +type ProgramCounter uint64 + +// InternalFunction exposes some information about a function instance. +type InternalFunction interface { + // Definition provides introspection into the function's names and + // signature. + Definition() api.FunctionDefinition + + // SourceOffsetForPC resolves a program counter into its corresponding + // offset in the Code section of the module this function belongs to. + // The source offset is meant to help map the function calls to their + // location in the original source files. Returns 0 if the offset cannot + // be calculated. + SourceOffsetForPC(pc ProgramCounter) uint64 +} diff --git a/vendor/github.com/tetratelabs/wazero/experimental/features.go b/vendor/github.com/tetratelabs/wazero/experimental/features.go new file mode 100644 index 00000000000..a695f149f4f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/features.go @@ -0,0 +1,18 @@ +package experimental + +import "github.com/tetratelabs/wazero/api" + +// CoreFeaturesThreads enables threads instructions ("threads"). +// +// # Notes +// +// - The instruction list is too long to enumerate in godoc. +// See https://github.com/WebAssembly/threads/blob/main/proposals/threads/Overview.md +// - Atomic operations are guest-only until api.Memory or otherwise expose them to host functions. +// - On systems without mmap available, the memory will pre-allocate to the maximum size. Many +// binaries will use a theroetical maximum like 4GB, so if using such a binary on a system +// without mmap, consider editing the binary to reduce the max size setting of memory. +const CoreFeaturesThreads = api.CoreFeatureSIMD << 1 + +// CoreFeaturesThreads enables tail call instructions ("tail-call"). +const CoreFeaturesTailCall = api.CoreFeatureSIMD << 2 diff --git a/vendor/github.com/tetratelabs/wazero/experimental/importresolver.go b/vendor/github.com/tetratelabs/wazero/experimental/importresolver.go new file mode 100644 index 00000000000..36c0e22b159 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/importresolver.go @@ -0,0 +1,19 @@ +package experimental + +import ( + "context" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/expctxkeys" +) + +// ImportResolver is an experimental func type that, if set, +// will be used as the first step in resolving imports. +// See issue 2294. +// If the import name is not found, it should return nil. +type ImportResolver func(name string) api.Module + +// WithImportResolver returns a new context with the given ImportResolver. +func WithImportResolver(ctx context.Context, resolver ImportResolver) context.Context { + return context.WithValue(ctx, expctxkeys.ImportResolverKey{}, resolver) +} diff --git a/vendor/github.com/tetratelabs/wazero/experimental/listener.go b/vendor/github.com/tetratelabs/wazero/experimental/listener.go new file mode 100644 index 00000000000..55fc6b668ec --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/listener.go @@ -0,0 +1,324 @@ +package experimental + +import ( + "context" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/expctxkeys" +) + +// StackIterator allows iterating on each function of the call stack, starting +// from the top. At least one call to Next() is required to start the iteration. +// +// Note: The iterator provides a view of the call stack at the time of +// iteration. As a result, parameter values may be different than the ones their +// function was called with. +type StackIterator interface { + // Next moves the iterator to the next function in the stack. Returns + // false if it reached the bottom of the stack. + Next() bool + // Function describes the function called by the current frame. + Function() InternalFunction + // ProgramCounter returns the program counter associated with the + // function call. + ProgramCounter() ProgramCounter +} + +// WithFunctionListenerFactory registers a FunctionListenerFactory +// with the context. +func WithFunctionListenerFactory(ctx context.Context, factory FunctionListenerFactory) context.Context { + return context.WithValue(ctx, expctxkeys.FunctionListenerFactoryKey{}, factory) +} + +// FunctionListenerFactory returns FunctionListeners to be notified when a +// function is called. +type FunctionListenerFactory interface { + // NewFunctionListener returns a FunctionListener for a defined function. + // If nil is returned, no listener will be notified. + NewFunctionListener(api.FunctionDefinition) FunctionListener + // ^^ A single instance can be returned to avoid instantiating a listener + // per function, especially as they may be thousands of functions. Shared + // listeners use their FunctionDefinition parameter to clarify. +} + +// FunctionListener can be registered for any function via +// FunctionListenerFactory to be notified when the function is called. +type FunctionListener interface { + // Before is invoked before a function is called. + // + // There is always one corresponding call to After or Abort for each call to + // Before. This guarantee allows the listener to maintain an internal stack + // to perform correlations between the entry and exit of functions. + // + // # Params + // + // - ctx: the context of the caller function which must be the same + // instance or parent of the result. + // - mod: the calling module. + // - def: the function definition. + // - params: api.ValueType encoded parameters. + // - stackIterator: iterator on the call stack. At least one entry is + // guaranteed (the called function), whose Args() will be equal to + // params. The iterator will be reused between calls to Before. + // + // Note: api.Memory is meant for inspection, not modification. + // mod can be cast to InternalModule to read non-exported globals. + Before(ctx context.Context, mod api.Module, def api.FunctionDefinition, params []uint64, stackIterator StackIterator) + + // After is invoked after a function is called. + // + // # Params + // + // - ctx: the context of the caller function. + // - mod: the calling module. + // - def: the function definition. + // - results: api.ValueType encoded results. + // + // # Notes + // + // - api.Memory is meant for inspection, not modification. + // - This is not called when a host function panics, or a guest function traps. + // See Abort for more details. + After(ctx context.Context, mod api.Module, def api.FunctionDefinition, results []uint64) + + // Abort is invoked when a function does not return due to a trap or panic. + // + // # Params + // + // - ctx: the context of the caller function. + // - mod: the calling module. + // - def: the function definition. + // - err: the error value representing the reason why the function aborted. + // + // # Notes + // + // - api.Memory is meant for inspection, not modification. + Abort(ctx context.Context, mod api.Module, def api.FunctionDefinition, err error) +} + +// FunctionListenerFunc is a function type implementing the FunctionListener +// interface, making it possible to use regular functions and methods as +// listeners of function invocation. +// +// The FunctionListener interface declares two methods (Before and After), +// but this type invokes its value only when Before is called. It is best +// suites for cases where the host does not need to perform correlation +// between the start and end of the function call. +type FunctionListenerFunc func(context.Context, api.Module, api.FunctionDefinition, []uint64, StackIterator) + +// Before satisfies the FunctionListener interface, calls f. +func (f FunctionListenerFunc) Before(ctx context.Context, mod api.Module, def api.FunctionDefinition, params []uint64, stackIterator StackIterator) { + f(ctx, mod, def, params, stackIterator) +} + +// After is declared to satisfy the FunctionListener interface, but it does +// nothing. +func (f FunctionListenerFunc) After(context.Context, api.Module, api.FunctionDefinition, []uint64) { +} + +// Abort is declared to satisfy the FunctionListener interface, but it does +// nothing. +func (f FunctionListenerFunc) Abort(context.Context, api.Module, api.FunctionDefinition, error) { +} + +// FunctionListenerFactoryFunc is a function type implementing the +// FunctionListenerFactory interface, making it possible to use regular +// functions and methods as factory of function listeners. +type FunctionListenerFactoryFunc func(api.FunctionDefinition) FunctionListener + +// NewFunctionListener satisfies the FunctionListenerFactory interface, calls f. +func (f FunctionListenerFactoryFunc) NewFunctionListener(def api.FunctionDefinition) FunctionListener { + return f(def) +} + +// MultiFunctionListenerFactory constructs a FunctionListenerFactory which +// combines the listeners created by each of the factories passed as arguments. +// +// This function is useful when multiple listeners need to be hooked to a module +// because the propagation mechanism based on installing a listener factory in +// the context.Context used when instantiating modules allows for a single +// listener to be installed. +// +// The stack iterator passed to the Before method is reset so that each listener +// can iterate the call stack independently without impacting the ability of +// other listeners to do so. +func MultiFunctionListenerFactory(factories ...FunctionListenerFactory) FunctionListenerFactory { + multi := make(multiFunctionListenerFactory, len(factories)) + copy(multi, factories) + return multi +} + +type multiFunctionListenerFactory []FunctionListenerFactory + +func (multi multiFunctionListenerFactory) NewFunctionListener(def api.FunctionDefinition) FunctionListener { + var lstns []FunctionListener + for _, factory := range multi { + if lstn := factory.NewFunctionListener(def); lstn != nil { + lstns = append(lstns, lstn) + } + } + switch len(lstns) { + case 0: + return nil + case 1: + return lstns[0] + default: + return &multiFunctionListener{lstns: lstns} + } +} + +type multiFunctionListener struct { + lstns []FunctionListener + stack stackIterator +} + +func (multi *multiFunctionListener) Before(ctx context.Context, mod api.Module, def api.FunctionDefinition, params []uint64, si StackIterator) { + multi.stack.base = si + for _, lstn := range multi.lstns { + multi.stack.index = -1 + lstn.Before(ctx, mod, def, params, &multi.stack) + } +} + +func (multi *multiFunctionListener) After(ctx context.Context, mod api.Module, def api.FunctionDefinition, results []uint64) { + for _, lstn := range multi.lstns { + lstn.After(ctx, mod, def, results) + } +} + +func (multi *multiFunctionListener) Abort(ctx context.Context, mod api.Module, def api.FunctionDefinition, err error) { + for _, lstn := range multi.lstns { + lstn.Abort(ctx, mod, def, err) + } +} + +type stackIterator struct { + base StackIterator + index int + pcs []uint64 + fns []InternalFunction +} + +func (si *stackIterator) Next() bool { + if si.base != nil { + si.pcs = si.pcs[:0] + si.fns = si.fns[:0] + + for si.base.Next() { + si.pcs = append(si.pcs, uint64(si.base.ProgramCounter())) + si.fns = append(si.fns, si.base.Function()) + } + + si.base = nil + } + si.index++ + return si.index < len(si.pcs) +} + +func (si *stackIterator) ProgramCounter() ProgramCounter { + return ProgramCounter(si.pcs[si.index]) +} + +func (si *stackIterator) Function() InternalFunction { + return si.fns[si.index] +} + +// StackFrame represents a frame on the call stack. +type StackFrame struct { + Function api.Function + Params []uint64 + Results []uint64 + PC uint64 + SourceOffset uint64 +} + +type internalFunction struct { + definition api.FunctionDefinition + sourceOffset uint64 +} + +func (f internalFunction) Definition() api.FunctionDefinition { + return f.definition +} + +func (f internalFunction) SourceOffsetForPC(pc ProgramCounter) uint64 { + return f.sourceOffset +} + +// stackFrameIterator is an implementation of the experimental.stackFrameIterator +// interface. +type stackFrameIterator struct { + index int + stack []StackFrame + fndef []api.FunctionDefinition +} + +func (si *stackFrameIterator) Next() bool { + si.index++ + return si.index < len(si.stack) +} + +func (si *stackFrameIterator) Function() InternalFunction { + return internalFunction{ + definition: si.fndef[si.index], + sourceOffset: si.stack[si.index].SourceOffset, + } +} + +func (si *stackFrameIterator) ProgramCounter() ProgramCounter { + return ProgramCounter(si.stack[si.index].PC) +} + +// NewStackIterator constructs a stack iterator from a list of stack frames. +// The top most frame is the last one. +func NewStackIterator(stack ...StackFrame) StackIterator { + si := &stackFrameIterator{ + index: -1, + stack: make([]StackFrame, len(stack)), + fndef: make([]api.FunctionDefinition, len(stack)), + } + for i := range stack { + si.stack[i] = stack[len(stack)-(i+1)] + } + // The size of function definition is only one pointer which should allow + // the compiler to optimize the conversion to api.FunctionDefinition; but + // the presence of internal.WazeroOnlyType, despite being defined as an + // empty struct, forces a heap allocation that we amortize by caching the + // result. + for i, frame := range stack { + si.fndef[i] = frame.Function.Definition() + } + return si +} + +// BenchmarkFunctionListener implements a benchmark for function listeners. +// +// The benchmark calls Before and After methods repeatedly using the provided +// module an stack frames to invoke the methods. +// +// The stack frame is a representation of the call stack that the Before method +// will be invoked with. The top of the stack is stored at index zero. The stack +// must contain at least one frame or the benchmark will fail. +func BenchmarkFunctionListener(n int, module api.Module, stack []StackFrame, listener FunctionListener) { + if len(stack) == 0 { + panic("cannot benchmark function listener with an empty stack") + } + + ctx := context.Background() + def := stack[0].Function.Definition() + params := stack[0].Params + results := stack[0].Results + stackIterator := &stackIterator{base: NewStackIterator(stack...)} + + for i := 0; i < n; i++ { + stackIterator.index = -1 + listener.Before(ctx, module, def, params, stackIterator) + listener.After(ctx, module, def, results) + } +} + +// TODO: the calls to Abort are not yet tested in internal/testing/enginetest, +// but they are validated indirectly in tests which exercise host logging, +// like Test_procExit in imports/wasi_snapshot_preview1. Eventually we should +// add dedicated tests to validate the behavior of the interpreter and compiler +// engines independently. diff --git a/vendor/github.com/tetratelabs/wazero/experimental/memory.go b/vendor/github.com/tetratelabs/wazero/experimental/memory.go new file mode 100644 index 00000000000..8bf3aa35fa7 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/memory.go @@ -0,0 +1,52 @@ +package experimental + +import ( + "context" + + "github.com/tetratelabs/wazero/internal/expctxkeys" +) + +// MemoryAllocator is a memory allocation hook, +// invoked to create a LinearMemory. +type MemoryAllocator interface { + // Allocate should create a new LinearMemory with the given specification: + // cap is the suggested initial capacity for the backing []byte, + // and max the maximum length that will ever be requested. + // + // Notes: + // - To back a shared memory, the address of the backing []byte cannot + // change. This is checked at runtime. Implementations should document + // if the returned LinearMemory meets this requirement. + Allocate(cap, max uint64) LinearMemory +} + +// MemoryAllocatorFunc is a convenience for defining inlining a MemoryAllocator. +type MemoryAllocatorFunc func(cap, max uint64) LinearMemory + +// Allocate implements MemoryAllocator.Allocate. +func (f MemoryAllocatorFunc) Allocate(cap, max uint64) LinearMemory { + return f(cap, max) +} + +// LinearMemory is an expandable []byte that backs a Wasm linear memory. +type LinearMemory interface { + // Reallocates the linear memory to size bytes in length. + // + // Notes: + // - To back a shared memory, Reallocate can't change the address of the + // backing []byte (only its length/capacity may change). + // - Reallocate may return nil if fails to grow the LinearMemory. This + // condition may or may not be handled gracefully by the Wasm module. + Reallocate(size uint64) []byte + // Free the backing memory buffer. + Free() +} + +// WithMemoryAllocator registers the given MemoryAllocator into the given +// context.Context. The context must be passed when initializing a module. +func WithMemoryAllocator(ctx context.Context, allocator MemoryAllocator) context.Context { + if allocator != nil { + return context.WithValue(ctx, expctxkeys.MemoryAllocatorKey{}, allocator) + } + return ctx +} diff --git a/vendor/github.com/tetratelabs/wazero/experimental/sys/dir.go b/vendor/github.com/tetratelabs/wazero/experimental/sys/dir.go new file mode 100644 index 00000000000..0b997cb8fc4 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/sys/dir.go @@ -0,0 +1,92 @@ +package sys + +import ( + "fmt" + "io/fs" + + "github.com/tetratelabs/wazero/sys" +) + +// FileType is fs.FileMode masked on fs.ModeType. For example, zero is a +// regular file, fs.ModeDir is a directory and fs.ModeIrregular is unknown. +// +// Note: This is defined by Linux, not POSIX. +type FileType = fs.FileMode + +// Dirent is an entry read from a directory via File.Readdir. +// +// # Notes +// +// - This extends `dirent` defined in POSIX with some fields defined by +// Linux. See https://man7.org/linux/man-pages/man3/readdir.3.html and +// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/dirent.h.html +// - This has a subset of fields defined in sys.Stat_t. Notably, there is no +// field corresponding to Stat_t.Dev because that value will be constant +// for all files in a directory. To get the Dev value, call File.Stat on +// the directory File.Readdir was called on. +type Dirent struct { + // Ino is the file serial number, or zero if not available. See Ino for + // more details including impact returning a zero value. + Ino sys.Inode + + // Name is the base name of the directory entry. Empty is invalid. + Name string + + // Type is fs.FileMode masked on fs.ModeType. For example, zero is a + // regular file, fs.ModeDir is a directory and fs.ModeIrregular is unknown. + // + // Note: This is defined by Linux, not POSIX. + Type fs.FileMode +} + +func (d *Dirent) String() string { + return fmt.Sprintf("name=%s, type=%v, ino=%d", d.Name, d.Type, d.Ino) +} + +// IsDir returns true if the Type is fs.ModeDir. +func (d *Dirent) IsDir() bool { + return d.Type == fs.ModeDir +} + +// DirFile is embeddable to reduce the amount of functions to implement a file. +type DirFile struct{} + +// IsAppend implements File.IsAppend +func (DirFile) IsAppend() bool { + return false +} + +// SetAppend implements File.SetAppend +func (DirFile) SetAppend(bool) Errno { + return EISDIR +} + +// IsDir implements File.IsDir +func (DirFile) IsDir() (bool, Errno) { + return true, 0 +} + +// Read implements File.Read +func (DirFile) Read([]byte) (int, Errno) { + return 0, EISDIR +} + +// Pread implements File.Pread +func (DirFile) Pread([]byte, int64) (int, Errno) { + return 0, EISDIR +} + +// Write implements File.Write +func (DirFile) Write([]byte) (int, Errno) { + return 0, EISDIR +} + +// Pwrite implements File.Pwrite +func (DirFile) Pwrite([]byte, int64) (int, Errno) { + return 0, EISDIR +} + +// Truncate implements File.Truncate +func (DirFile) Truncate(int64) Errno { + return EISDIR +} diff --git a/vendor/github.com/tetratelabs/wazero/experimental/sys/errno.go b/vendor/github.com/tetratelabs/wazero/experimental/sys/errno.go new file mode 100644 index 00000000000..2389494960c --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/sys/errno.go @@ -0,0 +1,98 @@ +package sys + +import "strconv" + +// Errno is a subset of POSIX errno used by wazero interfaces. Zero is not an +// error. Other values should not be interpreted numerically, rather by constants +// prefixed with 'E'. +// +// See https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html +type Errno uint16 + +// ^-- Note: This will eventually move to the public /sys package. It is +// experimental until we audit the socket related APIs to ensure we have all +// the Errno it returns, and we export fs.FS. This is not in /internal/sys as +// that would introduce a package cycle. + +// This is a subset of errors to reduce implementation burden. `wasip1` defines +// almost all POSIX error numbers, but not all are used in practice. wazero +// will add ones needed in POSIX order, as needed by functions that explicitly +// document returning them. +// +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-errno-enumu16 +const ( + EACCES Errno = iota + 1 + EAGAIN + EBADF + EEXIST + EFAULT + EINTR + EINVAL + EIO + EISDIR + ELOOP + ENAMETOOLONG + ENOENT + ENOSYS + ENOTDIR + ERANGE + ENOTEMPTY + ENOTSOCK + ENOTSUP + EPERM + EROFS + + // NOTE ENOTCAPABLE is defined in wasip1, but not in POSIX. wasi-libc + // converts it to EBADF, ESPIPE or EINVAL depending on the call site. + // It isn't known if compilers who don't use ENOTCAPABLE would crash on it. +) + +// Error implements error +func (e Errno) Error() string { + switch e { + case 0: // not an error + return "success" + case EACCES: + return "permission denied" + case EAGAIN: + return "resource unavailable, try again" + case EBADF: + return "bad file descriptor" + case EEXIST: + return "file exists" + case EFAULT: + return "bad address" + case EINTR: + return "interrupted function" + case EINVAL: + return "invalid argument" + case EIO: + return "input/output error" + case EISDIR: + return "is a directory" + case ELOOP: + return "too many levels of symbolic links" + case ENAMETOOLONG: + return "filename too long" + case ENOENT: + return "no such file or directory" + case ENOSYS: + return "functionality not supported" + case ENOTDIR: + return "not a directory or a symbolic link to a directory" + case ERANGE: + return "result too large" + case ENOTEMPTY: + return "directory not empty" + case ENOTSOCK: + return "not a socket" + case ENOTSUP: + return "not supported (may be the same value as [EOPNOTSUPP])" + case EPERM: + return "operation not permitted" + case EROFS: + return "read-only file system" + default: + return "Errno(" + strconv.Itoa(int(e)) + ")" + } +} diff --git a/vendor/github.com/tetratelabs/wazero/experimental/sys/error.go b/vendor/github.com/tetratelabs/wazero/experimental/sys/error.go new file mode 100644 index 00000000000..a0c76019aaf --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/sys/error.go @@ -0,0 +1,45 @@ +package sys + +import ( + "io" + "io/fs" + "os" +) + +// UnwrapOSError returns an Errno or zero if the input is nil. +func UnwrapOSError(err error) Errno { + if err == nil { + return 0 + } + err = underlyingError(err) + switch err { + case nil, io.EOF: + return 0 // EOF is not a Errno + case fs.ErrInvalid: + return EINVAL + case fs.ErrPermission: + return EPERM + case fs.ErrExist: + return EEXIST + case fs.ErrNotExist: + return ENOENT + case fs.ErrClosed: + return EBADF + } + return errorToErrno(err) +} + +// underlyingError returns the underlying error if a well-known OS error type. +// +// This impl is basically the same as os.underlyingError in os/error.go +func underlyingError(err error) error { + switch err := err.(type) { + case *os.PathError: + return err.Err + case *os.LinkError: + return err.Err + case *os.SyscallError: + return err.Err + } + return err +} diff --git a/vendor/github.com/tetratelabs/wazero/experimental/sys/file.go b/vendor/github.com/tetratelabs/wazero/experimental/sys/file.go new file mode 100644 index 00000000000..b6bfbcfebb6 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/sys/file.go @@ -0,0 +1,316 @@ +package sys + +import "github.com/tetratelabs/wazero/sys" + +// File is a writeable fs.File bridge backed by syscall functions needed for ABI +// including WASI. +// +// Implementations should embed UnimplementedFile for forward compatibility. Any +// unsupported method or parameter should return ENOSYS. +// +// # Errors +// +// All methods that can return an error return a Errno, which is zero +// on success. +// +// Restricting to Errno matches current WebAssembly host functions, +// which are constrained to well-known error codes. For example, WASI maps syscall +// errors to u32 numeric values. +// +// # Notes +// +// - You must call Close to avoid file resource conflicts. For example, +// Windows cannot delete the underlying directory while a handle to it +// remains open. +// - A writable filesystem abstraction is not yet implemented as of Go 1.20. +// See https://github.com/golang/go/issues/45757 +type File interface { + // Dev returns the device ID (Stat_t.Dev) of this file, zero if unknown or + // an error retrieving it. + // + // # Errors + // + // Possible errors are those from Stat, except ENOSYS should not + // be returned. Zero should be returned if there is no implementation. + // + // # Notes + // + // - Implementations should cache this result. + // - This combined with Ino can implement os.SameFile. + Dev() (uint64, Errno) + + // Ino returns the serial number (Stat_t.Ino) of this file, zero if unknown + // or an error retrieving it. + // + // # Errors + // + // Possible errors are those from Stat, except ENOSYS should not + // be returned. Zero should be returned if there is no implementation. + // + // # Notes + // + // - Implementations should cache this result. + // - This combined with Dev can implement os.SameFile. + Ino() (sys.Inode, Errno) + + // IsDir returns true if this file is a directory or an error there was an + // error retrieving this information. + // + // # Errors + // + // Possible errors are those from Stat, except ENOSYS should not + // be returned. false should be returned if there is no implementation. + // + // # Notes + // + // - Implementations should cache this result. + IsDir() (bool, Errno) + + // IsAppend returns true if the file was opened with O_APPEND, or + // SetAppend was successfully enabled on this file. + // + // # Notes + // + // - This might not match the underlying state of the file descriptor if + // the file was not opened via OpenFile. + IsAppend() bool + + // SetAppend toggles the append mode (O_APPEND) of this file. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EBADF: the file or directory was closed. + // + // # Notes + // + // - There is no `O_APPEND` for `fcntl` in POSIX, so implementations may + // have to re-open the underlying file to apply this. See + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html + SetAppend(enable bool) Errno + + // Stat is similar to syscall.Fstat. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EBADF: the file or directory was closed. + // + // # Notes + // + // - This is like syscall.Fstat and `fstatat` with `AT_FDCWD` in POSIX. + // See https://pubs.opengroup.org/onlinepubs/9699919799/functions/stat.html + // - A fs.FileInfo backed implementation sets atim, mtim and ctim to the + // same value. + // - Windows allows you to stat a closed directory. + Stat() (sys.Stat_t, Errno) + + // Read attempts to read all bytes in the file into `buf`, and returns the + // count read even on error. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EBADF: the file or directory was closed or not readable. + // - EISDIR: the file was a directory. + // + // # Notes + // + // - This is like io.Reader and `read` in POSIX, preferring semantics of + // io.Reader. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html + // - Unlike io.Reader, there is no io.EOF returned on end-of-file. To + // read the file completely, the caller must repeat until `n` is zero. + Read(buf []byte) (n int, errno Errno) + + // Pread attempts to read all bytes in the file into `p`, starting at the + // offset `off`, and returns the count read even on error. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EBADF: the file or directory was closed or not readable. + // - EINVAL: the offset was negative. + // - EISDIR: the file was a directory. + // + // # Notes + // + // - This is like io.ReaderAt and `pread` in POSIX, preferring semantics + // of io.ReaderAt. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/pread.html + // - Unlike io.ReaderAt, there is no io.EOF returned on end-of-file. To + // read the file completely, the caller must repeat until `n` is zero. + Pread(buf []byte, off int64) (n int, errno Errno) + + // Seek attempts to set the next offset for Read or Write and returns the + // resulting absolute offset or an error. + // + // # Parameters + // + // The `offset` parameters is interpreted in terms of `whence`: + // - io.SeekStart: relative to the start of the file, e.g. offset=0 sets + // the next Read or Write to the beginning of the file. + // - io.SeekCurrent: relative to the current offset, e.g. offset=16 sets + // the next Read or Write 16 bytes past the prior. + // - io.SeekEnd: relative to the end of the file, e.g. offset=-1 sets the + // next Read or Write to the last byte in the file. + // + // # Behavior when a directory + // + // The only supported use case for a directory is seeking to `offset` zero + // (`whence` = io.SeekStart). This should have the same behavior as + // os.File, which resets any internal state used by Readdir. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EBADF: the file or directory was closed or not readable. + // - EINVAL: the offset was negative. + // + // # Notes + // + // - This is like io.Seeker and `fseek` in POSIX, preferring semantics + // of io.Seeker. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fseek.html + Seek(offset int64, whence int) (newOffset int64, errno Errno) + + // Readdir reads the contents of the directory associated with file and + // returns a slice of up to n Dirent values in an arbitrary order. This is + // a stateful function, so subsequent calls return any next values. + // + // If n > 0, Readdir returns at most n entries or an error. + // If n <= 0, Readdir returns all remaining entries or an error. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EBADF: the file was closed or not a directory. + // - ENOENT: the directory could not be read (e.g. deleted). + // + // # Notes + // + // - This is like `Readdir` on os.File, but unlike `readdir` in POSIX. + // See https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html + // - Unlike os.File, there is no io.EOF returned on end-of-directory. To + // read the directory completely, the caller must repeat until the + // count read (`len(dirents)`) is less than `n`. + // - See /RATIONALE.md for design notes. + Readdir(n int) (dirents []Dirent, errno Errno) + + // Write attempts to write all bytes in `p` to the file, and returns the + // count written even on error. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EBADF: the file was closed, not writeable, or a directory. + // + // # Notes + // + // - This is like io.Writer and `write` in POSIX, preferring semantics of + // io.Writer. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html + Write(buf []byte) (n int, errno Errno) + + // Pwrite attempts to write all bytes in `p` to the file at the given + // offset `off`, and returns the count written even on error. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EBADF: the file or directory was closed or not writeable. + // - EINVAL: the offset was negative. + // - EISDIR: the file was a directory. + // + // # Notes + // + // - This is like io.WriterAt and `pwrite` in POSIX, preferring semantics + // of io.WriterAt. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/pwrite.html + Pwrite(buf []byte, off int64) (n int, errno Errno) + + // Truncate truncates a file to a specified length. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EBADF: the file or directory was closed. + // - EINVAL: the `size` is negative. + // - EISDIR: the file was a directory. + // + // # Notes + // + // - This is like syscall.Ftruncate and `ftruncate` in POSIX. See + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html + // - Windows does not error when calling Truncate on a closed file. + Truncate(size int64) Errno + + // Sync synchronizes changes to the file. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - EBADF: the file or directory was closed. + // + // # Notes + // + // - This is like syscall.Fsync and `fsync` in POSIX. See + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/fsync.html + // - This returns with no error instead of ENOSYS when + // unimplemented. This prevents fake filesystems from erring. + // - Windows does not error when calling Sync on a closed file. + Sync() Errno + + // Datasync synchronizes the data of a file. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - EBADF: the file or directory was closed. + // + // # Notes + // + // - This is like syscall.Fdatasync and `fdatasync` in POSIX. See + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdatasync.html + // - This returns with no error instead of ENOSYS when + // unimplemented. This prevents fake filesystems from erring. + // - As this is commonly missing, some implementations dispatch to Sync. + Datasync() Errno + + // Utimens set file access and modification times of this file, at + // nanosecond precision. + // + // # Parameters + // + // The `atim` and `mtim` parameters refer to access and modification time + // stamps as defined in sys.Stat_t. To retain one or the other, substitute + // it with the pseudo-timestamp UTIME_OMIT. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EBADF: the file or directory was closed. + // + // # Notes + // + // - This is like syscall.UtimesNano and `futimens` in POSIX. See + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html + // - Windows requires files to be open with O_RDWR, which means you + // cannot use this to update timestamps on a directory (EPERM). + Utimens(atim, mtim int64) Errno + + // Close closes the underlying file. + // + // A zero Errno is returned if unimplemented or success. + // + // # Notes + // + // - This is like syscall.Close and `close` in POSIX. See + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html + Close() Errno +} diff --git a/vendor/github.com/tetratelabs/wazero/experimental/sys/fs.go b/vendor/github.com/tetratelabs/wazero/experimental/sys/fs.go new file mode 100644 index 00000000000..87810510ab3 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/sys/fs.go @@ -0,0 +1,292 @@ +package sys + +import ( + "io/fs" + + "github.com/tetratelabs/wazero/sys" +) + +// FS is a writeable fs.FS bridge backed by syscall functions needed for ABI +// including WASI. +// +// Implementations should embed UnimplementedFS for forward compatibility. Any +// unsupported method or parameter should return ENO +// +// # Errors +// +// All methods that can return an error return a Errno, which is zero +// on success. +// +// Restricting to Errno matches current WebAssembly host functions, +// which are constrained to well-known error codes. For example, WASI maps syscall +// errors to u32 numeric values. +// +// # Notes +// +// A writable filesystem abstraction is not yet implemented as of Go 1.20. See +// https://github.com/golang/go/issues/45757 +type FS interface { + // OpenFile opens a file. It should be closed via Close on File. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EINVAL: `path` or `flag` is invalid. + // - EISDIR: the path was a directory, but flag included O_RDWR or + // O_WRONLY + // - ENOENT: `path` doesn't exist and `flag` doesn't contain O_CREAT. + // + // # Constraints on the returned file + // + // Implementations that can read flags should enforce them regardless of + // the type returned. For example, while os.File implements io.Writer, + // attempts to write to a directory or a file opened with O_RDONLY fail + // with a EBADF. + // + // Some implementations choose whether to enforce read-only opens, namely + // fs.FS. While fs.FS is supported (Adapt), wazero cannot runtime enforce + // open flags. Instead, we encourage good behavior and test our built-in + // implementations. + // + // # Notes + // + // - This is like os.OpenFile, except the path is relative to this file + // system, and Errno is returned instead of os.PathError. + // - Implications of permissions when O_CREAT are described in Chmod notes. + // - This is like `open` in POSIX. See + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html + OpenFile(path string, flag Oflag, perm fs.FileMode) (File, Errno) + + // Lstat gets file status without following symbolic links. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - ENOENT: `path` doesn't exist. + // + // # Notes + // + // - This is like syscall.Lstat, except the `path` is relative to this + // file system. + // - This is like `lstat` in POSIX. See + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/lstat.html + // - An fs.FileInfo backed implementation sets atim, mtim and ctim to the + // same value. + // - When the path is a symbolic link, the stat returned is for the link, + // not the file it refers to. + Lstat(path string) (sys.Stat_t, Errno) + + // Stat gets file status. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - ENOENT: `path` doesn't exist. + // + // # Notes + // + // - This is like syscall.Stat, except the `path` is relative to this + // file system. + // - This is like `stat` in POSIX. See + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/stat.html + // - An fs.FileInfo backed implementation sets atim, mtim and ctim to the + // same value. + // - When the path is a symbolic link, the stat returned is for the file + // it refers to. + Stat(path string) (sys.Stat_t, Errno) + + // Mkdir makes a directory. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EINVAL: `path` is invalid. + // - EEXIST: `path` exists and is a directory. + // - ENOTDIR: `path` exists and is a file. + // + // # Notes + // + // - This is like syscall.Mkdir, except the `path` is relative to this + // file system. + // - This is like `mkdir` in POSIX. See + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdir.html + // - Implications of permissions are described in Chmod notes. + Mkdir(path string, perm fs.FileMode) Errno + + // Chmod changes the mode of the file. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EINVAL: `path` is invalid. + // - ENOENT: `path` does not exist. + // + // # Notes + // + // - This is like syscall.Chmod, except the `path` is relative to this + // file system. + // - This is like `chmod` in POSIX. See + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/chmod.html + // - Windows ignores the execute bit, and any permissions come back as + // group and world. For example, chmod of 0400 reads back as 0444, and + // 0700 0666. Also, permissions on directories aren't supported at all. + Chmod(path string, perm fs.FileMode) Errno + + // Rename renames file or directory. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EINVAL: `from` or `to` is invalid. + // - ENOENT: `from` or `to` don't exist. + // - ENOTDIR: `from` is a directory and `to` exists as a file. + // - EISDIR: `from` is a file and `to` exists as a directory. + // - ENOTEMPTY: `both from` and `to` are existing directory, but + // `to` is not empty. + // + // # Notes + // + // - This is like syscall.Rename, except the paths are relative to this + // file system. + // - This is like `rename` in POSIX. See + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/rename.html + // - Windows doesn't let you overwrite an existing directory. + Rename(from, to string) Errno + + // Rmdir removes a directory. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EINVAL: `path` is invalid. + // - ENOENT: `path` doesn't exist. + // - ENOTDIR: `path` exists, but isn't a directory. + // - ENOTEMPTY: `path` exists, but isn't empty. + // + // # Notes + // + // - This is like syscall.Rmdir, except the `path` is relative to this + // file system. + // - This is like `rmdir` in POSIX. See + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/rmdir.html + // - As of Go 1.19, Windows maps ENOTDIR to ENOENT. + Rmdir(path string) Errno + + // Unlink removes a directory entry. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EINVAL: `path` is invalid. + // - ENOENT: `path` doesn't exist. + // - EISDIR: `path` exists, but is a directory. + // + // # Notes + // + // - This is like syscall.Unlink, except the `path` is relative to this + // file system. + // - This is like `unlink` in POSIX. See + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlink.html + // - On Windows, syscall.Unlink doesn't delete symlink to directory unlike other platforms. Implementations might + // want to combine syscall.RemoveDirectory with syscall.Unlink in order to delete such links on Windows. + // See https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-removedirectorya + Unlink(path string) Errno + + // Link creates a "hard" link from oldPath to newPath, in contrast to a + // soft link (via Symlink). + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EPERM: `oldPath` is invalid. + // - ENOENT: `oldPath` doesn't exist. + // - EISDIR: `newPath` exists, but is a directory. + // + // # Notes + // + // - This is like syscall.Link, except the `oldPath` is relative to this + // file system. + // - This is like `link` in POSIX. See + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/link.html + Link(oldPath, newPath string) Errno + + // Symlink creates a "soft" link from oldPath to newPath, in contrast to a + // hard link (via Link). + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EPERM: `oldPath` or `newPath` is invalid. + // - EEXIST: `newPath` exists. + // + // # Notes + // + // - This is like syscall.Symlink, except the `oldPath` is relative to + // this file system. + // - This is like `symlink` in POSIX. See + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlink.html + // - Only `newPath` is relative to this file system and `oldPath` is kept + // as-is. That is because the link is only resolved relative to the + // directory when dereferencing it (e.g. ReadLink). + // See https://github.com/bytecodealliance/cap-std/blob/v1.0.4/cap-std/src/fs/dir.rs#L404-L409 + // for how others implement this. + // - Symlinks in Windows requires `SeCreateSymbolicLinkPrivilege`. + // Otherwise, EPERM results. + // See https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-symbolic-links + Symlink(oldPath, linkName string) Errno + + // Readlink reads the contents of a symbolic link. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EINVAL: `path` is invalid. + // + // # Notes + // + // - This is like syscall.Readlink, except the path is relative to this + // filesystem. + // - This is like `readlink` in POSIX. See + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlink.html + // - On Windows, the path separator is different from other platforms, + // but to provide consistent results to Wasm, this normalizes to a "/" + // separator. + Readlink(path string) (string, Errno) + + // Utimens set file access and modification times on a path relative to + // this file system, at nanosecond precision. + // + // # Parameters + // + // If the path is a symbolic link, the target of expanding that link is + // updated. + // + // The `atim` and `mtim` parameters refer to access and modification time + // stamps as defined in sys.Stat_t. To retain one or the other, substitute + // it with the pseudo-timestamp UTIME_OMIT. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EINVAL: `path` is invalid. + // - EEXIST: `path` exists and is a directory. + // - ENOTDIR: `path` exists and is a file. + // + // # Notes + // + // - This is like syscall.UtimesNano and `utimensat` with `AT_FDCWD` in + // POSIX. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html + Utimens(path string, atim, mtim int64) Errno +} diff --git a/vendor/github.com/tetratelabs/wazero/experimental/sys/oflag.go b/vendor/github.com/tetratelabs/wazero/experimental/sys/oflag.go new file mode 100644 index 00000000000..39ebd378f65 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/sys/oflag.go @@ -0,0 +1,70 @@ +package sys + +// Oflag are flags used for FS.OpenFile. Values, including zero, should not be +// interpreted numerically. Instead, use by constants prefixed with 'O_' with +// special casing noted below. +// +// # Notes +// +// - O_RDONLY, O_RDWR and O_WRONLY are mutually exclusive, while the other +// flags can coexist bitwise. +// - This is like `flag` in os.OpenFile and `oflag` in POSIX. See +// https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html +type Oflag uint32 + +// This is a subset of oflags to reduce implementation burden. `wasip1` splits +// these across `oflags` and `fdflags`. We can't rely on the Go `os` package, +// as it is missing some values. Any flags added will be defined in POSIX +// order, as needed by functions that explicitly document accepting them. +// +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-oflags-flagsu16 +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fdflags-flagsu16 +const ( + // O_RDONLY is like os.O_RDONLY + O_RDONLY Oflag = iota + + // O_RDWR is like os.O_RDWR + O_RDWR + + // O_WRONLY is like os.O_WRONLY + O_WRONLY + + // Define bitflags as they are in POSIX `open`: alphabetically + + // O_APPEND is like os.O_APPEND + O_APPEND Oflag = 1 << iota + + // O_CREAT is link os.O_CREATE + O_CREAT + + // O_DIRECTORY is defined on some platforms as syscall.O_DIRECTORY. + // + // Note: This ensures that the opened file is a directory. Those emulating + // on platforms that don't support the O_DIRECTORY, can double-check the + // result with File.IsDir (or stat) and err if not a directory. + O_DIRECTORY + + // O_DSYNC is defined on some platforms as syscall.O_DSYNC. + O_DSYNC + + // O_EXCL is defined on some platforms as syscall.O_EXCL. + O_EXCL + + // O_NOFOLLOW is defined on some platforms as syscall.O_NOFOLLOW. + // + // Note: This allows programs to ensure that if the opened file is a + // symbolic link, the link itself is opened instead of its target. + O_NOFOLLOW + + // O_NONBLOCK is defined on some platforms as syscall.O_NONBLOCK. + O_NONBLOCK + + // O_RSYNC is defined on some platforms as syscall.O_RSYNC. + O_RSYNC + + // O_SYNC is defined on some platforms as syscall.O_SYNC. + O_SYNC + + // O_TRUNC is defined on some platforms as syscall.O_TRUNC. + O_TRUNC +) diff --git a/vendor/github.com/tetratelabs/wazero/experimental/sys/syscall_errno.go b/vendor/github.com/tetratelabs/wazero/experimental/sys/syscall_errno.go new file mode 100644 index 00000000000..86808cc741b --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/sys/syscall_errno.go @@ -0,0 +1,106 @@ +//go:build !(plan9 || aix) + +package sys + +import "syscall" + +func syscallToErrno(err error) (Errno, bool) { + errno, ok := err.(syscall.Errno) + if !ok { + return 0, false + } + switch errno { + case 0: + return 0, true + case syscall.EACCES: + return EACCES, true + case syscall.EAGAIN: + return EAGAIN, true + case syscall.EBADF: + return EBADF, true + case syscall.EEXIST: + return EEXIST, true + case syscall.EFAULT: + return EFAULT, true + case syscall.EINTR: + return EINTR, true + case syscall.EINVAL: + return EINVAL, true + case syscall.EIO: + return EIO, true + case syscall.EISDIR: + return EISDIR, true + case syscall.ELOOP: + return ELOOP, true + case syscall.ENAMETOOLONG: + return ENAMETOOLONG, true + case syscall.ENOENT: + return ENOENT, true + case syscall.ENOSYS: + return ENOSYS, true + case syscall.ENOTDIR: + return ENOTDIR, true + case syscall.ERANGE: + return ERANGE, true + case syscall.ENOTEMPTY: + return ENOTEMPTY, true + case syscall.ENOTSOCK: + return ENOTSOCK, true + case syscall.ENOTSUP: + return ENOTSUP, true + case syscall.EPERM: + return EPERM, true + case syscall.EROFS: + return EROFS, true + default: + return EIO, true + } +} + +// Unwrap is a convenience for runtime.GOOS which define syscall.Errno. +func (e Errno) Unwrap() error { + switch e { + case 0: + return nil + case EACCES: + return syscall.EACCES + case EAGAIN: + return syscall.EAGAIN + case EBADF: + return syscall.EBADF + case EEXIST: + return syscall.EEXIST + case EFAULT: + return syscall.EFAULT + case EINTR: + return syscall.EINTR + case EINVAL: + return syscall.EINVAL + case EIO: + return syscall.EIO + case EISDIR: + return syscall.EISDIR + case ELOOP: + return syscall.ELOOP + case ENAMETOOLONG: + return syscall.ENAMETOOLONG + case ENOENT: + return syscall.ENOENT + case ENOSYS: + return syscall.ENOSYS + case ENOTDIR: + return syscall.ENOTDIR + case ENOTEMPTY: + return syscall.ENOTEMPTY + case ENOTSOCK: + return syscall.ENOTSOCK + case ENOTSUP: + return syscall.ENOTSUP + case EPERM: + return syscall.EPERM + case EROFS: + return syscall.EROFS + default: + return syscall.EIO + } +} diff --git a/vendor/github.com/tetratelabs/wazero/experimental/sys/syscall_errno_notwindows.go b/vendor/github.com/tetratelabs/wazero/experimental/sys/syscall_errno_notwindows.go new file mode 100644 index 00000000000..8a88ed7658d --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/sys/syscall_errno_notwindows.go @@ -0,0 +1,13 @@ +//go:build !windows + +package sys + +func errorToErrno(err error) Errno { + if errno, ok := err.(Errno); ok { + return errno + } + if errno, ok := syscallToErrno(err); ok { + return errno + } + return EIO +} diff --git a/vendor/github.com/tetratelabs/wazero/experimental/sys/syscall_errno_unsupported.go b/vendor/github.com/tetratelabs/wazero/experimental/sys/syscall_errno_unsupported.go new file mode 100644 index 00000000000..1c6d423d06c --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/sys/syscall_errno_unsupported.go @@ -0,0 +1,7 @@ +//go:build plan9 || aix + +package sys + +func syscallToErrno(err error) (Errno, bool) { + return 0, false +} diff --git a/vendor/github.com/tetratelabs/wazero/experimental/sys/syscall_errno_windows.go b/vendor/github.com/tetratelabs/wazero/experimental/sys/syscall_errno_windows.go new file mode 100644 index 00000000000..8d9486683c3 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/sys/syscall_errno_windows.go @@ -0,0 +1,37 @@ +package sys + +import "golang.org/x/sys/windows" + +func errorToErrno(err error) Errno { + switch err := err.(type) { + case Errno: + return err + case windows.Errno: + // Note: In windows, _ERROR_PATH_NOT_FOUND(0x3) maps to syscall.ENOTDIR + switch err { + case windows.ERROR_ALREADY_EXISTS, windows.ERROR_FILE_EXISTS: + return EEXIST + case windows.ERROR_DIRECTORY: + // ERROR_DIRECTORY is returned by syscall.Rmdir. + return ENOTDIR + case windows.ERROR_DIR_NOT_EMPTY: + return ENOTEMPTY + case windows.ERROR_INVALID_HANDLE, windows.WSAENOTSOCK, windows.ERROR_ACCESS_DENIED: + // WSAENOTSOCK is returned by winsock_select when a given handle is not a socket. + // POSIX read and write functions expect EBADF, not EACCES when not + // open for reading or writing. + return EBADF + case windows.ERROR_PRIVILEGE_NOT_HELD: + return EPERM + case windows.ERROR_NEGATIVE_SEEK, windows.ERROR_NOT_A_REPARSE_POINT, windows.ERROR_INVALID_NAME: + // ERROR_NEGATIVE_SEEK is returned by os.Truncate. + // ERROR_NOT_A_REPARSE_POINT is returned by os.Readlink. + // ERROR_INVALID_NAME is returned by open when a file path has a trailing slash. + return EINVAL + } + errno, _ := syscallToErrno(err) + return errno + default: + return EIO + } +} diff --git a/vendor/github.com/tetratelabs/wazero/experimental/sys/time.go b/vendor/github.com/tetratelabs/wazero/experimental/sys/time.go new file mode 100644 index 00000000000..4f3e01fefbc --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/sys/time.go @@ -0,0 +1,10 @@ +package sys + +import "math" + +// UTIME_OMIT is a special constant for use in updating times via FS.Utimens +// or File.Utimens. When used for atim or mtim, the value is retained. +// +// Note: This may be implemented via a stat when the underlying filesystem +// does not support this value. +const UTIME_OMIT int64 = math.MinInt64 diff --git a/vendor/github.com/tetratelabs/wazero/experimental/sys/unimplemented.go b/vendor/github.com/tetratelabs/wazero/experimental/sys/unimplemented.go new file mode 100644 index 00000000000..d853d9e8f4e --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/experimental/sys/unimplemented.go @@ -0,0 +1,160 @@ +package sys + +import ( + "io/fs" + + "github.com/tetratelabs/wazero/sys" +) + +// UnimplementedFS is an FS that returns ENOSYS for all functions, +// This should be embedded to have forward compatible implementations. +type UnimplementedFS struct{} + +// OpenFile implements FS.OpenFile +func (UnimplementedFS) OpenFile(path string, flag Oflag, perm fs.FileMode) (File, Errno) { + return nil, ENOSYS +} + +// Lstat implements FS.Lstat +func (UnimplementedFS) Lstat(path string) (sys.Stat_t, Errno) { + return sys.Stat_t{}, ENOSYS +} + +// Stat implements FS.Stat +func (UnimplementedFS) Stat(path string) (sys.Stat_t, Errno) { + return sys.Stat_t{}, ENOSYS +} + +// Readlink implements FS.Readlink +func (UnimplementedFS) Readlink(path string) (string, Errno) { + return "", ENOSYS +} + +// Mkdir implements FS.Mkdir +func (UnimplementedFS) Mkdir(path string, perm fs.FileMode) Errno { + return ENOSYS +} + +// Chmod implements FS.Chmod +func (UnimplementedFS) Chmod(path string, perm fs.FileMode) Errno { + return ENOSYS +} + +// Rename implements FS.Rename +func (UnimplementedFS) Rename(from, to string) Errno { + return ENOSYS +} + +// Rmdir implements FS.Rmdir +func (UnimplementedFS) Rmdir(path string) Errno { + return ENOSYS +} + +// Link implements FS.Link +func (UnimplementedFS) Link(_, _ string) Errno { + return ENOSYS +} + +// Symlink implements FS.Symlink +func (UnimplementedFS) Symlink(_, _ string) Errno { + return ENOSYS +} + +// Unlink implements FS.Unlink +func (UnimplementedFS) Unlink(path string) Errno { + return ENOSYS +} + +// Utimens implements FS.Utimens +func (UnimplementedFS) Utimens(path string, atim, mtim int64) Errno { + return ENOSYS +} + +// UnimplementedFile is a File that returns ENOSYS for all functions, +// except where no-op are otherwise documented. +// +// This should be embedded to have forward compatible implementations. +type UnimplementedFile struct{} + +// Dev implements File.Dev +func (UnimplementedFile) Dev() (uint64, Errno) { + return 0, 0 +} + +// Ino implements File.Ino +func (UnimplementedFile) Ino() (sys.Inode, Errno) { + return 0, 0 +} + +// IsDir implements File.IsDir +func (UnimplementedFile) IsDir() (bool, Errno) { + return false, 0 +} + +// IsAppend implements File.IsAppend +func (UnimplementedFile) IsAppend() bool { + return false +} + +// SetAppend implements File.SetAppend +func (UnimplementedFile) SetAppend(bool) Errno { + return ENOSYS +} + +// Stat implements File.Stat +func (UnimplementedFile) Stat() (sys.Stat_t, Errno) { + return sys.Stat_t{}, ENOSYS +} + +// Read implements File.Read +func (UnimplementedFile) Read([]byte) (int, Errno) { + return 0, ENOSYS +} + +// Pread implements File.Pread +func (UnimplementedFile) Pread([]byte, int64) (int, Errno) { + return 0, ENOSYS +} + +// Seek implements File.Seek +func (UnimplementedFile) Seek(int64, int) (int64, Errno) { + return 0, ENOSYS +} + +// Readdir implements File.Readdir +func (UnimplementedFile) Readdir(int) (dirents []Dirent, errno Errno) { + return nil, ENOSYS +} + +// Write implements File.Write +func (UnimplementedFile) Write([]byte) (int, Errno) { + return 0, ENOSYS +} + +// Pwrite implements File.Pwrite +func (UnimplementedFile) Pwrite([]byte, int64) (int, Errno) { + return 0, ENOSYS +} + +// Truncate implements File.Truncate +func (UnimplementedFile) Truncate(int64) Errno { + return ENOSYS +} + +// Sync implements File.Sync +func (UnimplementedFile) Sync() Errno { + return 0 // not ENOSYS +} + +// Datasync implements File.Datasync +func (UnimplementedFile) Datasync() Errno { + return 0 // not ENOSYS +} + +// Utimens implements File.Utimens +func (UnimplementedFile) Utimens(int64, int64) Errno { + return ENOSYS +} + +// Close implements File.Close +func (UnimplementedFile) Close() (errno Errno) { return } diff --git a/vendor/github.com/tetratelabs/wazero/fsconfig.go b/vendor/github.com/tetratelabs/wazero/fsconfig.go new file mode 100644 index 00000000000..c21b6e80b9f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/fsconfig.go @@ -0,0 +1,213 @@ +package wazero + +import ( + "io/fs" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/sys" + "github.com/tetratelabs/wazero/internal/sysfs" +) + +// FSConfig configures filesystem paths the embedding host allows the wasm +// guest to access. Unconfigured paths are not allowed, so functions like +// `path_open` result in unsupported errors (e.g. syscall.ENOSYS). +// +// # Guest Path +// +// `guestPath` is the name of the path the guest should use a filesystem for, or +// empty for any files. +// +// All `guestPath` paths are normalized, specifically removing any leading or +// trailing slashes. This means "/", "./" or "." all coerce to empty "". +// +// Multiple `guestPath` values can be configured, but the last longest match +// wins. For example, if "tmp", then "" were added, a request to open +// "tmp/foo.txt" use the filesystem associated with "tmp" even though a wider +// path, "" (all files), was added later. +// +// A `guestPath` of "." coerces to the empty string "" because the current +// directory is handled by the guest. In other words, the guest resolves ites +// current directory prior to requesting files. +// +// More notes on `guestPath` +// - Working directories are typically tracked in wasm, though possible some +// relative paths are requested. For example, TinyGo may attempt to resolve +// a path "../.." in unit tests. +// - Zig uses the first path name it sees as the initial working directory of +// the process. +// +// # Scope +// +// Configuration here is module instance scoped. This means you can use the +// same configuration for multiple calls to Runtime.InstantiateModule. Each +// module will have a different file descriptor table. Any errors accessing +// resources allowed here are deferred to instantiation time of each module. +// +// Any host resources present at the time of configuration, but deleted before +// Runtime.InstantiateModule will trap/panic when the guest wasm initializes or +// calls functions like `fd_read`. +// +// # Windows +// +// While wazero supports Windows as a platform, all known compilers use POSIX +// conventions at runtime. For example, even when running on Windows, paths +// used by wasm are separated by forward slash (/), not backslash (\). +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +// - FSConfig is immutable. Each WithXXX function returns a new instance +// including the corresponding change. +// - RATIONALE.md includes design background and relationship to WebAssembly +// System Interfaces (WASI). +type FSConfig interface { + // WithDirMount assigns a directory at `dir` to any paths beginning at + // `guestPath`. + // + // For example, `dirPath` as / (or c:\ in Windows), makes the entire host + // volume writeable to the path on the guest. The `guestPath` is always a + // POSIX style path, slash (/) delimited, even if run on Windows. + // + // If the same `guestPath` was assigned before, this overrides its value, + // retaining the original precedence. See the documentation of FSConfig for + // more details on `guestPath`. + // + // # Isolation + // + // The guest will have full access to this directory including escaping it + // via relative path lookups like "../../". Full access includes operations + // such as creating or deleting files, limited to any host level access + // controls. + // + // # os.DirFS + // + // This configuration optimizes for WASI compatibility which is sometimes + // at odds with the behavior of os.DirFS. Hence, this will not behave + // exactly the same as os.DirFS. See /RATIONALE.md for more. + WithDirMount(dir, guestPath string) FSConfig + + // WithReadOnlyDirMount assigns a directory at `dir` to any paths + // beginning at `guestPath`. + // + // This is the same as WithDirMount except only read operations are + // permitted. However, escaping the directory via relative path lookups + // like "../../" is still allowed. + WithReadOnlyDirMount(dir, guestPath string) FSConfig + + // WithFSMount assigns a fs.FS file system for any paths beginning at + // `guestPath`. + // + // If the same `guestPath` was assigned before, this overrides its value, + // retaining the original precedence. See the documentation of FSConfig for + // more details on `guestPath`. + // + // # Isolation + // + // fs.FS does not restrict the ability to overwrite returned files via + // io.Writer. Moreover, os.DirFS documentation includes important notes + // about isolation, which also applies to fs.Sub. As of Go 1.19, the + // built-in file-systems are not jailed (chroot). See + // https://github.com/golang/go/issues/42322 + // + // # os.DirFS + // + // Due to limited control and functionality available in os.DirFS, we + // advise using WithDirMount instead. There will be behavior differences + // between os.DirFS and WithDirMount, as the latter biases towards what's + // expected from WASI implementations. + // + // # Custom fs.FileInfo + // + // The underlying implementation supports data not usually in fs.FileInfo + // when `info.Sys` returns *sys.Stat_t. For example, a custom fs.FS can use + // this approach to generate or mask sys.Inode data. Such a filesystem + // needs to decorate any functions that can return fs.FileInfo: + // + // - `Stat` as defined on `fs.File` (always) + // - `Readdir` as defined on `os.File` (if defined) + // + // See sys.NewStat_t for examples. + WithFSMount(fs fs.FS, guestPath string) FSConfig +} + +type fsConfig struct { + // fs are the currently configured filesystems. + fs []experimentalsys.FS + // guestPaths are the user-supplied names of the filesystems, retained for + // error messages and fmt.Stringer. + guestPaths []string + // guestPathToFS are the normalized paths to the currently configured + // filesystems, used for de-duplicating. + guestPathToFS map[string]int +} + +// NewFSConfig returns a FSConfig that can be used for configuring module instantiation. +func NewFSConfig() FSConfig { + return &fsConfig{guestPathToFS: map[string]int{}} +} + +// clone makes a deep copy of this module config. +func (c *fsConfig) clone() *fsConfig { + ret := *c // copy except slice and maps which share a ref + ret.fs = make([]experimentalsys.FS, 0, len(c.fs)) + ret.fs = append(ret.fs, c.fs...) + ret.guestPaths = make([]string, 0, len(c.guestPaths)) + ret.guestPaths = append(ret.guestPaths, c.guestPaths...) + ret.guestPathToFS = make(map[string]int, len(c.guestPathToFS)) + for key, value := range c.guestPathToFS { + ret.guestPathToFS[key] = value + } + return &ret +} + +// WithDirMount implements FSConfig.WithDirMount +func (c *fsConfig) WithDirMount(dir, guestPath string) FSConfig { + return c.WithSysFSMount(sysfs.DirFS(dir), guestPath) +} + +// WithReadOnlyDirMount implements FSConfig.WithReadOnlyDirMount +func (c *fsConfig) WithReadOnlyDirMount(dir, guestPath string) FSConfig { + return c.WithSysFSMount(&sysfs.ReadFS{FS: sysfs.DirFS(dir)}, guestPath) +} + +// WithFSMount implements FSConfig.WithFSMount +func (c *fsConfig) WithFSMount(fs fs.FS, guestPath string) FSConfig { + var adapted experimentalsys.FS + if fs != nil { + adapted = &sysfs.AdaptFS{FS: fs} + } + return c.WithSysFSMount(adapted, guestPath) +} + +// WithSysFSMount implements sysfs.FSConfig +func (c *fsConfig) WithSysFSMount(fs experimentalsys.FS, guestPath string) FSConfig { + if _, ok := fs.(experimentalsys.UnimplementedFS); ok { + return c // don't add fake paths. + } + cleaned := sys.StripPrefixesAndTrailingSlash(guestPath) + ret := c.clone() + if i, ok := ret.guestPathToFS[cleaned]; ok { + ret.fs[i] = fs + ret.guestPaths[i] = guestPath + } else if fs != nil { + ret.guestPathToFS[cleaned] = len(ret.fs) + ret.fs = append(ret.fs, fs) + ret.guestPaths = append(ret.guestPaths, guestPath) + } + return ret +} + +// preopens returns the possible nil index-correlated preopened filesystems +// with guest paths. +func (c *fsConfig) preopens() ([]experimentalsys.FS, []string) { + preopenCount := len(c.fs) + if preopenCount == 0 { + return nil, nil + } + fs := make([]experimentalsys.FS, len(c.fs)) + copy(fs, c.fs) + guestPaths := make([]string, len(c.guestPaths)) + copy(guestPaths, c.guestPaths) + return fs, guestPaths +} diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/args.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/args.go new file mode 100644 index 00000000000..4c82e95e244 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/args.go @@ -0,0 +1,97 @@ +package wasi_snapshot_preview1 + +import ( + "context" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/wasip1" + "github.com/tetratelabs/wazero/internal/wasm" +) + +// argsGet is the WASI function named ArgsGetName that reads command-line +// argument data. +// +// # Parameters +// +// - argv: offset to begin writing argument offsets in uint32 little-endian +// encoding to api.Memory +// - argsSizesGet result argc * 4 bytes are written to this offset +// - argvBuf: offset to write the null terminated arguments to api.Memory +// - argsSizesGet result argv_len bytes are written to this offset +// +// Result (Errno) +// +// The return value is ErrnoSuccess except the following error conditions: +// - sys.EFAULT: there is not enough memory to write results +// +// For example, if argsSizesGet wrote argc=2 and argvLen=5 for arguments: +// "a" and "bc" parameters argv=7 and argvBuf=1, this function writes the below +// to api.Memory: +// +// argvLen uint32le uint32le +// +----------------+ +--------+ +--------+ +// | | | | | | +// []byte{?, 'a', 0, 'b', 'c', 0, ?, 1, 0, 0, 0, 3, 0, 0, 0, ?} +// argvBuf --^ ^ ^ +// argv --| | +// offset that begins "a" --+ | +// offset that begins "bc" --+ +// +// See argsSizesGet +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#args_get +// See https://en.wikipedia.org/wiki/Null-terminated_string +var argsGet = newHostFunc(wasip1.ArgsGetName, argsGetFn, []api.ValueType{i32, i32}, "argv", "argv_buf") + +func argsGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno { + sysCtx := mod.(*wasm.ModuleInstance).Sys + argv, argvBuf := uint32(params[0]), uint32(params[1]) + return writeOffsetsAndNullTerminatedValues(mod.Memory(), sysCtx.Args(), argv, argvBuf, sysCtx.ArgsSize()) +} + +// argsSizesGet is the WASI function named ArgsSizesGetName that reads +// command-line argument sizes. +// +// # Parameters +// +// - resultArgc: offset to write the argument count to api.Memory +// - resultArgvLen: offset to write the null-terminated argument length to +// api.Memory +// +// Result (Errno) +// +// The return value is ErrnoSuccess except the following error conditions: +// - sys.EFAULT: there is not enough memory to write results +// +// For example, if args are "a", "bc" and parameters resultArgc=1 and +// resultArgvLen=6, this function writes the below to api.Memory: +// +// uint32le uint32le +// +--------+ +--------+ +// | | | | +// []byte{?, 2, 0, 0, 0, ?, 5, 0, 0, 0, ?} +// resultArgc --^ ^ +// 2 args --+ | +// resultArgvLen --| +// len([]byte{'a',0,'b',c',0}) --+ +// +// See argsGet +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#args_sizes_get +// See https://en.wikipedia.org/wiki/Null-terminated_string +var argsSizesGet = newHostFunc(wasip1.ArgsSizesGetName, argsSizesGetFn, []api.ValueType{i32, i32}, "result.argc", "result.argv_len") + +func argsSizesGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno { + sysCtx := mod.(*wasm.ModuleInstance).Sys + mem := mod.Memory() + resultArgc, resultArgvLen := uint32(params[0]), uint32(params[1]) + + // argc and argv_len offsets are not necessarily sequential, so we have to + // write them independently. + if !mem.WriteUint32Le(resultArgc, uint32(len(sysCtx.Args()))) { + return sys.EFAULT + } + if !mem.WriteUint32Le(resultArgvLen, sysCtx.ArgsSize()) { + return sys.EFAULT + } + return 0 +} diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/clock.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/clock.go new file mode 100644 index 00000000000..31af9107187 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/clock.go @@ -0,0 +1,116 @@ +package wasi_snapshot_preview1 + +import ( + "context" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/wasip1" + "github.com/tetratelabs/wazero/internal/wasm" +) + +// clockResGet is the WASI function named ClockResGetName that returns the +// resolution of time values returned by clockTimeGet. +// +// # Parameters +// +// - id: clock ID to use +// - resultResolution: offset to write the resolution to api.Memory +// - the resolution is an uint64 little-endian encoding +// +// Result (Errno) +// +// The return value is 0 except the following error conditions: +// - sys.ENOTSUP: the clock ID is not supported. +// - sys.EINVAL: the clock ID is invalid. +// - sys.EFAULT: there is not enough memory to write results +// +// For example, if the resolution is 100ns, this function writes the below to +// api.Memory: +// +// uint64le +// +-------------------------------------+ +// | | +// []byte{?, 0x64, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ?} +// resultResolution --^ +// +// Note: This is similar to `clock_getres` in POSIX. +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_res_getid-clockid---errno-timestamp +// See https://linux.die.net/man/3/clock_getres +var clockResGet = newHostFunc(wasip1.ClockResGetName, clockResGetFn, []api.ValueType{i32, i32}, "id", "result.resolution") + +func clockResGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno { + sysCtx := mod.(*wasm.ModuleInstance).Sys + id, resultResolution := uint32(params[0]), uint32(params[1]) + + var resolution uint64 // ns + switch id { + case wasip1.ClockIDRealtime: + resolution = uint64(sysCtx.WalltimeResolution()) + case wasip1.ClockIDMonotonic: + resolution = uint64(sysCtx.NanotimeResolution()) + default: + return sys.EINVAL + } + + if !mod.Memory().WriteUint64Le(resultResolution, resolution) { + return sys.EFAULT + } + return 0 +} + +// clockTimeGet is the WASI function named ClockTimeGetName that returns +// the time value of a name (time.Now). +// +// # Parameters +// +// - id: clock ID to use +// - precision: maximum lag (exclusive) that the returned time value may have, +// compared to its actual value +// - resultTimestamp: offset to write the timestamp to api.Memory +// - the timestamp is epoch nanos encoded as a little-endian uint64 +// +// Result (Errno) +// +// The return value is 0 except the following error conditions: +// - sys.ENOTSUP: the clock ID is not supported. +// - sys.EINVAL: the clock ID is invalid. +// - sys.EFAULT: there is not enough memory to write results +// +// For example, if time.Now returned exactly midnight UTC 2022-01-01 +// (1640995200000000000), and parameters resultTimestamp=1, this function +// writes the below to api.Memory: +// +// uint64le +// +------------------------------------------+ +// | | +// []byte{?, 0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, ?} +// resultTimestamp --^ +// +// Note: This is similar to `clock_gettime` in POSIX. +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_time_getid-clockid-precision-timestamp---errno-timestamp +// See https://linux.die.net/man/3/clock_gettime +var clockTimeGet = newHostFunc(wasip1.ClockTimeGetName, clockTimeGetFn, []api.ValueType{i32, i64, i32}, "id", "precision", "result.timestamp") + +func clockTimeGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno { + sysCtx := mod.(*wasm.ModuleInstance).Sys + id := uint32(params[0]) + // TODO: precision is currently ignored. + // precision = params[1] + resultTimestamp := uint32(params[2]) + + var val int64 + switch id { + case wasip1.ClockIDRealtime: + val = sysCtx.WalltimeNanos() + case wasip1.ClockIDMonotonic: + val = sysCtx.Nanotime() + default: + return sys.EINVAL + } + + if !mod.Memory().WriteUint64Le(resultTimestamp, uint64(val)) { + return sys.EFAULT + } + return 0 +} diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/environ.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/environ.go new file mode 100644 index 00000000000..ec8df708a8a --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/environ.go @@ -0,0 +1,100 @@ +package wasi_snapshot_preview1 + +import ( + "context" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/wasip1" + "github.com/tetratelabs/wazero/internal/wasm" +) + +// environGet is the WASI function named EnvironGetName that reads +// environment variables. +// +// # Parameters +// +// - environ: offset to begin writing environment offsets in uint32 +// little-endian encoding to api.Memory +// - environSizesGet result environc * 4 bytes are written to this offset +// - environBuf: offset to write the null-terminated variables to api.Memory +// - the format is like os.Environ: null-terminated "key=val" entries +// - environSizesGet result environLen bytes are written to this offset +// +// Result (Errno) +// +// The return value is 0 except the following error conditions: +// - sys.EFAULT: there is not enough memory to write results +// +// For example, if environSizesGet wrote environc=2 and environLen=9 for +// environment variables: "a=b", "b=cd" and parameters environ=11 and +// environBuf=1, this function writes the below to api.Memory: +// +// environLen uint32le uint32le +// +------------------------------------+ +--------+ +--------+ +// | | | | | | +// []byte{?, 'a', '=', 'b', 0, 'b', '=', 'c', 'd', 0, ?, 1, 0, 0, 0, 5, 0, 0, 0, ?} +// environBuf --^ ^ ^ +// environ offset for "a=b" --+ | +// environ offset for "b=cd" --+ +// +// See environSizesGet +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#environ_get +// See https://en.wikipedia.org/wiki/Null-terminated_string +var environGet = newHostFunc(wasip1.EnvironGetName, environGetFn, []api.ValueType{i32, i32}, "environ", "environ_buf") + +func environGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno { + sysCtx := mod.(*wasm.ModuleInstance).Sys + environ, environBuf := uint32(params[0]), uint32(params[1]) + + return writeOffsetsAndNullTerminatedValues(mod.Memory(), sysCtx.Environ(), environ, environBuf, sysCtx.EnvironSize()) +} + +// environSizesGet is the WASI function named EnvironSizesGetName that +// reads environment variable sizes. +// +// # Parameters +// +// - resultEnvironc: offset to write the count of environment variables to +// api.Memory +// - resultEnvironvLen: offset to write the null-terminated environment +// variable length to api.Memory +// +// Result (Errno) +// +// The return value is 0 except the following error conditions: +// - sys.EFAULT: there is not enough memory to write results +// +// For example, if environ are "a=b","b=cd" and parameters resultEnvironc=1 and +// resultEnvironvLen=6, this function writes the below to api.Memory: +// +// uint32le uint32le +// +--------+ +--------+ +// | | | | +// []byte{?, 2, 0, 0, 0, ?, 9, 0, 0, 0, ?} +// resultEnvironc --^ ^ +// 2 variables --+ | +// resultEnvironvLen --| +// len([]byte{'a','=','b',0, | +// 'b','=','c','d',0}) --+ +// +// See environGet +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#environ_sizes_get +// and https://en.wikipedia.org/wiki/Null-terminated_string +var environSizesGet = newHostFunc(wasip1.EnvironSizesGetName, environSizesGetFn, []api.ValueType{i32, i32}, "result.environc", "result.environv_len") + +func environSizesGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno { + sysCtx := mod.(*wasm.ModuleInstance).Sys + mem := mod.Memory() + resultEnvironc, resultEnvironvLen := uint32(params[0]), uint32(params[1]) + + // environc and environv_len offsets are not necessarily sequential, so we + // have to write them independently. + if !mem.WriteUint32Le(resultEnvironc, uint32(len(sysCtx.Environ()))) { + return sys.EFAULT + } + if !mem.WriteUint32Le(resultEnvironvLen, sysCtx.EnvironSize()) { + return sys.EFAULT + } + return 0 +} diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/fs.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/fs.go new file mode 100644 index 00000000000..150f75cc16f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/fs.go @@ -0,0 +1,2010 @@ +package wasi_snapshot_preview1 + +import ( + "context" + "io" + "io/fs" + "math" + "path" + "strings" + "unsafe" + + "github.com/tetratelabs/wazero/api" + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + socketapi "github.com/tetratelabs/wazero/internal/sock" + "github.com/tetratelabs/wazero/internal/sys" + "github.com/tetratelabs/wazero/internal/wasip1" + "github.com/tetratelabs/wazero/internal/wasm" + sysapi "github.com/tetratelabs/wazero/sys" +) + +// fdAdvise is the WASI function named FdAdviseName which provides file +// advisory information on a file descriptor. +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_advisefd-fd-offset-filesize-len-filesize-advice-advice---errno +var fdAdvise = newHostFunc( + wasip1.FdAdviseName, fdAdviseFn, + []wasm.ValueType{i32, i64, i64, i32}, + "fd", "offset", "len", "advice", +) + +func fdAdviseFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fd := int32(params[0]) + _ = params[1] + _ = params[2] + advice := byte(params[3]) + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + _, ok := fsc.LookupFile(fd) + if !ok { + return experimentalsys.EBADF + } + + switch advice { + case wasip1.FdAdviceNormal, + wasip1.FdAdviceSequential, + wasip1.FdAdviceRandom, + wasip1.FdAdviceWillNeed, + wasip1.FdAdviceDontNeed, + wasip1.FdAdviceNoReuse: + default: + return experimentalsys.EINVAL + } + + // FdAdvice corresponds to posix_fadvise, but it can only be supported on linux. + // However, the purpose of the call is just to do best-effort optimization on OS kernels, + // so just making this noop rather than returning NoSup error makes sense and doesn't affect + // the semantics of Wasm applications. + // TODO: invoke posix_fadvise on linux, and partially on darwin. + // - https://gitlab.com/cznic/fileutil/-/blob/v1.1.2/fileutil_linux.go#L87-95 + // - https://github.com/bytecodealliance/system-interface/blob/62b97f9776b86235f318c3a6e308395a1187439b/src/fs/file_io_ext.rs#L430-L442 + return 0 +} + +// fdAllocate is the WASI function named FdAllocateName which forces the +// allocation of space in a file. +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_allocatefd-fd-offset-filesize-len-filesize---errno +var fdAllocate = newHostFunc( + wasip1.FdAllocateName, fdAllocateFn, + []wasm.ValueType{i32, i64, i64}, + "fd", "offset", "len", +) + +func fdAllocateFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fd := int32(params[0]) + offset := params[1] + length := params[2] + + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + f, ok := fsc.LookupFile(fd) + if !ok { + return experimentalsys.EBADF + } + + tail := int64(offset + length) + if tail < 0 { + return experimentalsys.EINVAL + } + + st, errno := f.File.Stat() + if errno != 0 { + return errno + } + + if st.Size >= tail { + return 0 // We already have enough space. + } + + return f.File.Truncate(tail) +} + +// fdClose is the WASI function named FdCloseName which closes a file +// descriptor. +// +// # Parameters +// +// - fd: file descriptor to close +// +// Result (Errno) +// +// The return value is 0 except the following error conditions: +// - sys.EBADF: the fd was not open. +// - sys.ENOTSUP: the fs was a pre-open +// +// Note: This is similar to `close` in POSIX. +// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_close +// and https://linux.die.net/man/3/close +var fdClose = newHostFunc(wasip1.FdCloseName, fdCloseFn, []api.ValueType{i32}, "fd") + +func fdCloseFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + fd := int32(params[0]) + + return fsc.CloseFile(fd) +} + +// fdDatasync is the WASI function named FdDatasyncName which synchronizes +// the data of a file to disk. +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_datasyncfd-fd---errno +var fdDatasync = newHostFunc(wasip1.FdDatasyncName, fdDatasyncFn, []api.ValueType{i32}, "fd") + +func fdDatasyncFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + fd := int32(params[0]) + + // Check to see if the file descriptor is available + if f, ok := fsc.LookupFile(fd); !ok { + return experimentalsys.EBADF + } else { + return f.File.Datasync() + } +} + +// fdFdstatGet is the WASI function named FdFdstatGetName which returns the +// attributes of a file descriptor. +// +// # Parameters +// +// - fd: file descriptor to get the fdstat attributes data +// - resultFdstat: offset to write the result fdstat data +// +// Result (Errno) +// +// The return value is 0 except the following error conditions: +// - sys.EBADF: `fd` is invalid +// - sys.EFAULT: `resultFdstat` points to an offset out of memory +// +// fdstat byte layout is 24-byte size, with the following fields: +// - fs_filetype 1 byte: the file type +// - fs_flags 2 bytes: the file descriptor flag +// - 5 pad bytes +// - fs_right_base 8 bytes: ignored as rights were removed from WASI. +// - fs_right_inheriting 8 bytes: ignored as rights were removed from WASI. +// +// For example, with a file corresponding with `fd` was a directory (=3) opened +// with `fd_read` right (=1) and no fs_flags (=0), parameter resultFdstat=1, +// this function writes the below to api.Memory: +// +// uint16le padding uint64le uint64le +// uint8 --+ +--+ +-----------+ +--------------------+ +--------------------+ +// | | | | | | | | | +// []byte{?, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0} +// resultFdstat --^ ^-- fs_flags ^-- fs_right_base ^-- fs_right_inheriting +// | +// +-- fs_filetype +// +// Note: fdFdstatGet returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as +// well as additional fields. +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fdstat +// and https://linux.die.net/man/3/fsync +var fdFdstatGet = newHostFunc(wasip1.FdFdstatGetName, fdFdstatGetFn, []api.ValueType{i32, i32}, "fd", "result.stat") + +// fdFdstatGetFn cannot currently use proxyResultParams because fdstat is larger +// than api.ValueTypeI64 (i64 == 8 bytes, but fdstat is 24). +func fdFdstatGetFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + fd, resultFdstat := int32(params[0]), uint32(params[1]) + + // Ensure we can write the fdstat + buf, ok := mod.Memory().Read(resultFdstat, 24) + if !ok { + return experimentalsys.EFAULT + } + + var fdflags uint16 + var st sysapi.Stat_t + var errno experimentalsys.Errno + f, ok := fsc.LookupFile(fd) + if !ok { + return experimentalsys.EBADF + } else if st, errno = f.File.Stat(); errno != 0 { + return errno + } else if f.File.IsAppend() { + fdflags |= wasip1.FD_APPEND + } + + if f.File.IsNonblock() { + fdflags |= wasip1.FD_NONBLOCK + } + + var fsRightsBase uint32 + var fsRightsInheriting uint32 + fileType := getExtendedWasiFiletype(f.File, st.Mode) + + switch fileType { + case wasip1.FILETYPE_DIRECTORY: + // To satisfy wasi-testsuite, we must advertise that directories cannot + // be given seek permission (RIGHT_FD_SEEK). + fsRightsBase = dirRightsBase + fsRightsInheriting = fileRightsBase | dirRightsBase + case wasip1.FILETYPE_CHARACTER_DEVICE: + // According to wasi-libc, + // > A tty is a character device that we can't seek or tell on. + // See https://github.com/WebAssembly/wasi-libc/blob/a6f871343313220b76009827ed0153586361c0d5/libc-bottom-half/sources/isatty.c#L13-L18 + fsRightsBase = fileRightsBase &^ wasip1.RIGHT_FD_SEEK &^ wasip1.RIGHT_FD_TELL + default: + fsRightsBase = fileRightsBase + } + + writeFdstat(buf, fileType, fdflags, fsRightsBase, fsRightsInheriting) + return 0 +} + +// isPreopenedStdio returns true if the FD is sys.FdStdin, sys.FdStdout or +// sys.FdStderr and pre-opened. This double check is needed in case the guest +// closes stdin and re-opens it with a random alternative file. +// +// Currently, we only support non-blocking mode for standard I/O streams. +// Non-blocking mode is rarely supported for regular files, and we don't +// yet have support for sockets, so we make a special case. +// +// Note: this to get or set FD_NONBLOCK, but skip FD_APPEND. Our current +// implementation can't set FD_APPEND, without re-opening files. As stdio are +// pre-opened, we don't know how to re-open them, neither should we close the +// underlying file. Later, we could add support for setting FD_APPEND, similar +// to SetNonblock. +func isPreopenedStdio(fd int32, f *sys.FileEntry) bool { + return fd <= sys.FdStderr && f.IsPreopen +} + +const fileRightsBase = wasip1.RIGHT_FD_DATASYNC | + wasip1.RIGHT_FD_READ | + wasip1.RIGHT_FD_SEEK | + wasip1.RIGHT_FDSTAT_SET_FLAGS | + wasip1.RIGHT_FD_SYNC | + wasip1.RIGHT_FD_TELL | + wasip1.RIGHT_FD_WRITE | + wasip1.RIGHT_FD_ADVISE | + wasip1.RIGHT_FD_ALLOCATE | + wasip1.RIGHT_FD_FILESTAT_GET | + wasip1.RIGHT_FD_FILESTAT_SET_SIZE | + wasip1.RIGHT_FD_FILESTAT_SET_TIMES | + wasip1.RIGHT_POLL_FD_READWRITE + +const dirRightsBase = wasip1.RIGHT_FD_DATASYNC | + wasip1.RIGHT_FDSTAT_SET_FLAGS | + wasip1.RIGHT_FD_SYNC | + wasip1.RIGHT_PATH_CREATE_DIRECTORY | + wasip1.RIGHT_PATH_CREATE_FILE | + wasip1.RIGHT_PATH_LINK_SOURCE | + wasip1.RIGHT_PATH_LINK_TARGET | + wasip1.RIGHT_PATH_OPEN | + wasip1.RIGHT_FD_READDIR | + wasip1.RIGHT_PATH_READLINK | + wasip1.RIGHT_PATH_RENAME_SOURCE | + wasip1.RIGHT_PATH_RENAME_TARGET | + wasip1.RIGHT_PATH_FILESTAT_GET | + wasip1.RIGHT_PATH_FILESTAT_SET_SIZE | + wasip1.RIGHT_PATH_FILESTAT_SET_TIMES | + wasip1.RIGHT_FD_FILESTAT_GET | + wasip1.RIGHT_FD_FILESTAT_SET_TIMES | + wasip1.RIGHT_PATH_SYMLINK | + wasip1.RIGHT_PATH_REMOVE_DIRECTORY | + wasip1.RIGHT_PATH_UNLINK_FILE + +func writeFdstat(buf []byte, fileType uint8, fdflags uint16, fsRightsBase, fsRightsInheriting uint32) { + b := (*[24]byte)(buf) + le.PutUint16(b[0:], uint16(fileType)) + le.PutUint16(b[2:], fdflags) + le.PutUint32(b[4:], 0) + le.PutUint64(b[8:], uint64(fsRightsBase)) + le.PutUint64(b[16:], uint64(fsRightsInheriting)) +} + +// fdFdstatSetFlags is the WASI function named FdFdstatSetFlagsName which +// adjusts the flags associated with a file descriptor. +var fdFdstatSetFlags = newHostFunc(wasip1.FdFdstatSetFlagsName, fdFdstatSetFlagsFn, []wasm.ValueType{i32, i32}, "fd", "flags") + +func fdFdstatSetFlagsFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fd, wasiFlag := int32(params[0]), uint16(params[1]) + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + // Currently we only support APPEND and NONBLOCK. + if wasip1.FD_DSYNC&wasiFlag != 0 || wasip1.FD_RSYNC&wasiFlag != 0 || wasip1.FD_SYNC&wasiFlag != 0 { + return experimentalsys.EINVAL + } + + if f, ok := fsc.LookupFile(fd); !ok { + return experimentalsys.EBADF + } else { + nonblock := wasip1.FD_NONBLOCK&wasiFlag != 0 + errno := f.File.SetNonblock(nonblock) + if errno != 0 { + return errno + } + if stat, err := f.File.Stat(); err == 0 && stat.Mode.IsRegular() { + // For normal files, proceed to apply an append flag. + append := wasip1.FD_APPEND&wasiFlag != 0 + return f.File.SetAppend(append) + } + } + + return 0 +} + +// fdFdstatSetRights will not be implemented as rights were removed from WASI. +// +// See https://github.com/bytecodealliance/wasmtime/pull/4666 +var fdFdstatSetRights = stubFunction( + wasip1.FdFdstatSetRightsName, + []wasm.ValueType{i32, i64, i64}, + "fd", "fs_rights_base", "fs_rights_inheriting", +) + +// fdFilestatGet is the WASI function named FdFilestatGetName which returns +// the stat attributes of an open file. +// +// # Parameters +// +// - fd: file descriptor to get the filestat attributes data for +// - resultFilestat: offset to write the result filestat data +// +// Result (Errno) +// +// The return value is 0 except the following error conditions: +// - sys.EBADF: `fd` is invalid +// - sys.EIO: could not stat `fd` on filesystem +// - sys.EFAULT: `resultFilestat` points to an offset out of memory +// +// filestat byte layout is 64-byte size, with the following fields: +// - dev 8 bytes: the device ID of device containing the file +// - ino 8 bytes: the file serial number +// - filetype 1 byte: the type of the file +// - 7 pad bytes +// - nlink 8 bytes: number of hard links to the file +// - size 8 bytes: for regular files, the file size in bytes. For symbolic links, the length in bytes of the pathname contained in the symbolic link +// - atim 8 bytes: ast data access timestamp +// - mtim 8 bytes: last data modification timestamp +// - ctim 8 bytes: ast file status change timestamp +// +// For example, with a regular file this function writes the below to api.Memory: +// +// uint8 --+ +// uint64le uint64le | padding uint64le uint64le uint64le uint64le uint64le +// +--------------------+ +--------------------+ | +-----------------+ +--------------------+ +-----------------------+ +----------------------------------+ +----------------------------------+ +----------------------------------+ +// | | | | | | | | | | | | | | | | | +// []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 117, 80, 0, 0, 0, 0, 0, 0, 160, 153, 212, 128, 110, 221, 35, 23, 160, 153, 212, 128, 110, 221, 35, 23, 160, 153, 212, 128, 110, 221, 35, 23} +// resultFilestat ^-- dev ^-- ino ^ ^-- nlink ^-- size ^-- atim ^-- mtim ^-- ctim +// | +// +-- filetype +// +// The following properties of filestat are not implemented: +// - dev: not supported by Golang FS +// - ino: not supported by Golang FS +// - nlink: not supported by Golang FS, we use 1 +// - atime: not supported by Golang FS, we use mtim for this +// - ctim: not supported by Golang FS, we use mtim for this +// +// Note: This is similar to `fstat` in POSIX. +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_getfd-fd---errno-filestat +// and https://linux.die.net/man/3/fstat +var fdFilestatGet = newHostFunc(wasip1.FdFilestatGetName, fdFilestatGetFn, []api.ValueType{i32, i32}, "fd", "result.filestat") + +// fdFilestatGetFn cannot currently use proxyResultParams because filestat is +// larger than api.ValueTypeI64 (i64 == 8 bytes, but filestat is 64). +func fdFilestatGetFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + return fdFilestatGetFunc(mod, int32(params[0]), uint32(params[1])) +} + +func fdFilestatGetFunc(mod api.Module, fd int32, resultBuf uint32) experimentalsys.Errno { + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + // Ensure we can write the filestat + buf, ok := mod.Memory().Read(resultBuf, 64) + if !ok { + return experimentalsys.EFAULT + } + + f, ok := fsc.LookupFile(fd) + if !ok { + return experimentalsys.EBADF + } + + st, errno := f.File.Stat() + if errno != 0 { + return errno + } + + filetype := getExtendedWasiFiletype(f.File, st.Mode) + return writeFilestat(buf, &st, filetype) +} + +func getExtendedWasiFiletype(file experimentalsys.File, fm fs.FileMode) (ftype uint8) { + ftype = getWasiFiletype(fm) + if ftype == wasip1.FILETYPE_UNKNOWN { + if _, ok := file.(socketapi.TCPSock); ok { + ftype = wasip1.FILETYPE_SOCKET_STREAM + } else if _, ok = file.(socketapi.TCPConn); ok { + ftype = wasip1.FILETYPE_SOCKET_STREAM + } + } + return +} + +func getWasiFiletype(fm fs.FileMode) uint8 { + switch { + case fm.IsRegular(): + return wasip1.FILETYPE_REGULAR_FILE + case fm.IsDir(): + return wasip1.FILETYPE_DIRECTORY + case fm&fs.ModeSymlink != 0: + return wasip1.FILETYPE_SYMBOLIC_LINK + case fm&fs.ModeDevice != 0: + // Unlike ModeDevice and ModeCharDevice, FILETYPE_CHARACTER_DEVICE and + // FILETYPE_BLOCK_DEVICE are set mutually exclusively. + if fm&fs.ModeCharDevice != 0 { + return wasip1.FILETYPE_CHARACTER_DEVICE + } + return wasip1.FILETYPE_BLOCK_DEVICE + default: // unknown + return wasip1.FILETYPE_UNKNOWN + } +} + +func writeFilestat(buf []byte, st *sysapi.Stat_t, ftype uint8) (errno experimentalsys.Errno) { + le.PutUint64(buf, st.Dev) + le.PutUint64(buf[8:], st.Ino) + le.PutUint64(buf[16:], uint64(ftype)) + le.PutUint64(buf[24:], st.Nlink) + le.PutUint64(buf[32:], uint64(st.Size)) + le.PutUint64(buf[40:], uint64(st.Atim)) + le.PutUint64(buf[48:], uint64(st.Mtim)) + le.PutUint64(buf[56:], uint64(st.Ctim)) + return +} + +// fdFilestatSetSize is the WASI function named FdFilestatSetSizeName which +// adjusts the size of an open file. +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_set_sizefd-fd-size-filesize---errno +var fdFilestatSetSize = newHostFunc(wasip1.FdFilestatSetSizeName, fdFilestatSetSizeFn, []wasm.ValueType{i32, i64}, "fd", "size") + +func fdFilestatSetSizeFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fd := int32(params[0]) + size := int64(params[1]) + + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + // Check to see if the file descriptor is available + if f, ok := fsc.LookupFile(fd); !ok { + return experimentalsys.EBADF + } else { + return f.File.Truncate(size) + } +} + +// fdFilestatSetTimes is the WASI function named functionFdFilestatSetTimes +// which adjusts the times of an open file. +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_set_timesfd-fd-atim-timestamp-mtim-timestamp-fst_flags-fstflags---errno +var fdFilestatSetTimes = newHostFunc( + wasip1.FdFilestatSetTimesName, fdFilestatSetTimesFn, + []wasm.ValueType{i32, i64, i64, i32}, + "fd", "atim", "mtim", "fst_flags", +) + +func fdFilestatSetTimesFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fd := int32(params[0]) + atim := int64(params[1]) + mtim := int64(params[2]) + fstFlags := uint16(params[3]) + + sys := mod.(*wasm.ModuleInstance).Sys + fsc := sys.FS() + + f, ok := fsc.LookupFile(fd) + if !ok { + return experimentalsys.EBADF + } + + atim, mtim, errno := toTimes(sys.WalltimeNanos, atim, mtim, fstFlags) + if errno != 0 { + return errno + } + + // Try to update the file timestamps by file-descriptor. + errno = f.File.Utimens(atim, mtim) + + // Fall back to path based, despite it being less precise. + switch errno { + case experimentalsys.EPERM, experimentalsys.ENOSYS: + errno = f.FS.Utimens(f.Name, atim, mtim) + } + + return errno +} + +func toTimes(walltime func() int64, atim, mtim int64, fstFlags uint16) (int64, int64, experimentalsys.Errno) { + // times[0] == atim, times[1] == mtim + + var nowTim int64 + + // coerce atim into a timespec + if set, now := fstFlags&wasip1.FstflagsAtim != 0, fstFlags&wasip1.FstflagsAtimNow != 0; set && now { + return 0, 0, experimentalsys.EINVAL + } else if set { + // atim is already correct + } else if now { + nowTim = walltime() + atim = nowTim + } else { + atim = experimentalsys.UTIME_OMIT + } + + // coerce mtim into a timespec + if set, now := fstFlags&wasip1.FstflagsMtim != 0, fstFlags&wasip1.FstflagsMtimNow != 0; set && now { + return 0, 0, experimentalsys.EINVAL + } else if set { + // mtim is already correct + } else if now { + if nowTim != 0 { + mtim = nowTim + } else { + mtim = walltime() + } + } else { + mtim = experimentalsys.UTIME_OMIT + } + return atim, mtim, 0 +} + +// fdPread is the WASI function named FdPreadName which reads from a file +// descriptor, without using and updating the file descriptor's offset. +// +// Except for handling offset, this implementation is identical to fdRead. +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_preadfd-fd-iovs-iovec_array-offset-filesize---errno-size +var fdPread = newHostFunc( + wasip1.FdPreadName, fdPreadFn, + []api.ValueType{i32, i32, i32, i64, i32}, + "fd", "iovs", "iovs_len", "offset", "result.nread", +) + +func fdPreadFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + return fdReadOrPread(mod, params, true) +} + +// fdPrestatGet is the WASI function named FdPrestatGetName which returns +// the prestat data of a file descriptor. +// +// # Parameters +// +// - fd: file descriptor to get the prestat +// - resultPrestat: offset to write the result prestat data +// +// Result (Errno) +// +// The return value is 0 except the following error conditions: +// - sys.EBADF: `fd` is invalid or the `fd` is not a pre-opened directory +// - sys.EFAULT: `resultPrestat` points to an offset out of memory +// +// prestat byte layout is 8 bytes, beginning with an 8-bit tag and 3 pad bytes. +// The only valid tag is `prestat_dir`, which is tag zero. This simplifies the +// byte layout to 4 empty bytes followed by the uint32le encoded path length. +// +// For example, the directory name corresponding with `fd` was "/tmp" and +// parameter resultPrestat=1, this function writes the below to api.Memory: +// +// padding uint32le +// uint8 --+ +-----+ +--------+ +// | | | | | +// []byte{?, 0, 0, 0, 0, 4, 0, 0, 0, ?} +// resultPrestat --^ ^ +// tag --+ | +// +-- size in bytes of the string "/tmp" +// +// See fdPrestatDirName and +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#prestat +var fdPrestatGet = newHostFunc(wasip1.FdPrestatGetName, fdPrestatGetFn, []api.ValueType{i32, i32}, "fd", "result.prestat") + +func fdPrestatGetFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + fd, resultPrestat := int32(params[0]), uint32(params[1]) + + name, errno := preopenPath(fsc, fd) + if errno != 0 { + return errno + } + + // Upper 32-bits are zero because... + // * Zero-value 8-bit tag, and 3-byte zero-value padding + prestat := uint64(len(name) << 32) + if !mod.Memory().WriteUint64Le(resultPrestat, prestat) { + return experimentalsys.EFAULT + } + return 0 +} + +// fdPrestatDirName is the WASI function named FdPrestatDirNameName which +// returns the path of the pre-opened directory of a file descriptor. +// +// # Parameters +// +// - fd: file descriptor to get the path of the pre-opened directory +// - path: offset in api.Memory to write the result path +// - pathLen: count of bytes to write to `path` +// - This should match the uint32le fdPrestatGet writes to offset +// `resultPrestat`+4 +// +// Result (Errno) +// +// The return value is 0 except the following error conditions: +// - sys.EBADF: `fd` is invalid +// - sys.EFAULT: `path` points to an offset out of memory +// - sys.ENAMETOOLONG: `pathLen` is longer than the actual length of the result +// +// For example, the directory name corresponding with `fd` was "/tmp" and +// # Parameters path=1 pathLen=4 (correct), this function will write the below to +// api.Memory: +// +// pathLen +// +--------------+ +// | | +// []byte{?, '/', 't', 'm', 'p', ?} +// path --^ +// +// See fdPrestatGet +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_prestat_dir_name +var fdPrestatDirName = newHostFunc( + wasip1.FdPrestatDirNameName, fdPrestatDirNameFn, + []api.ValueType{i32, i32, i32}, + "fd", "result.path", "result.path_len", +) + +func fdPrestatDirNameFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + fd, path, pathLen := int32(params[0]), uint32(params[1]), uint32(params[2]) + + name, errno := preopenPath(fsc, fd) + if errno != 0 { + return errno + } + + // Some runtimes may have another semantics. See /RATIONALE.md + if uint32(len(name)) < pathLen { + return experimentalsys.ENAMETOOLONG + } + + if !mod.Memory().Write(path, []byte(name)[:pathLen]) { + return experimentalsys.EFAULT + } + return 0 +} + +// fdPwrite is the WASI function named FdPwriteName which writes to a file +// descriptor, without using and updating the file descriptor's offset. +// +// Except for handling offset, this implementation is identical to fdWrite. +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_pwritefd-fd-iovs-ciovec_array-offset-filesize---errno-size +var fdPwrite = newHostFunc( + wasip1.FdPwriteName, fdPwriteFn, + []api.ValueType{i32, i32, i32, i64, i32}, + "fd", "iovs", "iovs_len", "offset", "result.nwritten", +) + +func fdPwriteFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + return fdWriteOrPwrite(mod, params, true) +} + +// fdRead is the WASI function named FdReadName which reads from a file +// descriptor. +// +// # Parameters +// +// - fd: an opened file descriptor to read data from +// - iovs: offset in api.Memory to read offset, size pairs representing where +// to write file data +// - Both offset and length are encoded as uint32le +// - iovsCount: count of memory offset, size pairs to read sequentially +// starting at iovs +// - resultNread: offset in api.Memory to write the number of bytes read +// +// Result (Errno) +// +// The return value is 0 except the following error conditions: +// - sys.EBADF: `fd` is invalid +// - sys.EFAULT: `iovs` or `resultNread` point to an offset out of memory +// - sys.EIO: a file system error +// +// For example, this function needs to first read `iovs` to determine where +// to write contents. If parameters iovs=1 iovsCount=2, this function reads two +// offset/length pairs from api.Memory: +// +// iovs[0] iovs[1] +// +---------------------+ +--------------------+ +// | uint32le uint32le| |uint32le uint32le| +// +---------+ +--------+ +--------+ +--------+ +// | | | | | | | | +// []byte{?, 18, 0, 0, 0, 4, 0, 0, 0, 23, 0, 0, 0, 2, 0, 0, 0, ?... } +// iovs --^ ^ ^ ^ +// | | | | +// offset --+ length --+ offset --+ length --+ +// +// If the contents of the `fd` parameter was "wazero" (6 bytes) and parameter +// resultNread=26, this function writes the below to api.Memory: +// +// iovs[0].length iovs[1].length +// +--------------+ +----+ uint32le +// | | | | +--------+ +// []byte{ 0..16, ?, 'w', 'a', 'z', 'e', ?, 'r', 'o', ?, 6, 0, 0, 0 } +// iovs[0].offset --^ ^ ^ +// iovs[1].offset --+ | +// resultNread --+ +// +// Note: This is similar to `readv` in POSIX. https://linux.die.net/man/3/readv +// +// See fdWrite +// and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_read +var fdRead = newHostFunc( + wasip1.FdReadName, fdReadFn, + []api.ValueType{i32, i32, i32, i32}, + "fd", "iovs", "iovs_len", "result.nread", +) + +// preader tracks an offset across multiple reads. +type preader struct { + f experimentalsys.File + offset int64 +} + +// Read implements the same function as documented on sys.File. +func (w *preader) Read(buf []byte) (n int, errno experimentalsys.Errno) { + if len(buf) == 0 { + return 0, 0 // less overhead on zero-length reads. + } + + n, err := w.f.Pread(buf, w.offset) + w.offset += int64(n) + return n, err +} + +func fdReadFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + return fdReadOrPread(mod, params, false) +} + +func fdReadOrPread(mod api.Module, params []uint64, isPread bool) experimentalsys.Errno { + mem := mod.Memory() + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + fd := int32(params[0]) + iovs := uint32(params[1]) + iovsCount := uint32(params[2]) + + var resultNread uint32 + var reader func(buf []byte) (n int, errno experimentalsys.Errno) + if f, ok := fsc.LookupFile(fd); !ok { + return experimentalsys.EBADF + } else if isPread { + offset := int64(params[3]) + reader = (&preader{f: f.File, offset: offset}).Read + resultNread = uint32(params[4]) + } else { + reader = f.File.Read + resultNread = uint32(params[3]) + } + + nread, errno := readv(mem, iovs, iovsCount, reader) + if errno != 0 { + return errno + } + if !mem.WriteUint32Le(resultNread, nread) { + return experimentalsys.EFAULT + } else { + return 0 + } +} + +func readv(mem api.Memory, iovs uint32, iovsCount uint32, reader func(buf []byte) (nread int, errno experimentalsys.Errno)) (uint32, experimentalsys.Errno) { + var nread uint32 + iovsStop := iovsCount << 3 // iovsCount * 8 + iovsBuf, ok := mem.Read(iovs, iovsStop) + if !ok { + return 0, experimentalsys.EFAULT + } + + for iovsPos := uint32(0); iovsPos < iovsStop; iovsPos += 8 { + offset := le.Uint32(iovsBuf[iovsPos:]) + l := le.Uint32(iovsBuf[iovsPos+4:]) + + if l == 0 { // A zero length iovec could be ahead of another. + continue + } + + b, ok := mem.Read(offset, l) + if !ok { + return 0, experimentalsys.EFAULT + } + + n, errno := reader(b) + nread += uint32(n) + + if errno == experimentalsys.ENOSYS { + return 0, experimentalsys.EBADF // e.g. unimplemented for read + } else if errno != 0 { + return 0, errno + } else if n < int(l) { + break // stop when we read less than capacity. + } + } + return nread, 0 +} + +// fdReaddir is the WASI function named wasip1.FdReaddirName which reads +// directory entries from a directory. Special behaviors required by this +// function are implemented in sys.DirentCache. +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_readdirfd-fd-buf-pointeru8-buf_len-size-cookie-dircookie---errno-size +// +// # Result (Errno) +// +// The return value is 0 except the following known error conditions: +// - sys.ENOSYS: the implementation does not support this function. +// - sys.EBADF: the file was closed or not a directory. +// - sys.EFAULT: `buf` or `buf_len` point to an offset out of memory. +// - sys.ENOENT: `cookie` was invalid. +// - sys.EINVAL: `buf_len` was not large enough to write a dirent header. +// +// # End of Directory (EOF) +// +// More entries are available when `result.bufused` == `buf_len`. See +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_readdir +// https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c#L44 +var fdReaddir = newHostFunc( + wasip1.FdReaddirName, fdReaddirFn, + []wasm.ValueType{i32, i32, i32, i64, i32}, + "fd", "buf", "buf_len", "cookie", "result.bufused", +) + +func fdReaddirFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + mem := mod.Memory() + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + fd := int32(params[0]) + buf := uint32(params[1]) + bufLen := uint32(params[2]) + cookie := params[3] + resultBufused := uint32(params[4]) + + // The bufLen must be enough to write a dirent header. + if bufLen < wasip1.DirentSize { + // This is a bug in the caller, as unless `buf_len` is large enough to + // write a dirent, it can't read the `d_namlen` from it. + return experimentalsys.EINVAL + } + + // Get or open a dirent cache for this file descriptor. + dir, errno := direntCache(fsc, fd) + if errno != 0 { + return errno + } + + // First, determine the maximum directory entries that can be encoded as + // dirents. The total size is DirentSize(24) + nameSize, for each file. + // Since a zero-length file name is invalid, the minimum size entry is + // 25 (DirentSize + 1 character). + maxDirEntries := bufLen/wasip1.DirentSize + 1 + + // While unlikely maxDirEntries will fit into bufLen, add one more just in + // case, as we need to know if we hit the end of the directory or not to + // write the correct bufused (e.g. == bufLen unless EOF). + // >> If less than the size of the read buffer, the end of the + // >> directory has been reached. + maxDirEntries += 1 + + // Read up to max entries. The underlying implementation will cache these, + // starting at the current location, so that they can be re-read. This is + // important because even the first could end up larger than bufLen due to + // the size of its name. + dirents, errno := dir.Read(cookie, maxDirEntries) + if errno != 0 { + return errno + } + + // Determine how many dirents we can write, including a potentially + // truncated last entry. + bufToWrite, direntCount, truncatedLen := maxDirents(dirents, bufLen) + + // Now, write entries to the underlying buffer. + if bufToWrite > 0 { + + // d_next is the index of the next file in the list, so it should + // always be one higher than the requested cookie. + d_next := cookie + 1 + // ^^ yes this can overflow to negative, which means our implementation + // doesn't support writing greater than max int64 entries. + + buf, ok := mem.Read(buf, bufToWrite) + if !ok { + return experimentalsys.EFAULT + } + + writeDirents(buf, dirents, d_next, direntCount, truncatedLen) + } + + // bufused == bufLen means more dirents exist, which is the case when one + // is truncated. + bufused := bufToWrite + if truncatedLen > 0 { + bufused = bufLen + } + + if !mem.WriteUint32Le(resultBufused, bufused) { + return experimentalsys.EFAULT + } + return 0 +} + +const largestDirent = int64(math.MaxUint32 - wasip1.DirentSize) + +// maxDirents returns the dirents to write. +// +// `bufToWrite` is the amount of memory needed to write direntCount, which +// includes up to wasip1.DirentSize of a last truncated entry. +func maxDirents(dirents []experimentalsys.Dirent, bufLen uint32) (bufToWrite uint32, direntCount int, truncatedLen uint32) { + lenRemaining := bufLen + for i := range dirents { + if lenRemaining == 0 { + break + } + d := dirents[i] + direntCount++ + + // use int64 to guard against huge filenames + nameLen := int64(len(d.Name)) + var entryLen uint32 + + // Check to see if DirentSize + nameLen overflows, or if it would be + // larger than possible to encode. + if el := int64(wasip1.DirentSize) + nameLen; el < 0 || el > largestDirent { + // panic, as testing is difficult. ex we would have to extract a + // function to get size of a string or allocate a 2^32 size one! + panic("invalid filename: too large") + } else { // we know this can fit into a uint32 + entryLen = uint32(el) + } + + if entryLen > lenRemaining { + // We haven't room to write the entry, and docs say to write the + // header. This helps especially when there is an entry with a very + // long filename. Ex if bufLen is 4096 and the filename is 4096, + // we need to write DirentSize(24) + 4096 bytes to write the entry. + // In this case, we only write up to DirentSize(24) to allow the + // caller to resize. + if lenRemaining >= wasip1.DirentSize { + truncatedLen = wasip1.DirentSize + } else { + truncatedLen = lenRemaining + } + bufToWrite += truncatedLen + break + } + + // This won't go negative because we checked entryLen <= lenRemaining. + lenRemaining -= entryLen + bufToWrite += entryLen + } + return +} + +// writeDirents writes the directory entries to the buffer, which is pre-sized +// based on maxDirents. truncatedEntryLen means the last is written without its +// name. +func writeDirents(buf []byte, dirents []experimentalsys.Dirent, d_next uint64, direntCount int, truncatedLen uint32) { + pos := uint32(0) + skipNameI := -1 + + // If the last entry was truncated, we either skip it or write it without + // its name, depending on the length. + if truncatedLen > 0 { + if truncatedLen < wasip1.DirentSize { + direntCount-- // skip as too small to write the header. + } else { + skipNameI = direntCount - 1 // write the header, but not the name. + } + } + + for i := 0; i < direntCount; i++ { + e := dirents[i] + nameLen := uint32(len(e.Name)) + writeDirent(buf[pos:], d_next, e.Ino, nameLen, e.Type) + d_next++ + pos += wasip1.DirentSize + + if i != skipNameI { + copy(buf[pos:], e.Name) + pos += nameLen + } + } +} + +// writeDirent writes DirentSize bytes +func writeDirent(buf []byte, dNext uint64, ino sysapi.Inode, dNamlen uint32, dType fs.FileMode) { + le.PutUint64(buf, dNext) // d_next + le.PutUint64(buf[8:], ino) // d_ino + le.PutUint32(buf[16:], dNamlen) // d_namlen + filetype := getWasiFiletype(dType) + le.PutUint32(buf[20:], uint32(filetype)) // d_type +} + +// direntCache lazy opens a sys.DirentCache for this directory or returns an +// error. +func direntCache(fsc *sys.FSContext, fd int32) (*sys.DirentCache, experimentalsys.Errno) { + if f, ok := fsc.LookupFile(fd); !ok { + return nil, experimentalsys.EBADF + } else if dir, errno := f.DirentCache(); errno == 0 { + return dir, 0 + } else if errno == experimentalsys.ENOTDIR { + // fd_readdir docs don't indicate whether to return sys.ENOTDIR or + // sys.EBADF. It has been noticed that rust will crash on sys.ENOTDIR, + // and POSIX C ref seems to not return this, so we don't either. + // + // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_readdir + // and https://en.wikibooks.org/wiki/C_Programming/POSIX_Reference/dirent.h + return nil, experimentalsys.EBADF + } else { + return nil, errno + } +} + +// fdRenumber is the WASI function named FdRenumberName which atomically +// replaces a file descriptor by renumbering another file descriptor. +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_renumberfd-fd-to-fd---errno +var fdRenumber = newHostFunc(wasip1.FdRenumberName, fdRenumberFn, []wasm.ValueType{i32, i32}, "fd", "to") + +func fdRenumberFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + from := int32(params[0]) + to := int32(params[1]) + + if errno := fsc.Renumber(from, to); errno != 0 { + return errno + } + return 0 +} + +// fdSeek is the WASI function named FdSeekName which moves the offset of a +// file descriptor. +// +// # Parameters +// +// - fd: file descriptor to move the offset of +// - offset: signed int64, which is encoded as uint64, input argument to +// `whence`, which results in a new offset +// - whence: operator that creates the new offset, given `offset` bytes +// - If io.SeekStart, new offset == `offset`. +// - If io.SeekCurrent, new offset == existing offset + `offset`. +// - If io.SeekEnd, new offset == file size of `fd` + `offset`. +// - resultNewoffset: offset in api.Memory to write the new offset to, +// relative to start of the file +// +// Result (Errno) +// +// The return value is 0 except the following error conditions: +// - sys.EBADF: `fd` is invalid +// - sys.EFAULT: `resultNewoffset` points to an offset out of memory +// - sys.EINVAL: `whence` is an invalid value +// - sys.EIO: a file system error +// - sys.EISDIR: the file was a directory. +// +// For example, if fd 3 is a file with offset 0, and parameters fd=3, offset=4, +// whence=0 (=io.SeekStart), resultNewOffset=1, this function writes the below +// to api.Memory: +// +// uint64le +// +--------------------+ +// | | +// []byte{?, 4, 0, 0, 0, 0, 0, 0, 0, ? } +// resultNewoffset --^ +// +// Note: This is similar to `lseek` in POSIX. https://linux.die.net/man/3/lseek +// +// See io.Seeker +// and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_seek +var fdSeek = newHostFunc( + wasip1.FdSeekName, fdSeekFn, + []api.ValueType{i32, i64, i32, i32}, + "fd", "offset", "whence", "result.newoffset", +) + +func fdSeekFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + fd := int32(params[0]) + offset := params[1] + whence := uint32(params[2]) + resultNewoffset := uint32(params[3]) + + if f, ok := fsc.LookupFile(fd); !ok { + return experimentalsys.EBADF + } else if isDir, _ := f.File.IsDir(); isDir { + return experimentalsys.EISDIR // POSIX doesn't forbid seeking a directory, but wasi-testsuite does. + } else if newOffset, errno := f.File.Seek(int64(offset), int(whence)); errno != 0 { + return errno + } else if !mod.Memory().WriteUint64Le(resultNewoffset, uint64(newOffset)) { + return experimentalsys.EFAULT + } + return 0 +} + +// fdSync is the WASI function named FdSyncName which synchronizes the data +// and metadata of a file to disk. +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_syncfd-fd---errno +var fdSync = newHostFunc(wasip1.FdSyncName, fdSyncFn, []api.ValueType{i32}, "fd") + +func fdSyncFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + fd := int32(params[0]) + + // Check to see if the file descriptor is available + if f, ok := fsc.LookupFile(fd); !ok { + return experimentalsys.EBADF + } else { + return f.File.Sync() + } +} + +// fdTell is the WASI function named FdTellName which returns the current +// offset of a file descriptor. +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_tellfd-fd---errno-filesize +var fdTell = newHostFunc(wasip1.FdTellName, fdTellFn, []api.ValueType{i32, i32}, "fd", "result.offset") + +func fdTellFn(ctx context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fd := params[0] + offset := uint64(0) + whence := uint64(io.SeekCurrent) + resultNewoffset := params[1] + + fdSeekParams := []uint64{fd, offset, whence, resultNewoffset} + return fdSeekFn(ctx, mod, fdSeekParams) +} + +// fdWrite is the WASI function named FdWriteName which writes to a file +// descriptor. +// +// # Parameters +// +// - fd: an opened file descriptor to write data to +// - iovs: offset in api.Memory to read offset, size pairs representing the +// data to write to `fd` +// - Both offset and length are encoded as uint32le. +// - iovsCount: count of memory offset, size pairs to read sequentially +// starting at iovs +// - resultNwritten: offset in api.Memory to write the number of bytes +// written +// +// Result (Errno) +// +// The return value is 0 except the following error conditions: +// - sys.EBADF: `fd` is invalid +// - sys.EFAULT: `iovs` or `resultNwritten` point to an offset out of memory +// - sys.EIO: a file system error +// +// For example, this function needs to first read `iovs` to determine what to +// write to `fd`. If parameters iovs=1 iovsCount=2, this function reads two +// offset/length pairs from api.Memory: +// +// iovs[0] iovs[1] +// +---------------------+ +--------------------+ +// | uint32le uint32le| |uint32le uint32le| +// +---------+ +--------+ +--------+ +--------+ +// | | | | | | | | +// []byte{?, 18, 0, 0, 0, 4, 0, 0, 0, 23, 0, 0, 0, 2, 0, 0, 0, ?... } +// iovs --^ ^ ^ ^ +// | | | | +// offset --+ length --+ offset --+ length --+ +// +// This function reads those chunks api.Memory into the `fd` sequentially. +// +// iovs[0].length iovs[1].length +// +--------------+ +----+ +// | | | | +// []byte{ 0..16, ?, 'w', 'a', 'z', 'e', ?, 'r', 'o', ? } +// iovs[0].offset --^ ^ +// iovs[1].offset --+ +// +// Since "wazero" was written, if parameter resultNwritten=26, this function +// writes the below to api.Memory: +// +// uint32le +// +--------+ +// | | +// []byte{ 0..24, ?, 6, 0, 0, 0', ? } +// resultNwritten --^ +// +// Note: This is similar to `writev` in POSIX. https://linux.die.net/man/3/writev +// +// See fdRead +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#ciovec +// and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_write +var fdWrite = newHostFunc( + wasip1.FdWriteName, fdWriteFn, + []api.ValueType{i32, i32, i32, i32}, + "fd", "iovs", "iovs_len", "result.nwritten", +) + +func fdWriteFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + return fdWriteOrPwrite(mod, params, false) +} + +// pwriter tracks an offset across multiple writes. +type pwriter struct { + f experimentalsys.File + offset int64 +} + +// Write implements the same function as documented on sys.File. +func (w *pwriter) Write(buf []byte) (n int, errno experimentalsys.Errno) { + if len(buf) == 0 { + return 0, 0 // less overhead on zero-length writes. + } + + n, err := w.f.Pwrite(buf, w.offset) + w.offset += int64(n) + return n, err +} + +func fdWriteOrPwrite(mod api.Module, params []uint64, isPwrite bool) experimentalsys.Errno { + mem := mod.Memory() + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + fd := int32(params[0]) + iovs := uint32(params[1]) + iovsCount := uint32(params[2]) + + var resultNwritten uint32 + var writer func(buf []byte) (n int, errno experimentalsys.Errno) + if f, ok := fsc.LookupFile(fd); !ok { + return experimentalsys.EBADF + } else if isPwrite { + offset := int64(params[3]) + writer = (&pwriter{f: f.File, offset: offset}).Write + resultNwritten = uint32(params[4]) + } else { + writer = f.File.Write + resultNwritten = uint32(params[3]) + } + + nwritten, errno := writev(mem, iovs, iovsCount, writer) + if errno != 0 { + return errno + } + + if !mod.Memory().WriteUint32Le(resultNwritten, nwritten) { + return experimentalsys.EFAULT + } + return 0 +} + +func writev(mem api.Memory, iovs uint32, iovsCount uint32, writer func(buf []byte) (n int, errno experimentalsys.Errno)) (uint32, experimentalsys.Errno) { + var nwritten uint32 + iovsStop := iovsCount << 3 // iovsCount * 8 + iovsBuf, ok := mem.Read(iovs, iovsStop) + if !ok { + return 0, experimentalsys.EFAULT + } + + for iovsPos := uint32(0); iovsPos < iovsStop; iovsPos += 8 { + offset := le.Uint32(iovsBuf[iovsPos:]) + l := le.Uint32(iovsBuf[iovsPos+4:]) + + b, ok := mem.Read(offset, l) + if !ok { + return 0, experimentalsys.EFAULT + } + n, errno := writer(b) + nwritten += uint32(n) + if errno == experimentalsys.ENOSYS { + return 0, experimentalsys.EBADF // e.g. unimplemented for write + } else if errno != 0 { + return 0, errno + } + } + return nwritten, 0 +} + +// pathCreateDirectory is the WASI function named PathCreateDirectoryName which +// creates a directory. +// +// # Parameters +// +// - fd: file descriptor of a directory that `path` is relative to +// - path: offset in api.Memory to read the path string from +// - pathLen: length of `path` +// +// # Result (Errno) +// +// The return value is 0 except the following error conditions: +// - sys.EBADF: `fd` is invalid +// - sys.ENOENT: `path` does not exist. +// - sys.ENOTDIR: `path` is a file +// +// # Notes +// - This is similar to mkdirat in POSIX. +// See https://linux.die.net/man/2/mkdirat +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_create_directoryfd-fd-path-string---errno +var pathCreateDirectory = newHostFunc( + wasip1.PathCreateDirectoryName, pathCreateDirectoryFn, + []wasm.ValueType{i32, i32, i32}, + "fd", "path", "path_len", +) + +func pathCreateDirectoryFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + fd := int32(params[0]) + path := uint32(params[1]) + pathLen := uint32(params[2]) + + preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen) + if errno != 0 { + return errno + } + + if errno = preopen.Mkdir(pathName, 0o700); errno != 0 { + return errno + } + + return 0 +} + +// pathFilestatGet is the WASI function named PathFilestatGetName which +// returns the stat attributes of a file or directory. +// +// # Parameters +// +// - fd: file descriptor of the folder to look in for the path +// - flags: flags determining the method of how paths are resolved +// - path: path under fd to get the filestat attributes data for +// - path_len: length of the path that was given +// - resultFilestat: offset to write the result filestat data +// +// Result (Errno) +// +// The return value is 0 except the following error conditions: +// - sys.EBADF: `fd` is invalid +// - sys.ENOTDIR: `fd` points to a file not a directory +// - sys.EIO: could not stat `fd` on filesystem +// - sys.EINVAL: the path contained "../" +// - sys.ENAMETOOLONG: `path` + `path_len` is out of memory +// - sys.EFAULT: `resultFilestat` points to an offset out of memory +// - sys.ENOENT: could not find the path +// +// The rest of this implementation matches that of fdFilestatGet, so is not +// repeated here. +// +// Note: This is similar to `fstatat` in POSIX. +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_filestat_getfd-fd-flags-lookupflags-path-string---errno-filestat +// and https://linux.die.net/man/2/fstatat +var pathFilestatGet = newHostFunc( + wasip1.PathFilestatGetName, pathFilestatGetFn, + []api.ValueType{i32, i32, i32, i32, i32}, + "fd", "flags", "path", "path_len", "result.filestat", +) + +func pathFilestatGetFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + fd := int32(params[0]) + flags := uint16(params[1]) + path := uint32(params[2]) + pathLen := uint32(params[3]) + + preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen) + if errno != 0 { + return errno + } + + // Stat the file without allocating a file descriptor. + var st sysapi.Stat_t + + if (flags & wasip1.LOOKUP_SYMLINK_FOLLOW) == 0 { + st, errno = preopen.Lstat(pathName) + } else { + st, errno = preopen.Stat(pathName) + } + if errno != 0 { + return errno + } + + // Write the stat result to memory + resultBuf := uint32(params[4]) + buf, ok := mod.Memory().Read(resultBuf, 64) + if !ok { + return experimentalsys.EFAULT + } + + filetype := getWasiFiletype(st.Mode) + return writeFilestat(buf, &st, filetype) +} + +// pathFilestatSetTimes is the WASI function named PathFilestatSetTimesName +// which adjusts the timestamps of a file or directory. +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_filestat_set_timesfd-fd-flags-lookupflags-path-string-atim-timestamp-mtim-timestamp-fst_flags-fstflags---errno +var pathFilestatSetTimes = newHostFunc( + wasip1.PathFilestatSetTimesName, pathFilestatSetTimesFn, + []wasm.ValueType{i32, i32, i32, i32, i64, i64, i32}, + "fd", "flags", "path", "path_len", "atim", "mtim", "fst_flags", +) + +func pathFilestatSetTimesFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fd := int32(params[0]) + flags := uint16(params[1]) + path := uint32(params[2]) + pathLen := uint32(params[3]) + atim := int64(params[4]) + mtim := int64(params[5]) + fstFlags := uint16(params[6]) + + sys := mod.(*wasm.ModuleInstance).Sys + fsc := sys.FS() + + atim, mtim, errno := toTimes(sys.WalltimeNanos, atim, mtim, fstFlags) + if errno != 0 { + return errno + } + + preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen) + if errno != 0 { + return errno + } + + symlinkFollow := flags&wasip1.LOOKUP_SYMLINK_FOLLOW != 0 + if symlinkFollow { + return preopen.Utimens(pathName, atim, mtim) + } + // Otherwise, we need to emulate don't follow by opening the file by path. + if f, errno := preopen.OpenFile(pathName, experimentalsys.O_WRONLY, 0); errno != 0 { + return errno + } else { + defer f.Close() + return f.Utimens(atim, mtim) + } +} + +// pathLink is the WASI function named PathLinkName which adjusts the +// timestamps of a file or directory. +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#path_link +var pathLink = newHostFunc( + wasip1.PathLinkName, pathLinkFn, + []wasm.ValueType{i32, i32, i32, i32, i32, i32, i32}, + "old_fd", "old_flags", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len", +) + +func pathLinkFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + mem := mod.Memory() + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + oldFD := int32(params[0]) + // TODO: use old_flags? + _ = uint32(params[1]) + oldPath := uint32(params[2]) + oldPathLen := uint32(params[3]) + + oldFS, oldName, errno := atPath(fsc, mem, oldFD, oldPath, oldPathLen) + if errno != 0 { + return errno + } + + newFD := int32(params[4]) + newPath := uint32(params[5]) + newPathLen := uint32(params[6]) + + newFS, newName, errno := atPath(fsc, mem, newFD, newPath, newPathLen) + if errno != 0 { + return errno + } + + if oldFS != newFS { // TODO: handle link across filesystems + return experimentalsys.ENOSYS + } + + return oldFS.Link(oldName, newName) +} + +// pathOpen is the WASI function named PathOpenName which opens a file or +// directory. This returns sys.EBADF if the fd is invalid. +// +// # Parameters +// +// - fd: file descriptor of a directory that `path` is relative to +// - dirflags: flags to indicate how to resolve `path` +// - path: offset in api.Memory to read the path string from +// - pathLen: length of `path` +// - oFlags: open flags to indicate the method by which to open the file +// - fsRightsBase: interpret RIGHT_FD_WRITE to set O_RDWR +// - fsRightsInheriting: ignored as rights were removed from WASI. +// created file descriptor for `path` +// - fdFlags: file descriptor flags +// - resultOpenedFD: offset in api.Memory to write the newly created file +// descriptor to. +// - The result FD value is guaranteed to be less than 2**31 +// +// Result (Errno) +// +// The return value is 0 except the following error conditions: +// - sys.EBADF: `fd` is invalid +// - sys.EFAULT: `resultOpenedFD` points to an offset out of memory +// - sys.ENOENT: `path` does not exist. +// - sys.EEXIST: `path` exists, while `oFlags` requires that it must not. +// - sys.ENOTDIR: `path` is not a directory, while `oFlags` requires it. +// - sys.EIO: a file system error +// +// For example, this function needs to first read `path` to determine the file +// to open. If parameters `path` = 1, `pathLen` = 6, and the path is "wazero", +// pathOpen reads the path from api.Memory: +// +// pathLen +// +------------------------+ +// | | +// []byte{ ?, 'w', 'a', 'z', 'e', 'r', 'o', ?... } +// path --^ +// +// Then, if parameters resultOpenedFD = 8, and this function opened a new file +// descriptor 5 with the given flags, this function writes the below to +// api.Memory: +// +// uint32le +// +--------+ +// | | +// []byte{ 0..6, ?, 5, 0, 0, 0, ?} +// resultOpenedFD --^ +// +// # Notes +// - This is similar to `openat` in POSIX. https://linux.die.net/man/3/openat +// - The returned file descriptor is not guaranteed to be the lowest-number +// +// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#path_open +var pathOpen = newHostFunc( + wasip1.PathOpenName, pathOpenFn, + []api.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32}, + "fd", "dirflags", "path", "path_len", "oflags", "fs_rights_base", "fs_rights_inheriting", "fdflags", "result.opened_fd", +) + +func pathOpenFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + preopenFD := int32(params[0]) + + // TODO: dirflags is a lookupflags, and it only has one bit: symlink_follow + // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#lookupflags + dirflags := uint16(params[1]) + + path := uint32(params[2]) + pathLen := uint32(params[3]) + + oflags := uint16(params[4]) + + rights := uint32(params[5]) + // inherited rights aren't used + _ = params[6] + + fdflags := uint16(params[7]) + resultOpenedFD := uint32(params[8]) + + preopen, pathName, errno := atPath(fsc, mod.Memory(), preopenFD, path, pathLen) + if errno != 0 { + return errno + } + + if pathLen == 0 { + return experimentalsys.EINVAL + } + + fileOpenFlags := openFlags(dirflags, oflags, fdflags, rights) + isDir := fileOpenFlags&experimentalsys.O_DIRECTORY != 0 + + if isDir && oflags&wasip1.O_CREAT != 0 { + return experimentalsys.EINVAL // use pathCreateDirectory! + } + + newFD, errno := fsc.OpenFile(preopen, pathName, fileOpenFlags, 0o600) + if errno != 0 { + return errno + } + + // Check any flags that require the file to evaluate. + if isDir { + if f, ok := fsc.LookupFile(newFD); !ok { + return experimentalsys.EBADF // unexpected + } else if isDir, errno := f.File.IsDir(); errno != 0 { + _ = fsc.CloseFile(newFD) + return errno + } else if !isDir { + _ = fsc.CloseFile(newFD) + return experimentalsys.ENOTDIR + } + } + + if !mod.Memory().WriteUint32Le(resultOpenedFD, uint32(newFD)) { + _ = fsc.CloseFile(newFD) + return experimentalsys.EFAULT + } + return 0 +} + +// atPath returns the pre-open specific path after verifying it is a directory. +// +// # Notes +// +// Languages including Zig and Rust use only pre-opens for the FD because +// wasi-libc `__wasilibc_find_relpath` will only return a preopen. That said, +// our wasi.c example shows other languages act differently and can use a non +// pre-opened file descriptor. +// +// We don't handle `AT_FDCWD`, as that's resolved in the compiler. There's no +// working directory function in WASI, so most assume CWD is "/". Notably, Zig +// has different behavior which assumes it is whatever the first pre-open name +// is. +// +// See https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/sources/at_fdcwd.c +// See https://linux.die.net/man/2/openat +func atPath(fsc *sys.FSContext, mem api.Memory, fd int32, p, pathLen uint32) (experimentalsys.FS, string, experimentalsys.Errno) { + b, ok := mem.Read(p, pathLen) + if !ok { + return nil, "", experimentalsys.EFAULT + } + pathName := string(b) + + // interesting_paths wants us to break on trailing slash if the input ends + // up a file, not a directory! + hasTrailingSlash := strings.HasSuffix(pathName, "/") + + // interesting_paths includes paths that include relative links but end up + // not escaping + pathName = path.Clean(pathName) + + // interesting_paths wants to break on root paths or anything that escapes. + // This part is the same as fs.FS.Open() + if !fs.ValidPath(pathName) { + return nil, "", experimentalsys.EPERM + } + + // add the trailing slash back + if hasTrailingSlash { + pathName = pathName + "/" + } + + if f, ok := fsc.LookupFile(fd); !ok { + return nil, "", experimentalsys.EBADF // closed or invalid + } else if isDir, errno := f.File.IsDir(); errno != 0 { + return nil, "", errno + } else if !isDir { + return nil, "", experimentalsys.ENOTDIR + } else if f.IsPreopen { // don't append the pre-open name + return f.FS, pathName, 0 + } else { + // Join via concat to avoid name conflict on path.Join + return f.FS, f.Name + "/" + pathName, 0 + } +} + +func preopenPath(fsc *sys.FSContext, fd int32) (string, experimentalsys.Errno) { + if f, ok := fsc.LookupFile(fd); !ok { + return "", experimentalsys.EBADF // closed + } else if !f.IsPreopen { + return "", experimentalsys.EBADF + } else if isDir, errno := f.File.IsDir(); errno != 0 || !isDir { + // In wasip1, only directories can be returned by fd_prestat_get as + // there are no prestat types defined for files or sockets. + return "", errno + } else { + return f.Name, 0 + } +} + +func openFlags(dirflags, oflags, fdflags uint16, rights uint32) (openFlags experimentalsys.Oflag) { + if dirflags&wasip1.LOOKUP_SYMLINK_FOLLOW == 0 { + openFlags |= experimentalsys.O_NOFOLLOW + } + if oflags&wasip1.O_DIRECTORY != 0 { + openFlags |= experimentalsys.O_DIRECTORY + } else if oflags&wasip1.O_EXCL != 0 { + openFlags |= experimentalsys.O_EXCL + } + // Because we don't implement rights, we partially rely on the open flags + // to determine the mode in which the file will be opened. This will create + // divergent behavior compared to WASI runtimes which have a more strict + // interpretation of the WASI capabilities model; for example, a program + // which sets O_CREAT but does not give read or write permissions will + // successfully create a file when running with wazero, but might get a + // permission denied error on other runtimes. + defaultMode := experimentalsys.O_RDONLY + if oflags&wasip1.O_TRUNC != 0 { + openFlags |= experimentalsys.O_TRUNC + defaultMode = experimentalsys.O_RDWR + } + if oflags&wasip1.O_CREAT != 0 { + openFlags |= experimentalsys.O_CREAT + defaultMode = experimentalsys.O_RDWR + } + if fdflags&wasip1.FD_NONBLOCK != 0 { + openFlags |= experimentalsys.O_NONBLOCK + } + if fdflags&wasip1.FD_APPEND != 0 { + openFlags |= experimentalsys.O_APPEND + defaultMode = experimentalsys.O_RDWR + } + if fdflags&wasip1.FD_DSYNC != 0 { + openFlags |= experimentalsys.O_DSYNC + } + if fdflags&wasip1.FD_RSYNC != 0 { + openFlags |= experimentalsys.O_RSYNC + } + if fdflags&wasip1.FD_SYNC != 0 { + openFlags |= experimentalsys.O_SYNC + } + + // Since rights were discontinued in wasi, we only interpret RIGHT_FD_WRITE + // because it is the only way to know that we need to set write permissions + // on a file if the application did not pass any of O_CREAT, O_APPEND, nor + // O_TRUNC. + const r = wasip1.RIGHT_FD_READ + const w = wasip1.RIGHT_FD_WRITE + const rw = r | w + switch { + case (rights & rw) == rw: + openFlags |= experimentalsys.O_RDWR + case (rights & w) == w: + openFlags |= experimentalsys.O_WRONLY + case (rights & r) == r: + openFlags |= experimentalsys.O_RDONLY + default: + openFlags |= defaultMode + } + return +} + +// pathReadlink is the WASI function named PathReadlinkName that reads the +// contents of a symbolic link. +// +// See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_readlinkfd-fd-path-string-buf-pointeru8-buf_len-size---errno-size +var pathReadlink = newHostFunc( + wasip1.PathReadlinkName, pathReadlinkFn, + []wasm.ValueType{i32, i32, i32, i32, i32, i32}, + "fd", "path", "path_len", "buf", "buf_len", "result.bufused", +) + +func pathReadlinkFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + fd := int32(params[0]) + path := uint32(params[1]) + pathLen := uint32(params[2]) + buf := uint32(params[3]) + bufLen := uint32(params[4]) + resultBufused := uint32(params[5]) + + if pathLen == 0 || bufLen == 0 { + return experimentalsys.EINVAL + } + + mem := mod.Memory() + preopen, p, errno := atPath(fsc, mem, fd, path, pathLen) + if errno != 0 { + return errno + } + + dst, errno := preopen.Readlink(p) + if errno != 0 { + return errno + } + + if len(dst) > int(bufLen) { + return experimentalsys.ERANGE + } + + if ok := mem.WriteString(buf, dst); !ok { + return experimentalsys.EFAULT + } + + if !mem.WriteUint32Le(resultBufused, uint32(len(dst))) { + return experimentalsys.EFAULT + } + return 0 +} + +// pathRemoveDirectory is the WASI function named PathRemoveDirectoryName which +// removes a directory. +// +// # Parameters +// +// - fd: file descriptor of a directory that `path` is relative to +// - path: offset in api.Memory to read the path string from +// - pathLen: length of `path` +// +// # Result (Errno) +// +// The return value is 0 except the following error conditions: +// - sys.EBADF: `fd` is invalid +// - sys.ENOENT: `path` does not exist. +// - sys.ENOTEMPTY: `path` is not empty +// - sys.ENOTDIR: `path` is a file +// +// # Notes +// - This is similar to unlinkat with AT_REMOVEDIR in POSIX. +// See https://linux.die.net/man/2/unlinkat +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_remove_directoryfd-fd-path-string---errno +var pathRemoveDirectory = newHostFunc( + wasip1.PathRemoveDirectoryName, pathRemoveDirectoryFn, + []wasm.ValueType{i32, i32, i32}, + "fd", "path", "path_len", +) + +func pathRemoveDirectoryFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + fd := int32(params[0]) + path := uint32(params[1]) + pathLen := uint32(params[2]) + + preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen) + if errno != 0 { + return errno + } + + return preopen.Rmdir(pathName) +} + +// pathRename is the WASI function named PathRenameName which renames a file or +// directory. +// +// # Parameters +// +// - fd: file descriptor of a directory that `old_path` is relative to +// - old_path: offset in api.Memory to read the old path string from +// - old_path_len: length of `old_path` +// - new_fd: file descriptor of a directory that `new_path` is relative to +// - new_path: offset in api.Memory to read the new path string from +// - new_path_len: length of `new_path` +// +// # Result (Errno) +// +// The return value is 0 except the following error conditions: +// - sys.EBADF: `fd` or `new_fd` are invalid +// - sys.ENOENT: `old_path` does not exist. +// - sys.ENOTDIR: `old` is a directory and `new` exists, but is a file. +// - sys.EISDIR: `old` is a file and `new` exists, but is a directory. +// +// # Notes +// - This is similar to unlinkat in POSIX. +// See https://linux.die.net/man/2/renameat +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_renamefd-fd-old_path-string-new_fd-fd-new_path-string---errno +var pathRename = newHostFunc( + wasip1.PathRenameName, pathRenameFn, + []wasm.ValueType{i32, i32, i32, i32, i32, i32}, + "fd", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len", +) + +func pathRenameFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + fd := int32(params[0]) + oldPath := uint32(params[1]) + oldPathLen := uint32(params[2]) + + newFD := int32(params[3]) + newPath := uint32(params[4]) + newPathLen := uint32(params[5]) + + oldFS, oldPathName, errno := atPath(fsc, mod.Memory(), fd, oldPath, oldPathLen) + if errno != 0 { + return errno + } + + newFS, newPathName, errno := atPath(fsc, mod.Memory(), newFD, newPath, newPathLen) + if errno != 0 { + return errno + } + + if oldFS != newFS { // TODO: handle renames across filesystems + return experimentalsys.ENOSYS + } + + return oldFS.Rename(oldPathName, newPathName) +} + +// pathSymlink is the WASI function named PathSymlinkName which creates a +// symbolic link. +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#path_symlink +var pathSymlink = newHostFunc( + wasip1.PathSymlinkName, pathSymlinkFn, + []wasm.ValueType{i32, i32, i32, i32, i32}, + "old_path", "old_path_len", "fd", "new_path", "new_path_len", +) + +func pathSymlinkFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + oldPath := uint32(params[0]) + oldPathLen := uint32(params[1]) + fd := int32(params[2]) + newPath := uint32(params[3]) + newPathLen := uint32(params[4]) + + mem := mod.Memory() + + dir, ok := fsc.LookupFile(fd) + if !ok { + return experimentalsys.EBADF // closed + } else if isDir, errno := dir.File.IsDir(); errno != 0 { + return errno + } else if !isDir { + return experimentalsys.ENOTDIR + } + + if oldPathLen == 0 || newPathLen == 0 { + return experimentalsys.EINVAL + } + + oldPathBuf, ok := mem.Read(oldPath, oldPathLen) + if !ok { + return experimentalsys.EFAULT + } + + _, newPathName, errno := atPath(fsc, mod.Memory(), fd, newPath, newPathLen) + if errno != 0 { + return errno + } + + return dir.FS.Symlink( + // Do not join old path since it's only resolved when dereference the link created here. + // And the dereference result depends on the opening directory's file descriptor at that point. + unsafe.String(&oldPathBuf[0], int(oldPathLen)), + newPathName, + ) +} + +// pathUnlinkFile is the WASI function named PathUnlinkFileName which unlinks a +// file. +// +// # Parameters +// +// - fd: file descriptor of a directory that `path` is relative to +// - path: offset in api.Memory to read the path string from +// - pathLen: length of `path` +// +// # Result (Errno) +// +// The return value is 0 except the following error conditions: +// - sys.EBADF: `fd` is invalid +// - sys.ENOENT: `path` does not exist. +// - sys.EISDIR: `path` is a directory +// +// # Notes +// - This is similar to unlinkat without AT_REMOVEDIR in POSIX. +// See https://linux.die.net/man/2/unlinkat +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_unlink_filefd-fd-path-string---errno +var pathUnlinkFile = newHostFunc( + wasip1.PathUnlinkFileName, pathUnlinkFileFn, + []wasm.ValueType{i32, i32, i32}, + "fd", "path", "path_len", +) + +func pathUnlinkFileFn(_ context.Context, mod api.Module, params []uint64) experimentalsys.Errno { + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + fd := int32(params[0]) + path := uint32(params[1]) + pathLen := uint32(params[2]) + + preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen) + if errno != 0 { + return errno + } + + return preopen.Unlink(pathName) +} diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/poll.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/poll.go new file mode 100644 index 00000000000..4f96af2df38 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/poll.go @@ -0,0 +1,237 @@ +package wasi_snapshot_preview1 + +import ( + "context" + "time" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" + internalsys "github.com/tetratelabs/wazero/internal/sys" + "github.com/tetratelabs/wazero/internal/wasip1" + "github.com/tetratelabs/wazero/internal/wasm" +) + +// pollOneoff is the WASI function named PollOneoffName that concurrently +// polls for the occurrence of a set of events. +// +// # Parameters +// +// - in: pointer to the subscriptions (48 bytes each) +// - out: pointer to the resulting events (32 bytes each) +// - nsubscriptions: count of subscriptions, zero returns sys.EINVAL. +// - resultNevents: count of events. +// +// Result (Errno) +// +// The return value is 0 except the following error conditions: +// - sys.EINVAL: the parameters are invalid +// - sys.ENOTSUP: a parameters is valid, but not yet supported. +// - sys.EFAULT: there is not enough memory to read the subscriptions or +// write results. +// +// # Notes +// +// - Since the `out` pointer nests Errno, the result is always 0. +// - This is similar to `poll` in POSIX. +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#poll_oneoff +// See https://linux.die.net/man/3/poll +var pollOneoff = newHostFunc( + wasip1.PollOneoffName, pollOneoffFn, + []api.ValueType{i32, i32, i32, i32}, + "in", "out", "nsubscriptions", "result.nevents", +) + +type event struct { + eventType byte + userData []byte + errno wasip1.Errno +} + +func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) sys.Errno { + in := uint32(params[0]) + out := uint32(params[1]) + nsubscriptions := uint32(params[2]) + resultNevents := uint32(params[3]) + + if nsubscriptions == 0 { + return sys.EINVAL + } + + mem := mod.Memory() + + // Ensure capacity prior to the read loop to reduce error handling. + inBuf, ok := mem.Read(in, nsubscriptions*48) + if !ok { + return sys.EFAULT + } + outBuf, ok := mem.Read(out, nsubscriptions*32) + // zero-out all buffer before writing + clear(outBuf) + + if !ok { + return sys.EFAULT + } + + // Eagerly write the number of events which will equal subscriptions unless + // there's a fault in parsing (not processing). + if !mod.Memory().WriteUint32Le(resultNevents, nsubscriptions) { + return sys.EFAULT + } + + // Loop through all subscriptions and write their output. + + // Extract FS context, used in the body of the for loop for FS access. + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + // Slice of events that are processed out of the loop (blocking stdin subscribers). + var blockingStdinSubs []*event + // The timeout is initialized at max Duration, the loop will find the minimum. + var timeout time.Duration = 1<<63 - 1 + // Count of all the subscriptions that have been already written back to outBuf. + // nevents*32 returns at all times the offset where the next event should be written: + // this way we ensure that there are no gaps between records. + nevents := uint32(0) + + // Layout is subscription_u: Union + // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#subscription_u + for i := uint32(0); i < nsubscriptions; i++ { + inOffset := i * 48 + outOffset := nevents * 32 + + eventType := inBuf[inOffset+8] // +8 past userdata + // +8 past userdata +8 contents_offset + argBuf := inBuf[inOffset+8+8:] + userData := inBuf[inOffset : inOffset+8] + + evt := &event{ + eventType: eventType, + userData: userData, + errno: wasip1.ErrnoSuccess, + } + + switch eventType { + case wasip1.EventTypeClock: // handle later + newTimeout, err := processClockEvent(argBuf) + if err != 0 { + return err + } + // Min timeout. + if newTimeout < timeout { + timeout = newTimeout + } + // Ack the clock event to the outBuf. + writeEvent(outBuf[outOffset:], evt) + nevents++ + case wasip1.EventTypeFdRead: + fd := int32(le.Uint32(argBuf)) + if fd < 0 { + return sys.EBADF + } + if file, ok := fsc.LookupFile(fd); !ok { + evt.errno = wasip1.ErrnoBadf + writeEvent(outBuf[outOffset:], evt) + nevents++ + } else if fd != internalsys.FdStdin && file.File.IsNonblock() { + writeEvent(outBuf[outOffset:], evt) + nevents++ + } else { + // if the fd is Stdin, and it is in blocking mode, + // do not ack yet, append to a slice for delayed evaluation. + blockingStdinSubs = append(blockingStdinSubs, evt) + } + case wasip1.EventTypeFdWrite: + fd := int32(le.Uint32(argBuf)) + if fd < 0 { + return sys.EBADF + } + if _, ok := fsc.LookupFile(fd); ok { + evt.errno = wasip1.ErrnoNotsup + } else { + evt.errno = wasip1.ErrnoBadf + } + nevents++ + writeEvent(outBuf[outOffset:], evt) + default: + return sys.EINVAL + } + } + + sysCtx := mod.(*wasm.ModuleInstance).Sys + if nevents == nsubscriptions { + // We already wrote back all the results. We already wrote this number + // earlier to offset `resultNevents`. + // We only need to observe the timeout (nonzero if there are clock subscriptions) + // and return. + if timeout > 0 { + sysCtx.Nanosleep(int64(timeout)) + } + return 0 + } + + // If there are blocking stdin subscribers, check for data with given timeout. + stdin, ok := fsc.LookupFile(internalsys.FdStdin) + if !ok { + return sys.EBADF + } + // Wait for the timeout to expire, or for some data to become available on Stdin. + + if stdinReady, errno := stdin.File.Poll(fsapi.POLLIN, int32(timeout.Milliseconds())); errno != 0 { + return errno + } else if stdinReady { + // stdin has data ready to for reading, write back all the events + for i := range blockingStdinSubs { + evt := blockingStdinSubs[i] + evt.errno = 0 + writeEvent(outBuf[nevents*32:], evt) + nevents++ + } + } + + if nevents != nsubscriptions { + if !mod.Memory().WriteUint32Le(resultNevents, nevents) { + return sys.EFAULT + } + } + + return 0 +} + +// processClockEvent supports only relative name events, as that's what's used +// to implement sleep in various compilers including Rust, Zig and TinyGo. +func processClockEvent(inBuf []byte) (time.Duration, sys.Errno) { + _ /* ID */ = le.Uint32(inBuf[0:8]) // See below + timeout := le.Uint64(inBuf[8:16]) // nanos if relative + _ /* precision */ = le.Uint64(inBuf[16:24]) // Unused + flags := le.Uint16(inBuf[24:32]) + + var err sys.Errno + // subclockflags has only one flag defined: subscription_clock_abstime + switch flags { + case 0: // relative time + case 1: // subscription_clock_abstime + err = sys.ENOTSUP + default: // subclockflags has only one flag defined. + err = sys.EINVAL + } + + if err != 0 { + return 0, err + } else { + // https://linux.die.net/man/3/clock_settime says relative timers are + // unaffected. Since this function only supports relative timeout, we can + // skip name ID validation and use a single sleep function. + + return time.Duration(timeout), 0 + } +} + +// writeEvent writes the event corresponding to the processed subscription. +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-event-struct +func writeEvent(outBuf []byte, evt *event) { + copy(outBuf, evt.userData) // userdata + outBuf[8] = byte(evt.errno) // uint16, but safe as < 255 + outBuf[9] = 0 + le.PutUint32(outBuf[10:], uint32(evt.eventType)) + // TODO: When FD events are supported, write outOffset+16 +} diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/proc.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/proc.go new file mode 100644 index 00000000000..cb0ab487c33 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/proc.go @@ -0,0 +1,44 @@ +package wasi_snapshot_preview1 + +import ( + "context" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/wasip1" + "github.com/tetratelabs/wazero/internal/wasm" + "github.com/tetratelabs/wazero/sys" +) + +// procExit is the WASI function named ProcExitName that terminates the +// execution of the module with an exit code. The only successful exit code is +// zero. +// +// # Parameters +// +// - exitCode: exit code. +// +// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit +var procExit = &wasm.HostFunc{ + ExportName: wasip1.ProcExitName, + Name: wasip1.ProcExitName, + ParamTypes: []api.ValueType{i32}, + ParamNames: []string{"rval"}, + Code: wasm.Code{GoFunc: api.GoModuleFunc(procExitFn)}, +} + +func procExitFn(ctx context.Context, mod api.Module, params []uint64) { + exitCode := uint32(params[0]) + + // Ensure other callers see the exit code. + _ = mod.CloseWithExitCode(ctx, exitCode) + + // Prevent any code from executing after this function. For example, LLVM + // inserts unreachable instructions after calls to exit. + // See: https://github.com/emscripten-core/emscripten/issues/12322 + panic(sys.NewExitError(exitCode)) +} + +// procRaise is stubbed and will never be supported, as it was removed. +// +// See https://github.com/WebAssembly/WASI/pull/136 +var procRaise = stubFunction(wasip1.ProcRaiseName, []api.ValueType{i32}, "sig") diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/random.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/random.go new file mode 100644 index 00000000000..e4d7ccee154 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/random.go @@ -0,0 +1,55 @@ +package wasi_snapshot_preview1 + +import ( + "context" + "io" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/wasip1" + "github.com/tetratelabs/wazero/internal/wasm" +) + +// randomGet is the WASI function named RandomGetName which writes random +// data to a buffer. +// +// # Parameters +// +// - buf: api.Memory offset to write random values +// - bufLen: size of random data in bytes +// +// Result (Errno) +// +// The return value is ErrnoSuccess except the following error conditions: +// - sys.EFAULT: `buf` or `bufLen` point to an offset out of memory +// - sys.EIO: a file system error +// +// For example, if underlying random source was seeded like +// `rand.NewSource(42)`, we expect api.Memory to contain: +// +// bufLen (5) +// +--------------------------+ +// | | +// []byte{?, 0x53, 0x8c, 0x7f, 0x96, 0xb1, ?} +// buf --^ +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-bufLen-size---errno +var randomGet = newHostFunc(wasip1.RandomGetName, randomGetFn, []api.ValueType{i32, i32}, "buf", "buf_len") + +func randomGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno { + sysCtx := mod.(*wasm.ModuleInstance).Sys + randSource := sysCtx.RandSource() + buf, bufLen := uint32(params[0]), uint32(params[1]) + + randomBytes, ok := mod.Memory().Read(buf, bufLen) + if !ok { // out-of-range + return sys.EFAULT + } + + // We can ignore the returned n as it only != byteCount on error + if _, err := io.ReadAtLeast(randSource, randomBytes, int(bufLen)); err != nil { + return sys.EIO + } + + return 0 +} diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/sched.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/sched.go new file mode 100644 index 00000000000..86748e6d6f7 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/sched.go @@ -0,0 +1,22 @@ +package wasi_snapshot_preview1 + +import ( + "context" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/wasip1" + "github.com/tetratelabs/wazero/internal/wasm" +) + +// schedYield is the WASI function named SchedYieldName which temporarily +// yields execution of the calling thread. +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sched_yield---errno +var schedYield = newHostFunc(wasip1.SchedYieldName, schedYieldFn, nil) + +func schedYieldFn(_ context.Context, mod api.Module, _ []uint64) sys.Errno { + sysCtx := mod.(*wasm.ModuleInstance).Sys + sysCtx.Osyield() + return 0 +} diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/sock.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/sock.go new file mode 100644 index 00000000000..756c0d39137 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/sock.go @@ -0,0 +1,188 @@ +package wasi_snapshot_preview1 + +import ( + "context" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental/sys" + socketapi "github.com/tetratelabs/wazero/internal/sock" + "github.com/tetratelabs/wazero/internal/sysfs" + "github.com/tetratelabs/wazero/internal/wasip1" + "github.com/tetratelabs/wazero/internal/wasm" +) + +// sockAccept is the WASI function named SockAcceptName which accepts a new +// incoming connection. +// +// See: https://github.com/WebAssembly/WASI/blob/0ba0c5e2e37625ca5a6d3e4255a998dfaa3efc52/phases/snapshot/docs.md#sock_accept +// and https://github.com/WebAssembly/WASI/pull/458 +var sockAccept = newHostFunc( + wasip1.SockAcceptName, + sockAcceptFn, + []wasm.ValueType{i32, i32, i32}, + "fd", "flags", "result.fd", +) + +func sockAcceptFn(_ context.Context, mod api.Module, params []uint64) (errno sys.Errno) { + mem := mod.Memory() + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + fd := int32(params[0]) + flags := uint32(params[1]) + resultFd := uint32(params[2]) + nonblock := flags&uint32(wasip1.FD_NONBLOCK) != 0 + + var connFD int32 + if connFD, errno = fsc.SockAccept(fd, nonblock); errno == 0 { + mem.WriteUint32Le(resultFd, uint32(connFD)) + } + return +} + +// sockRecv is the WASI function named SockRecvName which receives a +// message from a socket. +// +// See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sock_recvfd-fd-ri_data-iovec_array-ri_flags-riflags---errno-size-roflags +var sockRecv = newHostFunc( + wasip1.SockRecvName, + sockRecvFn, + []wasm.ValueType{i32, i32, i32, i32, i32, i32}, + "fd", "ri_data", "ri_data_len", "ri_flags", "result.ro_datalen", "result.ro_flags", +) + +func sockRecvFn(_ context.Context, mod api.Module, params []uint64) sys.Errno { + mem := mod.Memory() + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + fd := int32(params[0]) + riData := uint32(params[1]) + riDataCount := uint32(params[2]) + riFlags := uint8(params[3]) + resultRoDatalen := uint32(params[4]) + resultRoFlags := uint32(params[5]) + + var conn socketapi.TCPConn + if e, ok := fsc.LookupFile(fd); !ok { + return sys.EBADF // Not open + } else if conn, ok = e.File.(socketapi.TCPConn); !ok { + return sys.EBADF // Not a conn + } + + if riFlags & ^(wasip1.RI_RECV_PEEK|wasip1.RI_RECV_WAITALL) != 0 { + return sys.ENOTSUP + } + + if riFlags&wasip1.RI_RECV_PEEK != 0 { + // Each record in riData is of the form: + // type iovec struct { buf *uint8; bufLen uint32 } + // This means that the first `uint32` is a `buf *uint8`. + firstIovecBufAddr, ok := mem.ReadUint32Le(riData) + if !ok { + return sys.EINVAL + } + // Read bufLen + firstIovecBufLen, ok := mem.ReadUint32Le(riData + 4) + if !ok { + return sys.EINVAL + } + firstIovecBuf, ok := mem.Read(firstIovecBufAddr, firstIovecBufLen) + if !ok { + return sys.EINVAL + } + n, err := conn.Recvfrom(firstIovecBuf, sysfs.MSG_PEEK) + if err != 0 { + return err + } + mem.WriteUint32Le(resultRoDatalen, uint32(n)) + mem.WriteUint16Le(resultRoFlags, 0) + return 0 + } + + // If riFlags&wasip1.RECV_WAITALL != 0 then we should + // do a blocking operation until all data has been retrieved; + // otherwise we are able to return earlier. + // For simplicity, we currently wait all regardless the flag. + bufSize, errno := readv(mem, riData, riDataCount, conn.Read) + if errno != 0 { + return errno + } + mem.WriteUint32Le(resultRoDatalen, bufSize) + mem.WriteUint16Le(resultRoFlags, 0) + return 0 +} + +// sockSend is the WASI function named SockSendName which sends a message +// on a socket. +// +// See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sock_sendfd-fd-si_data-ciovec_array-si_flags-siflags---errno-size +var sockSend = newHostFunc( + wasip1.SockSendName, + sockSendFn, + []wasm.ValueType{i32, i32, i32, i32, i32}, + "fd", "si_data", "si_data_len", "si_flags", "result.so_datalen", +) + +func sockSendFn(_ context.Context, mod api.Module, params []uint64) sys.Errno { + mem := mod.Memory() + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + fd := int32(params[0]) + siData := uint32(params[1]) + siDataCount := uint32(params[2]) + siFlags := uint32(params[3]) + resultSoDatalen := uint32(params[4]) + + if siFlags != 0 { + return sys.ENOTSUP + } + + var conn socketapi.TCPConn + if e, ok := fsc.LookupFile(fd); !ok { + return sys.EBADF // Not open + } else if conn, ok = e.File.(socketapi.TCPConn); !ok { + return sys.EBADF // Not a conn + } + + bufSize, errno := writev(mem, siData, siDataCount, conn.Write) + if errno != 0 { + return errno + } + mem.WriteUint32Le(resultSoDatalen, bufSize) + return 0 +} + +// sockShutdown is the WASI function named SockShutdownName which shuts +// down socket send and receive channels. +// +// See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sock_shutdownfd-fd-how-sdflags---errno +var sockShutdown = newHostFunc(wasip1.SockShutdownName, sockShutdownFn, []wasm.ValueType{i32, i32}, "fd", "how") + +func sockShutdownFn(_ context.Context, mod api.Module, params []uint64) sys.Errno { + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + + fd := int32(params[0]) + how := uint8(params[1]) + + var conn socketapi.TCPConn + if e, ok := fsc.LookupFile(fd); !ok { + return sys.EBADF // Not open + } else if conn, ok = e.File.(socketapi.TCPConn); !ok { + return sys.EBADF // Not a conn + } + + sysHow := 0 + + switch how { + case wasip1.SD_RD | wasip1.SD_WR: + sysHow = socketapi.SHUT_RD | socketapi.SHUT_WR + case wasip1.SD_RD: + sysHow = socketapi.SHUT_RD + case wasip1.SD_WR: + sysHow = socketapi.SHUT_WR + default: + return sys.EINVAL + } + + // TODO: Map this instead of relying on syscall symbols. + return conn.Shutdown(sysHow) +} diff --git a/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/wasi.go b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/wasi.go new file mode 100644 index 00000000000..4ef41d501c8 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1/wasi.go @@ -0,0 +1,314 @@ +// Package wasi_snapshot_preview1 contains Go-defined functions to access +// system calls, such as opening a file, similar to Go's x/sys package. These +// are accessible from WebAssembly-defined functions via importing ModuleName. +// All WASI functions return a single Errno result: ErrnoSuccess on success. +// +// e.g. Call Instantiate before instantiating any wasm binary that imports +// "wasi_snapshot_preview1", Otherwise, it will error due to missing imports. +// +// ctx := context.Background() +// r := wazero.NewRuntime(ctx) +// defer r.Close(ctx) // This closes everything this Runtime created. +// +// wasi_snapshot_preview1.MustInstantiate(ctx, r) +// mod, _ := r.Instantiate(ctx, wasm) +// +// See https://github.com/WebAssembly/WASI +package wasi_snapshot_preview1 + +import ( + "context" + "encoding/binary" + + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/wasip1" + "github.com/tetratelabs/wazero/internal/wasm" +) + +// ModuleName is the module name WASI functions are exported into. +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md +const ModuleName = wasip1.InternalModuleName + +const i32, i64 = wasm.ValueTypeI32, wasm.ValueTypeI64 + +var le = binary.LittleEndian + +// MustInstantiate calls Instantiate or panics on error. +// +// This is a simpler function for those who know the module ModuleName is not +// already instantiated, and don't need to unload it. +func MustInstantiate(ctx context.Context, r wazero.Runtime) { + if _, err := Instantiate(ctx, r); err != nil { + panic(err) + } +} + +// Instantiate instantiates the ModuleName module into the runtime. +// +// # Notes +// +// - Failure cases are documented on wazero.Runtime InstantiateModule. +// - Closing the wazero.Runtime has the same effect as closing the result. +func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) { + return NewBuilder(r).Instantiate(ctx) +} + +// Builder configures the ModuleName module for later use via Compile or Instantiate. +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +type Builder interface { + // Compile compiles the ModuleName module. Call this before Instantiate. + // + // Note: This has the same effect as the same function on wazero.HostModuleBuilder. + Compile(context.Context) (wazero.CompiledModule, error) + + // Instantiate instantiates the ModuleName module and returns a function to close it. + // + // Note: This has the same effect as the same function on wazero.HostModuleBuilder. + Instantiate(context.Context) (api.Closer, error) +} + +// NewBuilder returns a new Builder. +func NewBuilder(r wazero.Runtime) Builder { + return &builder{r} +} + +type builder struct{ r wazero.Runtime } + +// hostModuleBuilder returns a new wazero.HostModuleBuilder for ModuleName +func (b *builder) hostModuleBuilder() wazero.HostModuleBuilder { + ret := b.r.NewHostModuleBuilder(ModuleName) + exportFunctions(ret) + return ret +} + +// Compile implements Builder.Compile +func (b *builder) Compile(ctx context.Context) (wazero.CompiledModule, error) { + return b.hostModuleBuilder().Compile(ctx) +} + +// Instantiate implements Builder.Instantiate +func (b *builder) Instantiate(ctx context.Context) (api.Closer, error) { + return b.hostModuleBuilder().Instantiate(ctx) +} + +// FunctionExporter exports functions into a wazero.HostModuleBuilder. +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +type FunctionExporter interface { + ExportFunctions(wazero.HostModuleBuilder) +} + +// NewFunctionExporter returns a new FunctionExporter. This is used for the +// following two use cases: +// - Overriding a builtin function with an alternate implementation. +// - Exporting functions to the module "wasi_unstable" for legacy code. +// +// # Example of overriding default behavior +// +// // Export the default WASI functions. +// wasiBuilder := r.NewHostModuleBuilder(ModuleName) +// wasi_snapshot_preview1.NewFunctionExporter().ExportFunctions(wasiBuilder) +// +// // Subsequent calls to NewFunctionBuilder override built-in exports. +// wasiBuilder.NewFunctionBuilder(). +// WithFunc(func(ctx context.Context, mod api.Module, exitCode uint32) { +// // your custom logic +// }).Export("proc_exit") +// +// # Example of using the old module name for WASI +// +// // Instantiate the current WASI functions under the wasi_unstable +// // instead of wasi_snapshot_preview1. +// wasiBuilder := r.NewHostModuleBuilder("wasi_unstable") +// wasi_snapshot_preview1.NewFunctionExporter().ExportFunctions(wasiBuilder) +// _, err := wasiBuilder.Instantiate(testCtx, r) +func NewFunctionExporter() FunctionExporter { + return &functionExporter{} +} + +type functionExporter struct{} + +// ExportFunctions implements FunctionExporter.ExportFunctions +func (functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) { + exportFunctions(builder) +} + +// ## Translation notes +// ### String +// WebAssembly 1.0 has no string type, so any string input parameter expands to two uint32 parameters: offset +// and length. +// +// ### iovec_array +// `iovec_array` is encoded as two uin32le values (i32): offset and count. +// +// ### Result +// Each result besides Errno is always an uint32 parameter. WebAssembly 1.0 can have up to one result, +// which is already used by Errno. This forces other results to be parameters. A result parameter is a memory +// offset to write the result to. As memory offsets are uint32, each parameter representing a result is uint32. +// +// ### Errno +// The WASI specification is sometimes ambiguous resulting in some runtimes interpreting the same function ways. +// Errno mappings are not defined in WASI, yet, so these mappings are best efforts by maintainers. When in doubt +// about portability, first look at /RATIONALE.md and if needed an issue on +// https://github.com/WebAssembly/WASI/issues +// +// ## Memory +// In WebAssembly 1.0 (20191205), there may be up to one Memory per store, which means api.Memory is always the +// wasm.Store Memories index zero: `store.Memories[0].Buffer` +// +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md +// See https://github.com/WebAssembly/WASI/issues/215 +// See https://wwa.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0. + +// exportFunctions adds all go functions that implement wasi. +// These should be exported in the module named ModuleName. +func exportFunctions(builder wazero.HostModuleBuilder) { + exporter := builder.(wasm.HostFuncExporter) + + // Note: these are ordered per spec for consistency even if the resulting + // map can't guarantee that. + // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#functions + exporter.ExportHostFunc(argsGet) + exporter.ExportHostFunc(argsSizesGet) + exporter.ExportHostFunc(environGet) + exporter.ExportHostFunc(environSizesGet) + exporter.ExportHostFunc(clockResGet) + exporter.ExportHostFunc(clockTimeGet) + exporter.ExportHostFunc(fdAdvise) + exporter.ExportHostFunc(fdAllocate) + exporter.ExportHostFunc(fdClose) + exporter.ExportHostFunc(fdDatasync) + exporter.ExportHostFunc(fdFdstatGet) + exporter.ExportHostFunc(fdFdstatSetFlags) + exporter.ExportHostFunc(fdFdstatSetRights) + exporter.ExportHostFunc(fdFilestatGet) + exporter.ExportHostFunc(fdFilestatSetSize) + exporter.ExportHostFunc(fdFilestatSetTimes) + exporter.ExportHostFunc(fdPread) + exporter.ExportHostFunc(fdPrestatGet) + exporter.ExportHostFunc(fdPrestatDirName) + exporter.ExportHostFunc(fdPwrite) + exporter.ExportHostFunc(fdRead) + exporter.ExportHostFunc(fdReaddir) + exporter.ExportHostFunc(fdRenumber) + exporter.ExportHostFunc(fdSeek) + exporter.ExportHostFunc(fdSync) + exporter.ExportHostFunc(fdTell) + exporter.ExportHostFunc(fdWrite) + exporter.ExportHostFunc(pathCreateDirectory) + exporter.ExportHostFunc(pathFilestatGet) + exporter.ExportHostFunc(pathFilestatSetTimes) + exporter.ExportHostFunc(pathLink) + exporter.ExportHostFunc(pathOpen) + exporter.ExportHostFunc(pathReadlink) + exporter.ExportHostFunc(pathRemoveDirectory) + exporter.ExportHostFunc(pathRename) + exporter.ExportHostFunc(pathSymlink) + exporter.ExportHostFunc(pathUnlinkFile) + exporter.ExportHostFunc(pollOneoff) + exporter.ExportHostFunc(procExit) + exporter.ExportHostFunc(procRaise) + exporter.ExportHostFunc(schedYield) + exporter.ExportHostFunc(randomGet) + exporter.ExportHostFunc(sockAccept) + exporter.ExportHostFunc(sockRecv) + exporter.ExportHostFunc(sockSend) + exporter.ExportHostFunc(sockShutdown) +} + +// writeOffsetsAndNullTerminatedValues is used to write NUL-terminated values +// for args or environ, given a pre-defined bytesLen (which includes NUL +// terminators). +func writeOffsetsAndNullTerminatedValues(mem api.Memory, values [][]byte, offsets, bytes, bytesLen uint32) sys.Errno { + // The caller may not place bytes directly after offsets, so we have to + // read them independently. + valuesLen := len(values) + offsetsLen := uint32(valuesLen * 4) // uint32Le + offsetsBuf, ok := mem.Read(offsets, offsetsLen) + if !ok { + return sys.EFAULT + } + bytesBuf, ok := mem.Read(bytes, bytesLen) + if !ok { + return sys.EFAULT + } + + // Loop through the values, first writing the location of its data to + // offsetsBuf[oI], then its NUL-terminated data at bytesBuf[bI] + var oI, bI uint32 + for _, value := range values { + // Go can't guarantee inlining as there's not //go:inline directive. + // This inlines uint32 little-endian encoding instead. + bytesOffset := bytes + bI + offsetsBuf[oI] = byte(bytesOffset) + offsetsBuf[oI+1] = byte(bytesOffset >> 8) + offsetsBuf[oI+2] = byte(bytesOffset >> 16) + offsetsBuf[oI+3] = byte(bytesOffset >> 24) + oI += 4 // size of uint32 we just wrote + + // Write the next value to memory with a NUL terminator + copy(bytesBuf[bI:], value) + bI += uint32(len(value)) + bytesBuf[bI] = 0 // NUL terminator + bI++ + } + + return 0 +} + +func newHostFunc( + name string, + goFunc wasiFunc, + paramTypes []api.ValueType, + paramNames ...string, +) *wasm.HostFunc { + return &wasm.HostFunc{ + ExportName: name, + Name: name, + ParamTypes: paramTypes, + ParamNames: paramNames, + ResultTypes: []api.ValueType{i32}, + ResultNames: []string{"errno"}, + Code: wasm.Code{GoFunc: goFunc}, + } +} + +// wasiFunc special cases that all WASI functions return a single Errno +// result. The returned value will be written back to the stack at index zero. +type wasiFunc func(ctx context.Context, mod api.Module, params []uint64) sys.Errno + +// Call implements the same method as documented on api.GoModuleFunction. +func (f wasiFunc) Call(ctx context.Context, mod api.Module, stack []uint64) { + // Write the result back onto the stack + errno := f(ctx, mod, stack) + if errno != 0 { + stack[0] = uint64(wasip1.ToErrno(errno)) + } else { // special case ass ErrnoSuccess is zero + stack[0] = 0 + } +} + +// stubFunction stubs for GrainLang per #271. +func stubFunction(name string, paramTypes []wasm.ValueType, paramNames ...string) *wasm.HostFunc { + return &wasm.HostFunc{ + ExportName: name, + Name: name, + ParamTypes: paramTypes, + ParamNames: paramNames, + ResultTypes: []api.ValueType{i32}, + ResultNames: []string{"errno"}, + Code: wasm.Code{ + GoFunc: api.GoModuleFunc(func(_ context.Context, _ api.Module, stack []uint64) { stack[0] = uint64(wasip1.ErrnoNosys) }), + }, + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/descriptor/table.go b/vendor/github.com/tetratelabs/wazero/internal/descriptor/table.go new file mode 100644 index 00000000000..a20e19100a2 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/descriptor/table.go @@ -0,0 +1,149 @@ +package descriptor + +import ( + "math/bits" + "slices" +) + +// Table is a data structure mapping 32 bit descriptor to items. +// +// # Negative keys are invalid. +// +// Negative keys (e.g. -1) are invalid inputs and will return a corresponding +// not-found value. This matches POSIX behavior of file descriptors. +// See https://pubs.opengroup.org/onlinepubs/9699919799/functions/dirfd.html#tag_16_90 +// +// # Data structure design +// +// The data structure optimizes for memory density and lookup performance, +// trading off compute at insertion time. This is a useful compromise for the +// use cases we employ it with: items are usually accessed a lot more often +// than they are inserted, each operation requires a table lookup, so we are +// better off spending extra compute to insert items in the table in order to +// get cheaper lookups. Memory efficiency is also crucial to support scaling +// with programs that maintain thousands of items: having a high or non-linear +// memory-to-item ratio could otherwise be used as an attack vector by +// malicious applications attempting to damage performance of the host. +type Table[Key ~int32, Item any] struct { + masks []uint64 + items []Item +} + +// Len returns the number of items stored in the table. +func (t *Table[Key, Item]) Len() (n int) { + // We could make this a O(1) operation if we cached the number of items in + // the table. More state usually means more problems, so until we have a + // clear need for this, the simple implementation may be a better trade off. + for _, mask := range t.masks { + n += bits.OnesCount64(mask) + } + return n +} + +// grow grows the table by n * 64 items. +func (t *Table[Key, Item]) grow(n int) { + total := len(t.masks) + n + t.masks = slices.Grow(t.masks, n)[:total] + + total = len(t.items) + n*64 + t.items = slices.Grow(t.items, n*64)[:total] +} + +// Insert inserts the given item to the table, returning the key that it is +// mapped to or false if the table was full. +// +// The method does not perform deduplication, it is possible for the same item +// to be inserted multiple times, each insertion will return a different key. +func (t *Table[Key, Item]) Insert(item Item) (key Key, ok bool) { + offset := 0 +insert: + // Note: this loop could be made a lot more efficient using vectorized + // operations: 256 bits vector registers would yield a theoretical 4x + // speed up (e.g. using AVX2). + for index, mask := range t.masks[offset:] { + if ^mask != 0 { // not full? + shift := bits.TrailingZeros64(^mask) + index += offset + key = Key(index)*64 + Key(shift) + t.items[key] = item + t.masks[index] = mask | uint64(1<= 0 + } + } + + // No free slot found, grow the table and retry. + offset = len(t.masks) + t.grow(1) + goto insert +} + +// Lookup returns the item associated with the given key (may be nil). +func (t *Table[Key, Item]) Lookup(key Key) (item Item, found bool) { + if key < 0 { // invalid key + return + } + if i := int(key); i >= 0 && i < len(t.items) { + index := uint(key) / 64 + shift := uint(key) % 64 + if (t.masks[index] & (1 << shift)) != 0 { + item, found = t.items[i], true + } + } + return +} + +// InsertAt inserts the given `item` at the item descriptor `key`. This returns +// false if the insert was impossible due to negative key. +func (t *Table[Key, Item]) InsertAt(item Item, key Key) bool { + if key < 0 { + return false + } + index := uint(key) / 64 + if diff := int(index) - len(t.masks) + 1; diff > 0 { + t.grow(diff) + } + shift := uint(key) % 64 + t.masks[index] |= 1 << shift + t.items[key] = item + return true +} + +// Delete deletes the item stored at the given key from the table. +func (t *Table[Key, Item]) Delete(key Key) { + if key < 0 { // invalid key + return + } + if index := uint(key) / 64; int(index) < len(t.masks) { + shift := uint(key) % 64 + mask := t.masks[index] + if (mask & (1 << shift)) != 0 { + var zero Item + t.items[key] = zero + t.masks[index] = mask & ^uint64(1< 0 { + // We reserve the stack slots for result values below the return call frame slots. + if diff := c.sig.ResultNumInUint64 - c.sig.ParamNumInUint64; diff > 0 { + current += diff + } + } + + // Non-func param locals Start after the return call frame. + current += c.callFrameStackSizeInUint64 + + for _, lt := range c.localTypes { + c.localIndexToStackHeightInUint64 = append(c.localIndexToStackHeightInUint64, current) + if lt == wasm.ValueTypeV128 { + current++ + } + current++ + } + + // Push function arguments. + for _, t := range c.sig.Params { + c.stackPush(wasmValueTypeTounsignedType(t)) + } + + if c.callFrameStackSizeInUint64 > 0 { + // Reserve the stack slots for results. + for i := 0; i < c.sig.ResultNumInUint64-c.sig.ParamNumInUint64; i++ { + c.stackPush(unsignedTypeI64) + } + + // Reserve the stack slots for call frame. + for i := 0; i < c.callFrameStackSizeInUint64; i++ { + c.stackPush(unsignedTypeI64) + } + } +} + +// compiler is in charge of lowering raw Wasm function body to get compilationResult. +// This is created per *wasm.Module and reused for all functions in it to reduce memory allocations. +type compiler struct { + module *wasm.Module + enabledFeatures api.CoreFeatures + callFrameStackSizeInUint64 int + stack []unsignedType + // stackLenInUint64 is the length of the stack in uint64. + stackLenInUint64 int + currentFrameID uint32 + controlFrames controlFrames + unreachableState struct { + on bool + depth int + } + pc, currentOpPC uint64 + result compilationResult + + // body holds the code for the function's body where Wasm instructions are stored. + body []byte + // sig is the function type of the target function. + sig *wasm.FunctionType + // localTypes holds the target function locals' value types except function params. + localTypes []wasm.ValueType + // localIndexToStackHeightInUint64 maps the local index (starting with function params) to the stack height + // where the local is places. This is the necessary mapping for functions who contain vector type locals. + localIndexToStackHeightInUint64 []int + + // types hold all the function types in the module where the targe function exists. + types []wasm.FunctionType + // funcs holds the type indexes for all declared functions in the module where the target function exists. + funcs []uint32 + // globals holds the global types for all declared globals in the module where the target function exists. + globals []wasm.GlobalType + + // needSourceOffset is true if this module requires DWARF based stack trace. + needSourceOffset bool + // bodyOffsetInCodeSection is the offset of the body of this function in the original Wasm binary's code section. + bodyOffsetInCodeSection uint64 + + ensureTermination bool + // Pre-allocated bytes.Reader to be used in various places. + br *bytes.Reader + funcTypeToSigs funcTypeToIRSignatures + + next int +} + +//lint:ignore U1000 for debugging only. +func (c *compiler) stackDump() string { + strs := make([]string, 0, len(c.stack)) + for _, s := range c.stack { + strs = append(strs, s.String()) + } + return "[" + strings.Join(strs, ", ") + "]" +} + +func (c *compiler) markUnreachable() { + c.unreachableState.on = true +} + +func (c *compiler) resetUnreachable() { + c.unreachableState.on = false +} + +// memoryType is the type of memory in a compiled module. +type memoryType byte + +const ( + // memoryTypeNone indicates there is no memory. + memoryTypeNone memoryType = iota + // memoryTypeStandard indicates there is a non-shared memory. + memoryTypeStandard + // memoryTypeShared indicates there is a shared memory. + memoryTypeShared +) + +type compilationResult struct { + // Operations holds interpreterir operations compiled from Wasm instructions in a Wasm function. + Operations []unionOperation + + // IROperationSourceOffsetsInWasmBinary is index-correlated with Operation and maps each operation to the corresponding source instruction's + // offset in the original WebAssembly binary. + // Non nil only when the given Wasm module has the DWARF section. + IROperationSourceOffsetsInWasmBinary []uint64 + + // LabelCallers maps label to the number of callers to that label. + // Here "callers" means that the call-sites which jumps to the label with br, br_if or br_table + // instructions. + // + // Note: zero possible and allowed in wasm. e.g. + // + // (block + // (br 0) + // (block i32.const 1111) + // ) + // + // This example the label corresponding to `(block i32.const 1111)` is never be reached at runtime because `br 0` exits the function before we reach there + LabelCallers map[label]uint32 + // UsesMemory is true if this function might use memory. + UsesMemory bool + + // The following fields are per-module values, not per-function. + + // Globals holds all the declarations of globals in the module from which this function is compiled. + Globals []wasm.GlobalType + // Functions holds all the declarations of function in the module from which this function is compiled, including itself. + Functions []wasm.Index + // Types holds all the types in the module from which this function is compiled. + Types []wasm.FunctionType + // Memory indicates the type of memory of the module. + Memory memoryType + // HasTable is true if the module from which this function is compiled has table declaration. + HasTable bool + // HasDataInstances is true if the module has data instances which might be used by memory.init or data.drop instructions. + HasDataInstances bool + // HasDataInstances is true if the module has element instances which might be used by table.init or elem.drop instructions. + HasElementInstances bool +} + +// newCompiler returns the new *compiler for the given parameters. +// Use compiler.Next function to get compilation result per function. +func newCompiler(enabledFeatures api.CoreFeatures, callFrameStackSizeInUint64 int, module *wasm.Module, ensureTermination bool) (*compiler, error) { + functions, globals, mem, tables, err := module.AllDeclarations() + if err != nil { + return nil, err + } + + hasTable, hasDataInstances, hasElementInstances := len(tables) > 0, + len(module.DataSection) > 0, len(module.ElementSection) > 0 + + var mt memoryType + switch { + case mem == nil: + mt = memoryTypeNone + case mem.IsShared: + mt = memoryTypeShared + default: + mt = memoryTypeStandard + } + + types := module.TypeSection + + c := &compiler{ + module: module, + enabledFeatures: enabledFeatures, + controlFrames: controlFrames{}, + callFrameStackSizeInUint64: callFrameStackSizeInUint64, + result: compilationResult{ + Globals: globals, + Functions: functions, + Types: types, + Memory: mt, + HasTable: hasTable, + HasDataInstances: hasDataInstances, + HasElementInstances: hasElementInstances, + LabelCallers: map[label]uint32{}, + }, + globals: globals, + funcs: functions, + types: types, + ensureTermination: ensureTermination, + br: bytes.NewReader(nil), + funcTypeToSigs: funcTypeToIRSignatures{ + indirectCalls: make([]*signature, len(types)), + directCalls: make([]*signature, len(types)), + wasmTypes: types, + }, + needSourceOffset: module.DWARFLines != nil, + } + return c, nil +} + +// Next returns the next compilationResult for this compiler. +func (c *compiler) Next() (*compilationResult, error) { + funcIndex := c.next + code := &c.module.CodeSection[funcIndex] + sig := &c.types[c.module.FunctionSection[funcIndex]] + + // Reset the previous result. + c.result.Operations = c.result.Operations[:0] + c.result.IROperationSourceOffsetsInWasmBinary = c.result.IROperationSourceOffsetsInWasmBinary[:0] + c.result.UsesMemory = false + // Clears the existing entries in LabelCallers. + for frameID := uint32(0); frameID <= c.currentFrameID; frameID++ { + for k := labelKind(0); k < labelKindNum; k++ { + delete(c.result.LabelCallers, newLabel(k, frameID)) + } + } + // Reset the previous states. + c.pc = 0 + c.currentOpPC = 0 + c.currentFrameID = 0 + c.stackLenInUint64 = 0 + c.unreachableState.on, c.unreachableState.depth = false, 0 + + if err := c.compile(sig, code.Body, code.LocalTypes, code.BodyOffsetInCodeSection); err != nil { + return nil, err + } + c.next++ + return &c.result, nil +} + +// Compile lowers given function instance into interpreterir operations +// so that the resulting operations can be consumed by the interpreter +// or the compiler compilation engine. +func (c *compiler) compile(sig *wasm.FunctionType, body []byte, localTypes []wasm.ValueType, bodyOffsetInCodeSection uint64) error { + // Set function specific fields. + c.body = body + c.localTypes = localTypes + c.sig = sig + c.bodyOffsetInCodeSection = bodyOffsetInCodeSection + + // Reuses the underlying slices. + c.stack = c.stack[:0] + c.controlFrames.frames = c.controlFrames.frames[:0] + + c.initializeStack() + + // Emit const expressions for locals. + // Note that here we don't take function arguments + // into account, meaning that callers must push + // arguments before entering into the function body. + for _, t := range c.localTypes { + c.emitDefaultValue(t) + } + + // Insert the function control frame. + c.controlFrames.push(controlFrame{ + frameID: c.nextFrameID(), + blockType: c.sig, + kind: controlFrameKindFunction, + }) + + // Now, enter the function body. + for !c.controlFrames.empty() && c.pc < uint64(len(c.body)) { + if err := c.handleInstruction(); err != nil { + return fmt.Errorf("handling instruction: %w", err) + } + } + return nil +} + +// Translate the current Wasm instruction to interpreterir's operations, +// and emit the results into c.results. +func (c *compiler) handleInstruction() error { + op := c.body[c.pc] + c.currentOpPC = c.pc + if false { + var instName string + if op == wasm.OpcodeVecPrefix { + instName = wasm.VectorInstructionName(c.body[c.pc+1]) + } else if op == wasm.OpcodeAtomicPrefix { + instName = wasm.AtomicInstructionName(c.body[c.pc+1]) + } else if op == wasm.OpcodeMiscPrefix { + instName = wasm.MiscInstructionName(c.body[c.pc+1]) + } else { + instName = wasm.InstructionName(op) + } + fmt.Printf("handling %s, unreachable_state(on=%v,depth=%d), stack=%v\n", + instName, c.unreachableState.on, c.unreachableState.depth, c.stack, + ) + } + + var peekValueType unsignedType + if len(c.stack) > 0 { + peekValueType = c.stackPeek() + } + + // Modify the stack according the current instruction. + // Note that some instructions will read "index" in + // applyToStack and advance c.pc inside the function. + index, err := c.applyToStack(op) + if err != nil { + return fmt.Errorf("apply stack failed for %s: %w", wasm.InstructionName(op), err) + } + // Now we handle each instruction, and + // emit the corresponding interpreterir operations to the results. +operatorSwitch: + switch op { + case wasm.OpcodeUnreachable: + c.emit(newOperationUnreachable()) + c.markUnreachable() + case wasm.OpcodeNop: + // Nop is noop! + case wasm.OpcodeBlock: + c.br.Reset(c.body[c.pc+1:]) + bt, num, err := wasm.DecodeBlockType(c.types, c.br, c.enabledFeatures) + if err != nil { + return fmt.Errorf("reading block type for block instruction: %w", err) + } + c.pc += num + + if c.unreachableState.on { + // If it is currently in unreachable, + // just remove the entire block. + c.unreachableState.depth++ + break operatorSwitch + } + + // Create a new frame -- entering this block. + frame := controlFrame{ + frameID: c.nextFrameID(), + originalStackLenWithoutParam: len(c.stack) - len(bt.Params), + originalStackLenWithoutParamUint64: c.stackLenInUint64 - bt.ParamNumInUint64, + kind: controlFrameKindBlockWithoutContinuationLabel, + blockType: bt, + } + c.controlFrames.push(frame) + + case wasm.OpcodeLoop: + c.br.Reset(c.body[c.pc+1:]) + bt, num, err := wasm.DecodeBlockType(c.types, c.br, c.enabledFeatures) + if err != nil { + return fmt.Errorf("reading block type for loop instruction: %w", err) + } + c.pc += num + + if c.unreachableState.on { + // If it is currently in unreachable, + // just remove the entire block. + c.unreachableState.depth++ + break operatorSwitch + } + + // Create a new frame -- entering loop. + frame := controlFrame{ + frameID: c.nextFrameID(), + originalStackLenWithoutParam: len(c.stack) - len(bt.Params), + originalStackLenWithoutParamUint64: c.stackLenInUint64 - bt.ParamNumInUint64, + kind: controlFrameKindLoop, + blockType: bt, + } + c.controlFrames.push(frame) + + // Prep labels for inside and the continuation of this loop. + loopLabel := newLabel(labelKindHeader, frame.frameID) + c.result.LabelCallers[loopLabel]++ + + // Emit the branch operation to enter inside the loop. + c.emit(newOperationBr(loopLabel)) + c.emit(newOperationLabel(loopLabel)) + + // Insert the exit code check on the loop header, which is the only necessary point in the function body + // to prevent infinite loop. + // + // Note that this is a little aggressive: this checks the exit code regardless the loop header is actually + // the loop. In other words, this checks even when no br/br_if/br_table instructions jumping to this loop + // exist. However, in reality, that shouldn't be an issue since such "noop" loop header will highly likely be + // optimized out by almost all guest language compilers which have the control flow optimization passes. + if c.ensureTermination { + c.emit(newOperationBuiltinFunctionCheckExitCode()) + } + case wasm.OpcodeIf: + c.br.Reset(c.body[c.pc+1:]) + bt, num, err := wasm.DecodeBlockType(c.types, c.br, c.enabledFeatures) + if err != nil { + return fmt.Errorf("reading block type for if instruction: %w", err) + } + c.pc += num + + if c.unreachableState.on { + // If it is currently in unreachable, + // just remove the entire block. + c.unreachableState.depth++ + break operatorSwitch + } + + // Create a new frame -- entering if. + frame := controlFrame{ + frameID: c.nextFrameID(), + originalStackLenWithoutParam: len(c.stack) - len(bt.Params), + originalStackLenWithoutParamUint64: c.stackLenInUint64 - bt.ParamNumInUint64, + // Note this will be set to controlFrameKindIfWithElse + // when else opcode found later. + kind: controlFrameKindIfWithoutElse, + blockType: bt, + } + c.controlFrames.push(frame) + + // Prep labels for if and else of this if. + thenLabel := newLabel(labelKindHeader, frame.frameID) + elseLabel := newLabel(labelKindElse, frame.frameID) + c.result.LabelCallers[thenLabel]++ + c.result.LabelCallers[elseLabel]++ + + // Emit the branch operation to enter the then block. + c.emit(newOperationBrIf(thenLabel, elseLabel, nopinclusiveRange)) + c.emit(newOperationLabel(thenLabel)) + case wasm.OpcodeElse: + frame := c.controlFrames.top() + if c.unreachableState.on && c.unreachableState.depth > 0 { + // If it is currently in unreachable, and the nested if, + // just remove the entire else block. + break operatorSwitch + } else if c.unreachableState.on { + // If it is currently in unreachable, and the non-nested if, + // reset the stack so we can correctly handle the else block. + top := c.controlFrames.top() + c.stackSwitchAt(top) + top.kind = controlFrameKindIfWithElse + + // Re-push the parameters to the if block so that else block can use them. + for _, t := range frame.blockType.Params { + c.stackPush(wasmValueTypeTounsignedType(t)) + } + + // We are no longer unreachable in else frame, + // so emit the correct label, and reset the unreachable state. + elseLabel := newLabel(labelKindElse, frame.frameID) + c.resetUnreachable() + c.emit( + newOperationLabel(elseLabel), + ) + break operatorSwitch + } + + // Change the Kind of this If block, indicating that + // the if has else block. + frame.kind = controlFrameKindIfWithElse + + // We need to reset the stack so that + // the values pushed inside the then block + // do not affect the else block. + dropOp := newOperationDrop(c.getFrameDropRange(frame, false)) + + // Reset the stack manipulated by the then block, and re-push the block param types to the stack. + + c.stackSwitchAt(frame) + for _, t := range frame.blockType.Params { + c.stackPush(wasmValueTypeTounsignedType(t)) + } + + // Prep labels for else and the continuation of this if block. + elseLabel := newLabel(labelKindElse, frame.frameID) + continuationLabel := newLabel(labelKindContinuation, frame.frameID) + c.result.LabelCallers[continuationLabel]++ + + // Emit the instructions for exiting the if loop, + // and then the initiation of else block. + c.emit(dropOp) + // Jump to the continuation of this block. + c.emit(newOperationBr(continuationLabel)) + // Initiate the else block. + c.emit(newOperationLabel(elseLabel)) + case wasm.OpcodeEnd: + if c.unreachableState.on && c.unreachableState.depth > 0 { + c.unreachableState.depth-- + break operatorSwitch + } else if c.unreachableState.on { + c.resetUnreachable() + + frame := c.controlFrames.pop() + if c.controlFrames.empty() { + return nil + } + + c.stackSwitchAt(frame) + for _, t := range frame.blockType.Results { + c.stackPush(wasmValueTypeTounsignedType(t)) + } + + continuationLabel := newLabel(labelKindContinuation, frame.frameID) + if frame.kind == controlFrameKindIfWithoutElse { + // Emit the else label. + elseLabel := newLabel(labelKindElse, frame.frameID) + c.result.LabelCallers[continuationLabel]++ + c.emit(newOperationLabel(elseLabel)) + c.emit(newOperationBr(continuationLabel)) + c.emit(newOperationLabel(continuationLabel)) + } else { + c.emit( + newOperationLabel(continuationLabel), + ) + } + + break operatorSwitch + } + + frame := c.controlFrames.pop() + + // We need to reset the stack so that + // the values pushed inside the block. + dropOp := newOperationDrop(c.getFrameDropRange(frame, true)) + c.stackSwitchAt(frame) + + // Push the result types onto the stack. + for _, t := range frame.blockType.Results { + c.stackPush(wasmValueTypeTounsignedType(t)) + } + + // Emit the instructions according to the Kind of the current control frame. + switch frame.kind { + case controlFrameKindFunction: + if !c.controlFrames.empty() { + // Should never happen. If so, there's a bug in the translation. + panic("bug: found more function control frames") + } + // Return from function. + c.emit(dropOp) + c.emit(newOperationBr(newLabel(labelKindReturn, 0))) + case controlFrameKindIfWithoutElse: + // This case we have to emit "empty" else label. + elseLabel := newLabel(labelKindElse, frame.frameID) + continuationLabel := newLabel(labelKindContinuation, frame.frameID) + c.result.LabelCallers[continuationLabel] += 2 + c.emit(dropOp) + c.emit(newOperationBr(continuationLabel)) + // Emit the else which soon branches into the continuation. + c.emit(newOperationLabel(elseLabel)) + c.emit(newOperationBr(continuationLabel)) + // Initiate the continuation. + c.emit(newOperationLabel(continuationLabel)) + case controlFrameKindBlockWithContinuationLabel, + controlFrameKindIfWithElse: + continuationLabel := newLabel(labelKindContinuation, frame.frameID) + c.result.LabelCallers[continuationLabel]++ + c.emit(dropOp) + c.emit(newOperationBr(continuationLabel)) + c.emit(newOperationLabel(continuationLabel)) + case controlFrameKindLoop, controlFrameKindBlockWithoutContinuationLabel: + c.emit( + dropOp, + ) + default: + // Should never happen. If so, there's a bug in the translation. + panic(fmt.Errorf("bug: invalid control frame Kind: 0x%x", frame.kind)) + } + + case wasm.OpcodeBr: + targetIndex, n, err := leb128.LoadUint32(c.body[c.pc+1:]) + if err != nil { + return fmt.Errorf("read the target for br_if: %w", err) + } + c.pc += n + + if c.unreachableState.on { + // If it is currently in unreachable, br is no-op. + break operatorSwitch + } + + targetFrame := c.controlFrames.get(int(targetIndex)) + targetFrame.ensureContinuation() + dropOp := newOperationDrop(c.getFrameDropRange(targetFrame, false)) + targetID := targetFrame.asLabel() + c.result.LabelCallers[targetID]++ + c.emit(dropOp) + c.emit(newOperationBr(targetID)) + // Br operation is stack-polymorphic, and mark the state as unreachable. + // That means subsequent instructions in the current control frame are "unreachable" + // and can be safely removed. + c.markUnreachable() + case wasm.OpcodeBrIf: + targetIndex, n, err := leb128.LoadUint32(c.body[c.pc+1:]) + if err != nil { + return fmt.Errorf("read the target for br_if: %w", err) + } + c.pc += n + + if c.unreachableState.on { + // If it is currently in unreachable, br-if is no-op. + break operatorSwitch + } + + targetFrame := c.controlFrames.get(int(targetIndex)) + targetFrame.ensureContinuation() + drop := c.getFrameDropRange(targetFrame, false) + target := targetFrame.asLabel() + c.result.LabelCallers[target]++ + + continuationLabel := newLabel(labelKindHeader, c.nextFrameID()) + c.result.LabelCallers[continuationLabel]++ + c.emit(newOperationBrIf(target, continuationLabel, drop)) + // Start emitting else block operations. + c.emit(newOperationLabel(continuationLabel)) + case wasm.OpcodeBrTable: + c.br.Reset(c.body[c.pc+1:]) + r := c.br + numTargets, n, err := leb128.DecodeUint32(r) + if err != nil { + return fmt.Errorf("error reading number of targets in br_table: %w", err) + } + c.pc += n + + if c.unreachableState.on { + // If it is currently in unreachable, br_table is no-op. + // But before proceeding to the next instruction, we must advance the pc + // according to the number of br_table targets. + for i := uint32(0); i <= numTargets; i++ { // inclusive as we also need to read the index of default target. + _, n, err := leb128.DecodeUint32(r) + if err != nil { + return fmt.Errorf("error reading target %d in br_table: %w", i, err) + } + c.pc += n + } + break operatorSwitch + } + + // Read the branch targets. + s := numTargets * 2 + targetLabels := make([]uint64, 2+s) // (label, inclusiveRange) * (default+numTargets) + for i := uint32(0); i < s; i += 2 { + l, n, err := leb128.DecodeUint32(r) + if err != nil { + return fmt.Errorf("error reading target %d in br_table: %w", i, err) + } + c.pc += n + targetFrame := c.controlFrames.get(int(l)) + targetFrame.ensureContinuation() + drop := c.getFrameDropRange(targetFrame, false) + targetLabel := targetFrame.asLabel() + targetLabels[i] = uint64(targetLabel) + targetLabels[i+1] = drop.AsU64() + c.result.LabelCallers[targetLabel]++ + } + + // Prep default target control frame. + l, n, err := leb128.DecodeUint32(r) + if err != nil { + return fmt.Errorf("error reading default target of br_table: %w", err) + } + c.pc += n + defaultTargetFrame := c.controlFrames.get(int(l)) + defaultTargetFrame.ensureContinuation() + defaultTargetDrop := c.getFrameDropRange(defaultTargetFrame, false) + defaultLabel := defaultTargetFrame.asLabel() + c.result.LabelCallers[defaultLabel]++ + targetLabels[s] = uint64(defaultLabel) + targetLabels[s+1] = defaultTargetDrop.AsU64() + c.emit(newOperationBrTable(targetLabels)) + + // br_table operation is stack-polymorphic, and mark the state as unreachable. + // That means subsequent instructions in the current control frame are "unreachable" + // and can be safely removed. + c.markUnreachable() + case wasm.OpcodeReturn: + functionFrame := c.controlFrames.functionFrame() + dropOp := newOperationDrop(c.getFrameDropRange(functionFrame, false)) + + // Cleanup the stack and then jmp to function frame's continuation (meaning return). + c.emit(dropOp) + c.emit(newOperationBr(functionFrame.asLabel())) + + // Return operation is stack-polymorphic, and mark the state as unreachable. + // That means subsequent instructions in the current control frame are "unreachable" + // and can be safely removed. + c.markUnreachable() + case wasm.OpcodeCall: + c.emit( + newOperationCall(index), + ) + case wasm.OpcodeCallIndirect: + typeIndex := index + tableIndex, n, err := leb128.LoadUint32(c.body[c.pc+1:]) + if err != nil { + return fmt.Errorf("read target for br_table: %w", err) + } + c.pc += n + c.emit( + newOperationCallIndirect(typeIndex, tableIndex), + ) + + case wasm.OpcodeDrop: + r := inclusiveRange{Start: 0, End: 0} + if peekValueType == unsignedTypeV128 { + // inclusiveRange is the range in uint64 representation, so dropping a vector value on top + // should be translated as drop [0..1] inclusively. + r.End++ + } + c.emit(newOperationDrop(r)) + case wasm.OpcodeSelect: + // If it is on the unreachable state, ignore the instruction. + if c.unreachableState.on { + break operatorSwitch + } + isTargetVector := c.stackPeek() == unsignedTypeV128 + c.emit( + newOperationSelect(isTargetVector), + ) + case wasm.OpcodeTypedSelect: + // Skips two bytes: vector size fixed to 1, and the value type for select. + c.pc += 2 + // If it is on the unreachable state, ignore the instruction. + if c.unreachableState.on { + break operatorSwitch + } + // Typed select is semantically equivalent to select at runtime. + isTargetVector := c.stackPeek() == unsignedTypeV128 + c.emit( + newOperationSelect(isTargetVector), + ) + case wasm.OpcodeLocalGet: + depth := c.localDepth(index) + if isVector := c.localType(index) == wasm.ValueTypeV128; !isVector { + c.emit( + // -1 because we already manipulated the stack before + // called localDepth ^^. + newOperationPick(depth-1, isVector), + ) + } else { + c.emit( + // -2 because we already manipulated the stack before + // called localDepth ^^. + newOperationPick(depth-2, isVector), + ) + } + case wasm.OpcodeLocalSet: + depth := c.localDepth(index) + + isVector := c.localType(index) == wasm.ValueTypeV128 + if isVector { + c.emit( + // +2 because we already popped the operands for this operation from the c.stack before + // called localDepth ^^, + newOperationSet(depth+2, isVector), + ) + } else { + c.emit( + // +1 because we already popped the operands for this operation from the c.stack before + // called localDepth ^^, + newOperationSet(depth+1, isVector), + ) + } + case wasm.OpcodeLocalTee: + depth := c.localDepth(index) + isVector := c.localType(index) == wasm.ValueTypeV128 + if isVector { + c.emit(newOperationPick(1, isVector)) + c.emit(newOperationSet(depth+2, isVector)) + } else { + c.emit( + newOperationPick(0, isVector)) + c.emit(newOperationSet(depth+1, isVector)) + } + case wasm.OpcodeGlobalGet: + c.emit( + newOperationGlobalGet(index), + ) + case wasm.OpcodeGlobalSet: + c.emit( + newOperationGlobalSet(index), + ) + case wasm.OpcodeI32Load: + imm, err := c.readMemoryArg(wasm.OpcodeI32LoadName) + if err != nil { + return err + } + c.emit(newOperationLoad(unsignedTypeI32, imm)) + case wasm.OpcodeI64Load: + imm, err := c.readMemoryArg(wasm.OpcodeI64LoadName) + if err != nil { + return err + } + c.emit(newOperationLoad(unsignedTypeI64, imm)) + case wasm.OpcodeF32Load: + imm, err := c.readMemoryArg(wasm.OpcodeF32LoadName) + if err != nil { + return err + } + c.emit(newOperationLoad(unsignedTypeF32, imm)) + case wasm.OpcodeF64Load: + imm, err := c.readMemoryArg(wasm.OpcodeF64LoadName) + if err != nil { + return err + } + c.emit(newOperationLoad(unsignedTypeF64, imm)) + case wasm.OpcodeI32Load8S: + imm, err := c.readMemoryArg(wasm.OpcodeI32Load8SName) + if err != nil { + return err + } + c.emit(newOperationLoad8(signedInt32, imm)) + case wasm.OpcodeI32Load8U: + imm, err := c.readMemoryArg(wasm.OpcodeI32Load8UName) + if err != nil { + return err + } + c.emit(newOperationLoad8(signedUint32, imm)) + case wasm.OpcodeI32Load16S: + imm, err := c.readMemoryArg(wasm.OpcodeI32Load16SName) + if err != nil { + return err + } + c.emit(newOperationLoad16(signedInt32, imm)) + case wasm.OpcodeI32Load16U: + imm, err := c.readMemoryArg(wasm.OpcodeI32Load16UName) + if err != nil { + return err + } + c.emit(newOperationLoad16(signedUint32, imm)) + case wasm.OpcodeI64Load8S: + imm, err := c.readMemoryArg(wasm.OpcodeI64Load8SName) + if err != nil { + return err + } + c.emit(newOperationLoad8(signedInt64, imm)) + case wasm.OpcodeI64Load8U: + imm, err := c.readMemoryArg(wasm.OpcodeI64Load8UName) + if err != nil { + return err + } + c.emit(newOperationLoad8(signedUint64, imm)) + case wasm.OpcodeI64Load16S: + imm, err := c.readMemoryArg(wasm.OpcodeI64Load16SName) + if err != nil { + return err + } + c.emit(newOperationLoad16(signedInt64, imm)) + case wasm.OpcodeI64Load16U: + imm, err := c.readMemoryArg(wasm.OpcodeI64Load16UName) + if err != nil { + return err + } + c.emit(newOperationLoad16(signedUint64, imm)) + case wasm.OpcodeI64Load32S: + imm, err := c.readMemoryArg(wasm.OpcodeI64Load32SName) + if err != nil { + return err + } + c.emit(newOperationLoad32(true, imm)) + case wasm.OpcodeI64Load32U: + imm, err := c.readMemoryArg(wasm.OpcodeI64Load32UName) + if err != nil { + return err + } + c.emit(newOperationLoad32(false, imm)) + case wasm.OpcodeI32Store: + imm, err := c.readMemoryArg(wasm.OpcodeI32StoreName) + if err != nil { + return err + } + c.emit( + newOperationStore(unsignedTypeI32, imm), + ) + case wasm.OpcodeI64Store: + imm, err := c.readMemoryArg(wasm.OpcodeI64StoreName) + if err != nil { + return err + } + c.emit( + newOperationStore(unsignedTypeI64, imm), + ) + case wasm.OpcodeF32Store: + imm, err := c.readMemoryArg(wasm.OpcodeF32StoreName) + if err != nil { + return err + } + c.emit( + newOperationStore(unsignedTypeF32, imm), + ) + case wasm.OpcodeF64Store: + imm, err := c.readMemoryArg(wasm.OpcodeF64StoreName) + if err != nil { + return err + } + c.emit( + newOperationStore(unsignedTypeF64, imm), + ) + case wasm.OpcodeI32Store8: + imm, err := c.readMemoryArg(wasm.OpcodeI32Store8Name) + if err != nil { + return err + } + c.emit( + newOperationStore8(imm), + ) + case wasm.OpcodeI32Store16: + imm, err := c.readMemoryArg(wasm.OpcodeI32Store16Name) + if err != nil { + return err + } + c.emit( + newOperationStore16(imm), + ) + case wasm.OpcodeI64Store8: + imm, err := c.readMemoryArg(wasm.OpcodeI64Store8Name) + if err != nil { + return err + } + c.emit( + newOperationStore8(imm), + ) + case wasm.OpcodeI64Store16: + imm, err := c.readMemoryArg(wasm.OpcodeI64Store16Name) + if err != nil { + return err + } + c.emit( + newOperationStore16(imm), + ) + case wasm.OpcodeI64Store32: + imm, err := c.readMemoryArg(wasm.OpcodeI64Store32Name) + if err != nil { + return err + } + c.emit( + newOperationStore32(imm), + ) + case wasm.OpcodeMemorySize: + c.result.UsesMemory = true + c.pc++ // Skip the reserved one byte. + c.emit( + newOperationMemorySize(), + ) + case wasm.OpcodeMemoryGrow: + c.result.UsesMemory = true + c.pc++ // Skip the reserved one byte. + c.emit( + newOperationMemoryGrow(), + ) + case wasm.OpcodeI32Const: + val, num, err := leb128.LoadInt32(c.body[c.pc+1:]) + if err != nil { + return fmt.Errorf("reading i32.const value: %v", err) + } + c.pc += num + c.emit( + newOperationConstI32(uint32(val)), + ) + case wasm.OpcodeI64Const: + val, num, err := leb128.LoadInt64(c.body[c.pc+1:]) + if err != nil { + return fmt.Errorf("reading i64.const value: %v", err) + } + c.pc += num + c.emit( + newOperationConstI64(uint64(val)), + ) + case wasm.OpcodeF32Const: + v := math.Float32frombits(binary.LittleEndian.Uint32(c.body[c.pc+1:])) + c.pc += 4 + c.emit( + newOperationConstF32(v), + ) + case wasm.OpcodeF64Const: + v := math.Float64frombits(binary.LittleEndian.Uint64(c.body[c.pc+1:])) + c.pc += 8 + c.emit( + newOperationConstF64(v), + ) + case wasm.OpcodeI32Eqz: + c.emit( + newOperationEqz(unsignedInt32), + ) + case wasm.OpcodeI32Eq: + c.emit( + newOperationEq(unsignedTypeI32), + ) + case wasm.OpcodeI32Ne: + c.emit( + newOperationNe(unsignedTypeI32), + ) + case wasm.OpcodeI32LtS: + c.emit( + newOperationLt(signedTypeInt32), + ) + case wasm.OpcodeI32LtU: + c.emit( + newOperationLt(signedTypeUint32), + ) + case wasm.OpcodeI32GtS: + c.emit( + newOperationGt(signedTypeInt32), + ) + case wasm.OpcodeI32GtU: + c.emit( + newOperationGt(signedTypeUint32), + ) + case wasm.OpcodeI32LeS: + c.emit( + newOperationLe(signedTypeInt32), + ) + case wasm.OpcodeI32LeU: + c.emit( + newOperationLe(signedTypeUint32), + ) + case wasm.OpcodeI32GeS: + c.emit( + newOperationGe(signedTypeInt32), + ) + case wasm.OpcodeI32GeU: + c.emit( + newOperationGe(signedTypeUint32), + ) + case wasm.OpcodeI64Eqz: + c.emit( + newOperationEqz(unsignedInt64), + ) + case wasm.OpcodeI64Eq: + c.emit( + newOperationEq(unsignedTypeI64), + ) + case wasm.OpcodeI64Ne: + c.emit( + newOperationNe(unsignedTypeI64), + ) + case wasm.OpcodeI64LtS: + c.emit( + newOperationLt(signedTypeInt64), + ) + case wasm.OpcodeI64LtU: + c.emit( + newOperationLt(signedTypeUint64), + ) + case wasm.OpcodeI64GtS: + c.emit( + newOperationGt(signedTypeInt64), + ) + case wasm.OpcodeI64GtU: + c.emit( + newOperationGt(signedTypeUint64), + ) + case wasm.OpcodeI64LeS: + c.emit( + newOperationLe(signedTypeInt64), + ) + case wasm.OpcodeI64LeU: + c.emit( + newOperationLe(signedTypeUint64), + ) + case wasm.OpcodeI64GeS: + c.emit( + newOperationGe(signedTypeInt64), + ) + case wasm.OpcodeI64GeU: + c.emit( + newOperationGe(signedTypeUint64), + ) + case wasm.OpcodeF32Eq: + c.emit( + newOperationEq(unsignedTypeF32), + ) + case wasm.OpcodeF32Ne: + c.emit( + newOperationNe(unsignedTypeF32), + ) + case wasm.OpcodeF32Lt: + c.emit( + newOperationLt(signedTypeFloat32), + ) + case wasm.OpcodeF32Gt: + c.emit( + newOperationGt(signedTypeFloat32), + ) + case wasm.OpcodeF32Le: + c.emit( + newOperationLe(signedTypeFloat32), + ) + case wasm.OpcodeF32Ge: + c.emit( + newOperationGe(signedTypeFloat32), + ) + case wasm.OpcodeF64Eq: + c.emit( + newOperationEq(unsignedTypeF64), + ) + case wasm.OpcodeF64Ne: + c.emit( + newOperationNe(unsignedTypeF64), + ) + case wasm.OpcodeF64Lt: + c.emit( + newOperationLt(signedTypeFloat64), + ) + case wasm.OpcodeF64Gt: + c.emit( + newOperationGt(signedTypeFloat64), + ) + case wasm.OpcodeF64Le: + c.emit( + newOperationLe(signedTypeFloat64), + ) + case wasm.OpcodeF64Ge: + c.emit( + newOperationGe(signedTypeFloat64), + ) + case wasm.OpcodeI32Clz: + c.emit( + newOperationClz(unsignedInt32), + ) + case wasm.OpcodeI32Ctz: + c.emit( + newOperationCtz(unsignedInt32), + ) + case wasm.OpcodeI32Popcnt: + c.emit( + newOperationPopcnt(unsignedInt32), + ) + case wasm.OpcodeI32Add: + c.emit( + newOperationAdd(unsignedTypeI32), + ) + case wasm.OpcodeI32Sub: + c.emit( + newOperationSub(unsignedTypeI32), + ) + case wasm.OpcodeI32Mul: + c.emit( + newOperationMul(unsignedTypeI32), + ) + case wasm.OpcodeI32DivS: + c.emit( + newOperationDiv(signedTypeInt32), + ) + case wasm.OpcodeI32DivU: + c.emit( + newOperationDiv(signedTypeUint32), + ) + case wasm.OpcodeI32RemS: + c.emit( + newOperationRem(signedInt32), + ) + case wasm.OpcodeI32RemU: + c.emit( + newOperationRem(signedUint32), + ) + case wasm.OpcodeI32And: + c.emit( + newOperationAnd(unsignedInt32), + ) + case wasm.OpcodeI32Or: + c.emit( + newOperationOr(unsignedInt32), + ) + case wasm.OpcodeI32Xor: + c.emit( + newOperationXor(unsignedInt64), + ) + case wasm.OpcodeI32Shl: + c.emit( + newOperationShl(unsignedInt32), + ) + case wasm.OpcodeI32ShrS: + c.emit( + newOperationShr(signedInt32), + ) + case wasm.OpcodeI32ShrU: + c.emit( + newOperationShr(signedUint32), + ) + case wasm.OpcodeI32Rotl: + c.emit( + newOperationRotl(unsignedInt32), + ) + case wasm.OpcodeI32Rotr: + c.emit( + newOperationRotr(unsignedInt32), + ) + case wasm.OpcodeI64Clz: + c.emit( + newOperationClz(unsignedInt64), + ) + case wasm.OpcodeI64Ctz: + c.emit( + newOperationCtz(unsignedInt64), + ) + case wasm.OpcodeI64Popcnt: + c.emit( + newOperationPopcnt(unsignedInt64), + ) + case wasm.OpcodeI64Add: + c.emit( + newOperationAdd(unsignedTypeI64), + ) + case wasm.OpcodeI64Sub: + c.emit( + newOperationSub(unsignedTypeI64), + ) + case wasm.OpcodeI64Mul: + c.emit( + newOperationMul(unsignedTypeI64), + ) + case wasm.OpcodeI64DivS: + c.emit( + newOperationDiv(signedTypeInt64), + ) + case wasm.OpcodeI64DivU: + c.emit( + newOperationDiv(signedTypeUint64), + ) + case wasm.OpcodeI64RemS: + c.emit( + newOperationRem(signedInt64), + ) + case wasm.OpcodeI64RemU: + c.emit( + newOperationRem(signedUint64), + ) + case wasm.OpcodeI64And: + c.emit( + newOperationAnd(unsignedInt64), + ) + case wasm.OpcodeI64Or: + c.emit( + newOperationOr(unsignedInt64), + ) + case wasm.OpcodeI64Xor: + c.emit( + newOperationXor(unsignedInt64), + ) + case wasm.OpcodeI64Shl: + c.emit( + newOperationShl(unsignedInt64), + ) + case wasm.OpcodeI64ShrS: + c.emit( + newOperationShr(signedInt64), + ) + case wasm.OpcodeI64ShrU: + c.emit( + newOperationShr(signedUint64), + ) + case wasm.OpcodeI64Rotl: + c.emit( + newOperationRotl(unsignedInt64), + ) + case wasm.OpcodeI64Rotr: + c.emit( + newOperationRotr(unsignedInt64), + ) + case wasm.OpcodeF32Abs: + c.emit( + newOperationAbs(f32), + ) + case wasm.OpcodeF32Neg: + c.emit( + newOperationNeg(f32), + ) + case wasm.OpcodeF32Ceil: + c.emit( + newOperationCeil(f32), + ) + case wasm.OpcodeF32Floor: + c.emit( + newOperationFloor(f32), + ) + case wasm.OpcodeF32Trunc: + c.emit( + newOperationTrunc(f32), + ) + case wasm.OpcodeF32Nearest: + c.emit( + newOperationNearest(f32), + ) + case wasm.OpcodeF32Sqrt: + c.emit( + newOperationSqrt(f32), + ) + case wasm.OpcodeF32Add: + c.emit( + newOperationAdd(unsignedTypeF32), + ) + case wasm.OpcodeF32Sub: + c.emit( + newOperationSub(unsignedTypeF32), + ) + case wasm.OpcodeF32Mul: + c.emit( + newOperationMul(unsignedTypeF32), + ) + case wasm.OpcodeF32Div: + c.emit( + newOperationDiv(signedTypeFloat32), + ) + case wasm.OpcodeF32Min: + c.emit( + newOperationMin(f32), + ) + case wasm.OpcodeF32Max: + c.emit( + newOperationMax(f32), + ) + case wasm.OpcodeF32Copysign: + c.emit( + newOperationCopysign(f32), + ) + case wasm.OpcodeF64Abs: + c.emit( + newOperationAbs(f64), + ) + case wasm.OpcodeF64Neg: + c.emit( + newOperationNeg(f64), + ) + case wasm.OpcodeF64Ceil: + c.emit( + newOperationCeil(f64), + ) + case wasm.OpcodeF64Floor: + c.emit( + newOperationFloor(f64), + ) + case wasm.OpcodeF64Trunc: + c.emit( + newOperationTrunc(f64), + ) + case wasm.OpcodeF64Nearest: + c.emit( + newOperationNearest(f64), + ) + case wasm.OpcodeF64Sqrt: + c.emit( + newOperationSqrt(f64), + ) + case wasm.OpcodeF64Add: + c.emit( + newOperationAdd(unsignedTypeF64), + ) + case wasm.OpcodeF64Sub: + c.emit( + newOperationSub(unsignedTypeF64), + ) + case wasm.OpcodeF64Mul: + c.emit( + newOperationMul(unsignedTypeF64), + ) + case wasm.OpcodeF64Div: + c.emit( + newOperationDiv(signedTypeFloat64), + ) + case wasm.OpcodeF64Min: + c.emit( + newOperationMin(f64), + ) + case wasm.OpcodeF64Max: + c.emit( + newOperationMax(f64), + ) + case wasm.OpcodeF64Copysign: + c.emit( + newOperationCopysign(f64), + ) + case wasm.OpcodeI32WrapI64: + c.emit( + newOperationI32WrapFromI64(), + ) + case wasm.OpcodeI32TruncF32S: + c.emit( + newOperationITruncFromF(f32, signedInt32, false), + ) + case wasm.OpcodeI32TruncF32U: + c.emit( + newOperationITruncFromF(f32, signedUint32, false), + ) + case wasm.OpcodeI32TruncF64S: + c.emit( + newOperationITruncFromF(f64, signedInt32, false), + ) + case wasm.OpcodeI32TruncF64U: + c.emit( + newOperationITruncFromF(f64, signedUint32, false), + ) + case wasm.OpcodeI64ExtendI32S: + c.emit( + newOperationExtend(true), + ) + case wasm.OpcodeI64ExtendI32U: + c.emit( + newOperationExtend(false), + ) + case wasm.OpcodeI64TruncF32S: + c.emit( + newOperationITruncFromF(f32, signedInt64, false), + ) + case wasm.OpcodeI64TruncF32U: + c.emit( + newOperationITruncFromF(f32, signedUint64, false), + ) + case wasm.OpcodeI64TruncF64S: + c.emit( + newOperationITruncFromF(f64, signedInt64, false), + ) + case wasm.OpcodeI64TruncF64U: + c.emit( + newOperationITruncFromF(f64, signedUint64, false), + ) + case wasm.OpcodeF32ConvertI32S: + c.emit( + newOperationFConvertFromI(signedInt32, f32), + ) + case wasm.OpcodeF32ConvertI32U: + c.emit( + newOperationFConvertFromI(signedUint32, f32), + ) + case wasm.OpcodeF32ConvertI64S: + c.emit( + newOperationFConvertFromI(signedInt64, f32), + ) + case wasm.OpcodeF32ConvertI64U: + c.emit( + newOperationFConvertFromI(signedUint64, f32), + ) + case wasm.OpcodeF32DemoteF64: + c.emit( + newOperationF32DemoteFromF64(), + ) + case wasm.OpcodeF64ConvertI32S: + c.emit( + newOperationFConvertFromI(signedInt32, f64), + ) + case wasm.OpcodeF64ConvertI32U: + c.emit( + newOperationFConvertFromI(signedUint32, f64), + ) + case wasm.OpcodeF64ConvertI64S: + c.emit( + newOperationFConvertFromI(signedInt64, f64), + ) + case wasm.OpcodeF64ConvertI64U: + c.emit( + newOperationFConvertFromI(signedUint64, f64), + ) + case wasm.OpcodeF64PromoteF32: + c.emit( + newOperationF64PromoteFromF32(), + ) + case wasm.OpcodeI32ReinterpretF32: + c.emit( + newOperationI32ReinterpretFromF32(), + ) + case wasm.OpcodeI64ReinterpretF64: + c.emit( + newOperationI64ReinterpretFromF64(), + ) + case wasm.OpcodeF32ReinterpretI32: + c.emit( + newOperationF32ReinterpretFromI32(), + ) + case wasm.OpcodeF64ReinterpretI64: + c.emit( + newOperationF64ReinterpretFromI64(), + ) + case wasm.OpcodeI32Extend8S: + c.emit( + newOperationSignExtend32From8(), + ) + case wasm.OpcodeI32Extend16S: + c.emit( + newOperationSignExtend32From16(), + ) + case wasm.OpcodeI64Extend8S: + c.emit( + newOperationSignExtend64From8(), + ) + case wasm.OpcodeI64Extend16S: + c.emit( + newOperationSignExtend64From16(), + ) + case wasm.OpcodeI64Extend32S: + c.emit( + newOperationSignExtend64From32(), + ) + case wasm.OpcodeRefFunc: + c.pc++ + index, num, err := leb128.LoadUint32(c.body[c.pc:]) + if err != nil { + return fmt.Errorf("failed to read function index for ref.func: %v", err) + } + c.pc += num - 1 + c.emit( + newOperationRefFunc(index), + ) + case wasm.OpcodeRefNull: + c.pc++ // Skip the type of reftype as every ref value is opaque pointer. + c.emit( + newOperationConstI64(0), + ) + case wasm.OpcodeRefIsNull: + // Simply compare the opaque pointer (i64) with zero. + c.emit( + newOperationEqz(unsignedInt64), + ) + case wasm.OpcodeTableGet: + c.pc++ + tableIndex, num, err := leb128.LoadUint32(c.body[c.pc:]) + if err != nil { + return fmt.Errorf("failed to read function index for table.get: %v", err) + } + c.pc += num - 1 + c.emit( + newOperationTableGet(tableIndex), + ) + case wasm.OpcodeTableSet: + c.pc++ + tableIndex, num, err := leb128.LoadUint32(c.body[c.pc:]) + if err != nil { + return fmt.Errorf("failed to read function index for table.set: %v", err) + } + c.pc += num - 1 + c.emit( + newOperationTableSet(tableIndex), + ) + case wasm.OpcodeMiscPrefix: + c.pc++ + // A misc opcode is encoded as an unsigned variable 32-bit integer. + miscOp, num, err := leb128.LoadUint32(c.body[c.pc:]) + if err != nil { + return fmt.Errorf("failed to read misc opcode: %v", err) + } + c.pc += num - 1 + switch byte(miscOp) { + case wasm.OpcodeMiscI32TruncSatF32S: + c.emit( + newOperationITruncFromF(f32, signedInt32, true), + ) + case wasm.OpcodeMiscI32TruncSatF32U: + c.emit( + newOperationITruncFromF(f32, signedUint32, true), + ) + case wasm.OpcodeMiscI32TruncSatF64S: + c.emit( + newOperationITruncFromF(f64, signedInt32, true), + ) + case wasm.OpcodeMiscI32TruncSatF64U: + c.emit( + newOperationITruncFromF(f64, signedUint32, true), + ) + case wasm.OpcodeMiscI64TruncSatF32S: + c.emit( + newOperationITruncFromF(f32, signedInt64, true), + ) + case wasm.OpcodeMiscI64TruncSatF32U: + c.emit( + newOperationITruncFromF(f32, signedUint64, true), + ) + case wasm.OpcodeMiscI64TruncSatF64S: + c.emit( + newOperationITruncFromF(f64, signedInt64, true), + ) + case wasm.OpcodeMiscI64TruncSatF64U: + c.emit( + newOperationITruncFromF(f64, signedUint64, true), + ) + case wasm.OpcodeMiscMemoryInit: + c.result.UsesMemory = true + dataIndex, num, err := leb128.LoadUint32(c.body[c.pc+1:]) + if err != nil { + return fmt.Errorf("reading i32.const value: %v", err) + } + c.pc += num + 1 // +1 to skip the memory index which is fixed to zero. + c.emit( + newOperationMemoryInit(dataIndex), + ) + case wasm.OpcodeMiscDataDrop: + dataIndex, num, err := leb128.LoadUint32(c.body[c.pc+1:]) + if err != nil { + return fmt.Errorf("reading i32.const value: %v", err) + } + c.pc += num + c.emit( + newOperationDataDrop(dataIndex), + ) + case wasm.OpcodeMiscMemoryCopy: + c.result.UsesMemory = true + c.pc += 2 // +2 to skip two memory indexes which are fixed to zero. + c.emit( + newOperationMemoryCopy(), + ) + case wasm.OpcodeMiscMemoryFill: + c.result.UsesMemory = true + c.pc += 1 // +1 to skip the memory index which is fixed to zero. + c.emit( + newOperationMemoryFill(), + ) + case wasm.OpcodeMiscTableInit: + elemIndex, num, err := leb128.LoadUint32(c.body[c.pc+1:]) + if err != nil { + return fmt.Errorf("reading i32.const value: %v", err) + } + c.pc += num + // Read table index which is fixed to zero currently. + tableIndex, num, err := leb128.LoadUint32(c.body[c.pc+1:]) + if err != nil { + return fmt.Errorf("reading i32.const value: %v", err) + } + c.pc += num + c.emit( + newOperationTableInit(elemIndex, tableIndex), + ) + case wasm.OpcodeMiscElemDrop: + elemIndex, num, err := leb128.LoadUint32(c.body[c.pc+1:]) + if err != nil { + return fmt.Errorf("reading i32.const value: %v", err) + } + c.pc += num + c.emit( + newOperationElemDrop(elemIndex), + ) + case wasm.OpcodeMiscTableCopy: + // Read the source table inde.g. + dst, num, err := leb128.LoadUint32(c.body[c.pc+1:]) + if err != nil { + return fmt.Errorf("reading i32.const value: %v", err) + } + c.pc += num + // Read the destination table inde.g. + src, num, err := leb128.LoadUint32(c.body[c.pc+1:]) + if err != nil { + return fmt.Errorf("reading i32.const value: %v", err) + } + c.pc += num + c.emit( + newOperationTableCopy(src, dst), + ) + case wasm.OpcodeMiscTableGrow: + // Read the source table inde.g. + tableIndex, num, err := leb128.LoadUint32(c.body[c.pc+1:]) + if err != nil { + return fmt.Errorf("reading i32.const value: %v", err) + } + c.pc += num + c.emit( + newOperationTableGrow(tableIndex), + ) + case wasm.OpcodeMiscTableSize: + // Read the source table inde.g. + tableIndex, num, err := leb128.LoadUint32(c.body[c.pc+1:]) + if err != nil { + return fmt.Errorf("reading i32.const value: %v", err) + } + c.pc += num + c.emit( + newOperationTableSize(tableIndex), + ) + case wasm.OpcodeMiscTableFill: + // Read the source table index. + tableIndex, num, err := leb128.LoadUint32(c.body[c.pc+1:]) + if err != nil { + return fmt.Errorf("reading i32.const value: %v", err) + } + c.pc += num + c.emit( + newOperationTableFill(tableIndex), + ) + default: + return fmt.Errorf("unsupported misc instruction in interpreterir: 0x%x", op) + } + case wasm.OpcodeVecPrefix: + c.pc++ + switch vecOp := c.body[c.pc]; vecOp { + case wasm.OpcodeVecV128Const: + c.pc++ + lo := binary.LittleEndian.Uint64(c.body[c.pc : c.pc+8]) + c.pc += 8 + hi := binary.LittleEndian.Uint64(c.body[c.pc : c.pc+8]) + c.emit( + newOperationV128Const(lo, hi), + ) + c.pc += 7 + case wasm.OpcodeVecV128Load: + arg, err := c.readMemoryArg(wasm.OpcodeI32LoadName) + if err != nil { + return err + } + c.emit( + newOperationV128Load(v128LoadType128, arg), + ) + case wasm.OpcodeVecV128Load8x8s: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load8x8SName) + if err != nil { + return err + } + c.emit( + newOperationV128Load(v128LoadType8x8s, arg), + ) + case wasm.OpcodeVecV128Load8x8u: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load8x8UName) + if err != nil { + return err + } + c.emit( + newOperationV128Load(v128LoadType8x8u, arg), + ) + case wasm.OpcodeVecV128Load16x4s: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load16x4SName) + if err != nil { + return err + } + c.emit( + newOperationV128Load(v128LoadType16x4s, arg), + ) + case wasm.OpcodeVecV128Load16x4u: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load16x4UName) + if err != nil { + return err + } + c.emit( + newOperationV128Load(v128LoadType16x4u, arg), + ) + case wasm.OpcodeVecV128Load32x2s: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load32x2SName) + if err != nil { + return err + } + c.emit( + newOperationV128Load(v128LoadType32x2s, arg), + ) + case wasm.OpcodeVecV128Load32x2u: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load32x2UName) + if err != nil { + return err + } + c.emit( + newOperationV128Load(v128LoadType32x2u, arg), + ) + case wasm.OpcodeVecV128Load8Splat: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load8SplatName) + if err != nil { + return err + } + c.emit( + newOperationV128Load(v128LoadType8Splat, arg), + ) + case wasm.OpcodeVecV128Load16Splat: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load16SplatName) + if err != nil { + return err + } + c.emit( + newOperationV128Load(v128LoadType16Splat, arg), + ) + case wasm.OpcodeVecV128Load32Splat: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load32SplatName) + if err != nil { + return err + } + c.emit( + newOperationV128Load(v128LoadType32Splat, arg), + ) + case wasm.OpcodeVecV128Load64Splat: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load64SplatName) + if err != nil { + return err + } + c.emit( + newOperationV128Load(v128LoadType64Splat, arg), + ) + case wasm.OpcodeVecV128Load32zero: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load32zeroName) + if err != nil { + return err + } + c.emit( + newOperationV128Load(v128LoadType32zero, arg), + ) + case wasm.OpcodeVecV128Load64zero: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load64zeroName) + if err != nil { + return err + } + c.emit( + newOperationV128Load(v128LoadType64zero, arg), + ) + case wasm.OpcodeVecV128Load8Lane: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load8LaneName) + if err != nil { + return err + } + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128LoadLane(laneIndex, 8, arg), + ) + case wasm.OpcodeVecV128Load16Lane: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load16LaneName) + if err != nil { + return err + } + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128LoadLane(laneIndex, 16, arg), + ) + case wasm.OpcodeVecV128Load32Lane: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load32LaneName) + if err != nil { + return err + } + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128LoadLane(laneIndex, 32, arg), + ) + case wasm.OpcodeVecV128Load64Lane: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Load64LaneName) + if err != nil { + return err + } + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128LoadLane(laneIndex, 64, arg), + ) + case wasm.OpcodeVecV128Store: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128StoreName) + if err != nil { + return err + } + c.emit( + newOperationV128Store(arg), + ) + case wasm.OpcodeVecV128Store8Lane: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Store8LaneName) + if err != nil { + return err + } + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128StoreLane(laneIndex, 8, arg), + ) + case wasm.OpcodeVecV128Store16Lane: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Store16LaneName) + if err != nil { + return err + } + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128StoreLane(laneIndex, 16, arg), + ) + case wasm.OpcodeVecV128Store32Lane: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Store32LaneName) + if err != nil { + return err + } + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128StoreLane(laneIndex, 32, arg), + ) + case wasm.OpcodeVecV128Store64Lane: + arg, err := c.readMemoryArg(wasm.OpcodeVecV128Store64LaneName) + if err != nil { + return err + } + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128StoreLane(laneIndex, 64, arg), + ) + case wasm.OpcodeVecI8x16ExtractLaneS: + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128ExtractLane(laneIndex, true, shapeI8x16), + ) + case wasm.OpcodeVecI8x16ExtractLaneU: + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128ExtractLane(laneIndex, false, shapeI8x16), + ) + case wasm.OpcodeVecI16x8ExtractLaneS: + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128ExtractLane(laneIndex, true, shapeI16x8), + ) + case wasm.OpcodeVecI16x8ExtractLaneU: + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128ExtractLane(laneIndex, false, shapeI16x8), + ) + case wasm.OpcodeVecI32x4ExtractLane: + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128ExtractLane(laneIndex, false, shapeI32x4), + ) + case wasm.OpcodeVecI64x2ExtractLane: + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128ExtractLane(laneIndex, false, shapeI64x2), + ) + case wasm.OpcodeVecF32x4ExtractLane: + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128ExtractLane(laneIndex, false, shapeF32x4), + ) + case wasm.OpcodeVecF64x2ExtractLane: + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128ExtractLane(laneIndex, false, shapeF64x2), + ) + case wasm.OpcodeVecI8x16ReplaceLane: + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128ReplaceLane(laneIndex, shapeI8x16), + ) + case wasm.OpcodeVecI16x8ReplaceLane: + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128ReplaceLane(laneIndex, shapeI16x8), + ) + case wasm.OpcodeVecI32x4ReplaceLane: + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128ReplaceLane(laneIndex, shapeI32x4), + ) + case wasm.OpcodeVecI64x2ReplaceLane: + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128ReplaceLane(laneIndex, shapeI64x2), + ) + case wasm.OpcodeVecF32x4ReplaceLane: + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128ReplaceLane(laneIndex, shapeF32x4), + ) + case wasm.OpcodeVecF64x2ReplaceLane: + c.pc++ + laneIndex := c.body[c.pc] + c.emit( + newOperationV128ReplaceLane(laneIndex, shapeF64x2), + ) + case wasm.OpcodeVecI8x16Splat: + c.emit( + newOperationV128Splat(shapeI8x16), + ) + case wasm.OpcodeVecI16x8Splat: + c.emit( + newOperationV128Splat(shapeI16x8), + ) + case wasm.OpcodeVecI32x4Splat: + c.emit( + newOperationV128Splat(shapeI32x4), + ) + case wasm.OpcodeVecI64x2Splat: + c.emit( + newOperationV128Splat(shapeI64x2), + ) + case wasm.OpcodeVecF32x4Splat: + c.emit( + newOperationV128Splat(shapeF32x4), + ) + case wasm.OpcodeVecF64x2Splat: + c.emit( + newOperationV128Splat(shapeF64x2), + ) + case wasm.OpcodeVecI8x16Swizzle: + c.emit( + newOperationV128Swizzle(), + ) + case wasm.OpcodeVecV128i8x16Shuffle: + c.pc++ + lanes := make([]uint64, 16) + for i := uint64(0); i < 16; i++ { + lanes[i] = uint64(c.body[c.pc+i]) + } + op := newOperationV128Shuffle(lanes) + c.emit(op) + c.pc += 15 + case wasm.OpcodeVecV128AnyTrue: + c.emit( + newOperationV128AnyTrue(), + ) + case wasm.OpcodeVecI8x16AllTrue: + c.emit( + newOperationV128AllTrue(shapeI8x16), + ) + case wasm.OpcodeVecI16x8AllTrue: + c.emit( + newOperationV128AllTrue(shapeI16x8), + ) + case wasm.OpcodeVecI32x4AllTrue: + c.emit( + newOperationV128AllTrue(shapeI32x4), + ) + case wasm.OpcodeVecI64x2AllTrue: + c.emit( + newOperationV128AllTrue(shapeI64x2), + ) + case wasm.OpcodeVecI8x16BitMask: + c.emit( + newOperationV128BitMask(shapeI8x16), + ) + case wasm.OpcodeVecI16x8BitMask: + c.emit( + newOperationV128BitMask(shapeI16x8), + ) + case wasm.OpcodeVecI32x4BitMask: + c.emit( + newOperationV128BitMask(shapeI32x4), + ) + case wasm.OpcodeVecI64x2BitMask: + c.emit( + newOperationV128BitMask(shapeI64x2), + ) + case wasm.OpcodeVecV128And: + c.emit( + newOperationV128And(), + ) + case wasm.OpcodeVecV128Not: + c.emit( + newOperationV128Not(), + ) + case wasm.OpcodeVecV128Or: + c.emit( + newOperationV128Or(), + ) + case wasm.OpcodeVecV128Xor: + c.emit( + newOperationV128Xor(), + ) + case wasm.OpcodeVecV128Bitselect: + c.emit( + newOperationV128Bitselect(), + ) + case wasm.OpcodeVecV128AndNot: + c.emit( + newOperationV128AndNot(), + ) + case wasm.OpcodeVecI8x16Shl: + c.emit( + newOperationV128Shl(shapeI8x16), + ) + case wasm.OpcodeVecI8x16ShrS: + c.emit( + newOperationV128Shr(shapeI8x16, true), + ) + case wasm.OpcodeVecI8x16ShrU: + c.emit( + newOperationV128Shr(shapeI8x16, false), + ) + case wasm.OpcodeVecI16x8Shl: + c.emit( + newOperationV128Shl(shapeI16x8), + ) + case wasm.OpcodeVecI16x8ShrS: + c.emit( + newOperationV128Shr(shapeI16x8, true), + ) + case wasm.OpcodeVecI16x8ShrU: + c.emit( + newOperationV128Shr(shapeI16x8, false), + ) + case wasm.OpcodeVecI32x4Shl: + c.emit( + newOperationV128Shl(shapeI32x4), + ) + case wasm.OpcodeVecI32x4ShrS: + c.emit( + newOperationV128Shr(shapeI32x4, true), + ) + case wasm.OpcodeVecI32x4ShrU: + c.emit( + newOperationV128Shr(shapeI32x4, false), + ) + case wasm.OpcodeVecI64x2Shl: + c.emit( + newOperationV128Shl(shapeI64x2), + ) + case wasm.OpcodeVecI64x2ShrS: + c.emit( + newOperationV128Shr(shapeI64x2, true), + ) + case wasm.OpcodeVecI64x2ShrU: + c.emit( + newOperationV128Shr(shapeI64x2, false), + ) + case wasm.OpcodeVecI8x16Eq: + c.emit( + newOperationV128Cmp(v128CmpTypeI8x16Eq), + ) + case wasm.OpcodeVecI8x16Ne: + c.emit( + newOperationV128Cmp(v128CmpTypeI8x16Ne), + ) + case wasm.OpcodeVecI8x16LtS: + c.emit( + newOperationV128Cmp(v128CmpTypeI8x16LtS), + ) + case wasm.OpcodeVecI8x16LtU: + c.emit( + newOperationV128Cmp(v128CmpTypeI8x16LtU), + ) + case wasm.OpcodeVecI8x16GtS: + c.emit( + newOperationV128Cmp(v128CmpTypeI8x16GtS), + ) + case wasm.OpcodeVecI8x16GtU: + c.emit( + newOperationV128Cmp(v128CmpTypeI8x16GtU), + ) + case wasm.OpcodeVecI8x16LeS: + c.emit( + newOperationV128Cmp(v128CmpTypeI8x16LeS), + ) + case wasm.OpcodeVecI8x16LeU: + c.emit( + newOperationV128Cmp(v128CmpTypeI8x16LeU), + ) + case wasm.OpcodeVecI8x16GeS: + c.emit( + newOperationV128Cmp(v128CmpTypeI8x16GeS), + ) + case wasm.OpcodeVecI8x16GeU: + c.emit( + newOperationV128Cmp(v128CmpTypeI8x16GeU), + ) + case wasm.OpcodeVecI16x8Eq: + c.emit( + newOperationV128Cmp(v128CmpTypeI16x8Eq), + ) + case wasm.OpcodeVecI16x8Ne: + c.emit( + newOperationV128Cmp(v128CmpTypeI16x8Ne), + ) + case wasm.OpcodeVecI16x8LtS: + c.emit( + newOperationV128Cmp(v128CmpTypeI16x8LtS), + ) + case wasm.OpcodeVecI16x8LtU: + c.emit( + newOperationV128Cmp(v128CmpTypeI16x8LtU), + ) + case wasm.OpcodeVecI16x8GtS: + c.emit( + newOperationV128Cmp(v128CmpTypeI16x8GtS), + ) + case wasm.OpcodeVecI16x8GtU: + c.emit( + newOperationV128Cmp(v128CmpTypeI16x8GtU), + ) + case wasm.OpcodeVecI16x8LeS: + c.emit( + newOperationV128Cmp(v128CmpTypeI16x8LeS), + ) + case wasm.OpcodeVecI16x8LeU: + c.emit( + newOperationV128Cmp(v128CmpTypeI16x8LeU), + ) + case wasm.OpcodeVecI16x8GeS: + c.emit( + newOperationV128Cmp(v128CmpTypeI16x8GeS), + ) + case wasm.OpcodeVecI16x8GeU: + c.emit( + newOperationV128Cmp(v128CmpTypeI16x8GeU), + ) + case wasm.OpcodeVecI32x4Eq: + c.emit( + newOperationV128Cmp(v128CmpTypeI32x4Eq), + ) + case wasm.OpcodeVecI32x4Ne: + c.emit( + newOperationV128Cmp(v128CmpTypeI32x4Ne), + ) + case wasm.OpcodeVecI32x4LtS: + c.emit( + newOperationV128Cmp(v128CmpTypeI32x4LtS), + ) + case wasm.OpcodeVecI32x4LtU: + c.emit( + newOperationV128Cmp(v128CmpTypeI32x4LtU), + ) + case wasm.OpcodeVecI32x4GtS: + c.emit( + newOperationV128Cmp(v128CmpTypeI32x4GtS), + ) + case wasm.OpcodeVecI32x4GtU: + c.emit( + newOperationV128Cmp(v128CmpTypeI32x4GtU), + ) + case wasm.OpcodeVecI32x4LeS: + c.emit( + newOperationV128Cmp(v128CmpTypeI32x4LeS), + ) + case wasm.OpcodeVecI32x4LeU: + c.emit( + newOperationV128Cmp(v128CmpTypeI32x4LeU), + ) + case wasm.OpcodeVecI32x4GeS: + c.emit( + newOperationV128Cmp(v128CmpTypeI32x4GeS), + ) + case wasm.OpcodeVecI32x4GeU: + c.emit( + newOperationV128Cmp(v128CmpTypeI32x4GeU), + ) + case wasm.OpcodeVecI64x2Eq: + c.emit( + newOperationV128Cmp(v128CmpTypeI64x2Eq), + ) + case wasm.OpcodeVecI64x2Ne: + c.emit( + newOperationV128Cmp(v128CmpTypeI64x2Ne), + ) + case wasm.OpcodeVecI64x2LtS: + c.emit( + newOperationV128Cmp(v128CmpTypeI64x2LtS), + ) + case wasm.OpcodeVecI64x2GtS: + c.emit( + newOperationV128Cmp(v128CmpTypeI64x2GtS), + ) + case wasm.OpcodeVecI64x2LeS: + c.emit( + newOperationV128Cmp(v128CmpTypeI64x2LeS), + ) + case wasm.OpcodeVecI64x2GeS: + c.emit( + newOperationV128Cmp(v128CmpTypeI64x2GeS), + ) + case wasm.OpcodeVecF32x4Eq: + c.emit( + newOperationV128Cmp(v128CmpTypeF32x4Eq), + ) + case wasm.OpcodeVecF32x4Ne: + c.emit( + newOperationV128Cmp(v128CmpTypeF32x4Ne), + ) + case wasm.OpcodeVecF32x4Lt: + c.emit( + newOperationV128Cmp(v128CmpTypeF32x4Lt), + ) + case wasm.OpcodeVecF32x4Gt: + c.emit( + newOperationV128Cmp(v128CmpTypeF32x4Gt), + ) + case wasm.OpcodeVecF32x4Le: + c.emit( + newOperationV128Cmp(v128CmpTypeF32x4Le), + ) + case wasm.OpcodeVecF32x4Ge: + c.emit( + newOperationV128Cmp(v128CmpTypeF32x4Ge), + ) + case wasm.OpcodeVecF64x2Eq: + c.emit( + newOperationV128Cmp(v128CmpTypeF64x2Eq), + ) + case wasm.OpcodeVecF64x2Ne: + c.emit( + newOperationV128Cmp(v128CmpTypeF64x2Ne), + ) + case wasm.OpcodeVecF64x2Lt: + c.emit( + newOperationV128Cmp(v128CmpTypeF64x2Lt), + ) + case wasm.OpcodeVecF64x2Gt: + c.emit( + newOperationV128Cmp(v128CmpTypeF64x2Gt), + ) + case wasm.OpcodeVecF64x2Le: + c.emit( + newOperationV128Cmp(v128CmpTypeF64x2Le), + ) + case wasm.OpcodeVecF64x2Ge: + c.emit( + newOperationV128Cmp(v128CmpTypeF64x2Ge), + ) + case wasm.OpcodeVecI8x16Neg: + c.emit( + newOperationV128Neg(shapeI8x16), + ) + case wasm.OpcodeVecI16x8Neg: + c.emit( + newOperationV128Neg(shapeI16x8), + ) + case wasm.OpcodeVecI32x4Neg: + c.emit( + newOperationV128Neg(shapeI32x4), + ) + case wasm.OpcodeVecI64x2Neg: + c.emit( + newOperationV128Neg(shapeI64x2), + ) + case wasm.OpcodeVecF32x4Neg: + c.emit( + newOperationV128Neg(shapeF32x4), + ) + case wasm.OpcodeVecF64x2Neg: + c.emit( + newOperationV128Neg(shapeF64x2), + ) + case wasm.OpcodeVecI8x16Add: + c.emit( + newOperationV128Add(shapeI8x16), + ) + case wasm.OpcodeVecI16x8Add: + c.emit( + newOperationV128Add(shapeI16x8), + ) + case wasm.OpcodeVecI32x4Add: + c.emit( + newOperationV128Add(shapeI32x4), + ) + case wasm.OpcodeVecI64x2Add: + c.emit( + newOperationV128Add(shapeI64x2), + ) + case wasm.OpcodeVecF32x4Add: + c.emit( + newOperationV128Add(shapeF32x4), + ) + case wasm.OpcodeVecF64x2Add: + c.emit( + newOperationV128Add(shapeF64x2), + ) + case wasm.OpcodeVecI8x16Sub: + c.emit( + newOperationV128Sub(shapeI8x16), + ) + case wasm.OpcodeVecI16x8Sub: + c.emit( + newOperationV128Sub(shapeI16x8), + ) + case wasm.OpcodeVecI32x4Sub: + c.emit( + newOperationV128Sub(shapeI32x4), + ) + case wasm.OpcodeVecI64x2Sub: + c.emit( + newOperationV128Sub(shapeI64x2), + ) + case wasm.OpcodeVecF32x4Sub: + c.emit( + newOperationV128Sub(shapeF32x4), + ) + case wasm.OpcodeVecF64x2Sub: + c.emit( + newOperationV128Sub(shapeF64x2), + ) + case wasm.OpcodeVecI8x16AddSatS: + c.emit( + newOperationV128AddSat(shapeI8x16, true), + ) + case wasm.OpcodeVecI8x16AddSatU: + c.emit( + newOperationV128AddSat(shapeI8x16, false), + ) + case wasm.OpcodeVecI16x8AddSatS: + c.emit( + newOperationV128AddSat(shapeI16x8, true), + ) + case wasm.OpcodeVecI16x8AddSatU: + c.emit( + newOperationV128AddSat(shapeI16x8, false), + ) + case wasm.OpcodeVecI8x16SubSatS: + c.emit( + newOperationV128SubSat(shapeI8x16, true), + ) + case wasm.OpcodeVecI8x16SubSatU: + c.emit( + newOperationV128SubSat(shapeI8x16, false), + ) + case wasm.OpcodeVecI16x8SubSatS: + c.emit( + newOperationV128SubSat(shapeI16x8, true), + ) + case wasm.OpcodeVecI16x8SubSatU: + c.emit( + newOperationV128SubSat(shapeI16x8, false), + ) + case wasm.OpcodeVecI16x8Mul: + c.emit( + newOperationV128Mul(shapeI16x8), + ) + case wasm.OpcodeVecI32x4Mul: + c.emit( + newOperationV128Mul(shapeI32x4), + ) + case wasm.OpcodeVecI64x2Mul: + c.emit( + newOperationV128Mul(shapeI64x2), + ) + case wasm.OpcodeVecF32x4Mul: + c.emit( + newOperationV128Mul(shapeF32x4), + ) + case wasm.OpcodeVecF64x2Mul: + c.emit( + newOperationV128Mul(shapeF64x2), + ) + case wasm.OpcodeVecF32x4Sqrt: + c.emit( + newOperationV128Sqrt(shapeF32x4), + ) + case wasm.OpcodeVecF64x2Sqrt: + c.emit( + newOperationV128Sqrt(shapeF64x2), + ) + case wasm.OpcodeVecF32x4Div: + c.emit( + newOperationV128Div(shapeF32x4), + ) + case wasm.OpcodeVecF64x2Div: + c.emit( + newOperationV128Div(shapeF64x2), + ) + case wasm.OpcodeVecI8x16Abs: + c.emit( + newOperationV128Abs(shapeI8x16), + ) + case wasm.OpcodeVecI8x16Popcnt: + c.emit( + newOperationV128Popcnt(shapeI8x16), + ) + case wasm.OpcodeVecI16x8Abs: + c.emit( + newOperationV128Abs(shapeI16x8), + ) + case wasm.OpcodeVecI32x4Abs: + c.emit( + newOperationV128Abs(shapeI32x4), + ) + case wasm.OpcodeVecI64x2Abs: + c.emit( + newOperationV128Abs(shapeI64x2), + ) + case wasm.OpcodeVecF32x4Abs: + c.emit( + newOperationV128Abs(shapeF32x4), + ) + case wasm.OpcodeVecF64x2Abs: + c.emit( + newOperationV128Abs(shapeF64x2), + ) + case wasm.OpcodeVecI8x16MinS: + c.emit( + newOperationV128Min(shapeI8x16, true), + ) + case wasm.OpcodeVecI8x16MinU: + c.emit( + newOperationV128Min(shapeI8x16, false), + ) + case wasm.OpcodeVecI8x16MaxS: + c.emit( + newOperationV128Max(shapeI8x16, true), + ) + case wasm.OpcodeVecI8x16MaxU: + c.emit( + newOperationV128Max(shapeI8x16, false), + ) + case wasm.OpcodeVecI8x16AvgrU: + c.emit( + newOperationV128AvgrU(shapeI8x16), + ) + case wasm.OpcodeVecI16x8MinS: + c.emit( + newOperationV128Min(shapeI16x8, true), + ) + case wasm.OpcodeVecI16x8MinU: + c.emit( + newOperationV128Min(shapeI16x8, false), + ) + case wasm.OpcodeVecI16x8MaxS: + c.emit( + newOperationV128Max(shapeI16x8, true), + ) + case wasm.OpcodeVecI16x8MaxU: + c.emit( + newOperationV128Max(shapeI16x8, false), + ) + case wasm.OpcodeVecI16x8AvgrU: + c.emit( + newOperationV128AvgrU(shapeI16x8), + ) + case wasm.OpcodeVecI32x4MinS: + c.emit( + newOperationV128Min(shapeI32x4, true), + ) + case wasm.OpcodeVecI32x4MinU: + c.emit( + newOperationV128Min(shapeI32x4, false), + ) + case wasm.OpcodeVecI32x4MaxS: + c.emit( + newOperationV128Max(shapeI32x4, true), + ) + case wasm.OpcodeVecI32x4MaxU: + c.emit( + newOperationV128Max(shapeI32x4, false), + ) + case wasm.OpcodeVecF32x4Min: + c.emit( + newOperationV128Min(shapeF32x4, false), + ) + case wasm.OpcodeVecF32x4Max: + c.emit( + newOperationV128Max(shapeF32x4, false), + ) + case wasm.OpcodeVecF64x2Min: + c.emit( + newOperationV128Min(shapeF64x2, false), + ) + case wasm.OpcodeVecF64x2Max: + c.emit( + newOperationV128Max(shapeF64x2, false), + ) + case wasm.OpcodeVecF32x4Pmin: + c.emit( + newOperationV128Pmin(shapeF32x4), + ) + case wasm.OpcodeVecF32x4Pmax: + c.emit( + newOperationV128Pmax(shapeF32x4), + ) + case wasm.OpcodeVecF64x2Pmin: + c.emit( + newOperationV128Pmin(shapeF64x2), + ) + case wasm.OpcodeVecF64x2Pmax: + c.emit( + newOperationV128Pmax(shapeF64x2), + ) + case wasm.OpcodeVecF32x4Ceil: + c.emit( + newOperationV128Ceil(shapeF32x4), + ) + case wasm.OpcodeVecF32x4Floor: + c.emit( + newOperationV128Floor(shapeF32x4), + ) + case wasm.OpcodeVecF32x4Trunc: + c.emit( + newOperationV128Trunc(shapeF32x4), + ) + case wasm.OpcodeVecF32x4Nearest: + c.emit( + newOperationV128Nearest(shapeF32x4), + ) + case wasm.OpcodeVecF64x2Ceil: + c.emit( + newOperationV128Ceil(shapeF64x2), + ) + case wasm.OpcodeVecF64x2Floor: + c.emit( + newOperationV128Floor(shapeF64x2), + ) + case wasm.OpcodeVecF64x2Trunc: + c.emit( + newOperationV128Trunc(shapeF64x2), + ) + case wasm.OpcodeVecF64x2Nearest: + c.emit( + newOperationV128Nearest(shapeF64x2), + ) + case wasm.OpcodeVecI16x8ExtendLowI8x16S: + c.emit( + newOperationV128Extend(shapeI8x16, true, true), + ) + case wasm.OpcodeVecI16x8ExtendHighI8x16S: + c.emit( + newOperationV128Extend(shapeI8x16, true, false), + ) + case wasm.OpcodeVecI16x8ExtendLowI8x16U: + c.emit( + newOperationV128Extend(shapeI8x16, false, true), + ) + case wasm.OpcodeVecI16x8ExtendHighI8x16U: + c.emit( + newOperationV128Extend(shapeI8x16, false, false), + ) + case wasm.OpcodeVecI32x4ExtendLowI16x8S: + c.emit( + newOperationV128Extend(shapeI16x8, true, true), + ) + case wasm.OpcodeVecI32x4ExtendHighI16x8S: + c.emit( + newOperationV128Extend(shapeI16x8, true, false), + ) + case wasm.OpcodeVecI32x4ExtendLowI16x8U: + c.emit( + newOperationV128Extend(shapeI16x8, false, true), + ) + case wasm.OpcodeVecI32x4ExtendHighI16x8U: + c.emit( + newOperationV128Extend(shapeI16x8, false, false), + ) + case wasm.OpcodeVecI64x2ExtendLowI32x4S: + c.emit( + newOperationV128Extend(shapeI32x4, true, true), + ) + case wasm.OpcodeVecI64x2ExtendHighI32x4S: + c.emit( + newOperationV128Extend(shapeI32x4, true, false), + ) + case wasm.OpcodeVecI64x2ExtendLowI32x4U: + c.emit( + newOperationV128Extend(shapeI32x4, false, true), + ) + case wasm.OpcodeVecI64x2ExtendHighI32x4U: + c.emit( + newOperationV128Extend(shapeI32x4, false, false), + ) + case wasm.OpcodeVecI16x8Q15mulrSatS: + c.emit( + newOperationV128Q15mulrSatS(), + ) + case wasm.OpcodeVecI16x8ExtMulLowI8x16S: + c.emit( + newOperationV128ExtMul(shapeI8x16, true, true), + ) + case wasm.OpcodeVecI16x8ExtMulHighI8x16S: + c.emit( + newOperationV128ExtMul(shapeI8x16, true, false), + ) + case wasm.OpcodeVecI16x8ExtMulLowI8x16U: + c.emit( + newOperationV128ExtMul(shapeI8x16, false, true), + ) + case wasm.OpcodeVecI16x8ExtMulHighI8x16U: + c.emit( + newOperationV128ExtMul(shapeI8x16, false, false), + ) + case wasm.OpcodeVecI32x4ExtMulLowI16x8S: + c.emit( + newOperationV128ExtMul(shapeI16x8, true, true), + ) + case wasm.OpcodeVecI32x4ExtMulHighI16x8S: + c.emit( + newOperationV128ExtMul(shapeI16x8, true, false), + ) + case wasm.OpcodeVecI32x4ExtMulLowI16x8U: + c.emit( + newOperationV128ExtMul(shapeI16x8, false, true), + ) + case wasm.OpcodeVecI32x4ExtMulHighI16x8U: + c.emit( + newOperationV128ExtMul(shapeI16x8, false, false), + ) + case wasm.OpcodeVecI64x2ExtMulLowI32x4S: + c.emit( + newOperationV128ExtMul(shapeI32x4, true, true), + ) + case wasm.OpcodeVecI64x2ExtMulHighI32x4S: + c.emit( + newOperationV128ExtMul(shapeI32x4, true, false), + ) + case wasm.OpcodeVecI64x2ExtMulLowI32x4U: + c.emit( + newOperationV128ExtMul(shapeI32x4, false, true), + ) + case wasm.OpcodeVecI64x2ExtMulHighI32x4U: + c.emit( + newOperationV128ExtMul(shapeI32x4, false, false), + ) + case wasm.OpcodeVecI16x8ExtaddPairwiseI8x16S: + c.emit( + newOperationV128ExtAddPairwise(shapeI8x16, true), + ) + case wasm.OpcodeVecI16x8ExtaddPairwiseI8x16U: + c.emit( + newOperationV128ExtAddPairwise(shapeI8x16, false), + ) + case wasm.OpcodeVecI32x4ExtaddPairwiseI16x8S: + c.emit( + newOperationV128ExtAddPairwise(shapeI16x8, true), + ) + case wasm.OpcodeVecI32x4ExtaddPairwiseI16x8U: + c.emit( + newOperationV128ExtAddPairwise(shapeI16x8, false), + ) + case wasm.OpcodeVecF64x2PromoteLowF32x4Zero: + c.emit( + newOperationV128FloatPromote(), + ) + case wasm.OpcodeVecF32x4DemoteF64x2Zero: + c.emit( + newOperationV128FloatDemote(), + ) + case wasm.OpcodeVecF32x4ConvertI32x4S: + c.emit( + newOperationV128FConvertFromI(shapeF32x4, true), + ) + case wasm.OpcodeVecF32x4ConvertI32x4U: + c.emit( + newOperationV128FConvertFromI(shapeF32x4, false), + ) + case wasm.OpcodeVecF64x2ConvertLowI32x4S: + c.emit( + newOperationV128FConvertFromI(shapeF64x2, true), + ) + case wasm.OpcodeVecF64x2ConvertLowI32x4U: + c.emit( + newOperationV128FConvertFromI(shapeF64x2, false), + ) + case wasm.OpcodeVecI32x4DotI16x8S: + c.emit( + newOperationV128Dot(), + ) + case wasm.OpcodeVecI8x16NarrowI16x8S: + c.emit( + newOperationV128Narrow(shapeI16x8, true), + ) + case wasm.OpcodeVecI8x16NarrowI16x8U: + c.emit( + newOperationV128Narrow(shapeI16x8, false), + ) + case wasm.OpcodeVecI16x8NarrowI32x4S: + c.emit( + newOperationV128Narrow(shapeI32x4, true), + ) + case wasm.OpcodeVecI16x8NarrowI32x4U: + c.emit( + newOperationV128Narrow(shapeI32x4, false), + ) + case wasm.OpcodeVecI32x4TruncSatF32x4S: + c.emit( + newOperationV128ITruncSatFromF(shapeF32x4, true), + ) + case wasm.OpcodeVecI32x4TruncSatF32x4U: + c.emit( + newOperationV128ITruncSatFromF(shapeF32x4, false), + ) + case wasm.OpcodeVecI32x4TruncSatF64x2SZero: + c.emit( + newOperationV128ITruncSatFromF(shapeF64x2, true), + ) + case wasm.OpcodeVecI32x4TruncSatF64x2UZero: + c.emit( + newOperationV128ITruncSatFromF(shapeF64x2, false), + ) + default: + return fmt.Errorf("unsupported vector instruction in interpreterir: %s", wasm.VectorInstructionName(vecOp)) + } + case wasm.OpcodeAtomicPrefix: + c.pc++ + atomicOp := c.body[c.pc] + switch atomicOp { + case wasm.OpcodeAtomicMemoryWait32: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicMemoryWait32Name) + if err != nil { + return err + } + c.emit( + newOperationAtomicMemoryWait(unsignedTypeI32, imm), + ) + case wasm.OpcodeAtomicMemoryWait64: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicMemoryWait64Name) + if err != nil { + return err + } + c.emit( + newOperationAtomicMemoryWait(unsignedTypeI64, imm), + ) + case wasm.OpcodeAtomicMemoryNotify: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicMemoryNotifyName) + if err != nil { + return err + } + c.emit( + newOperationAtomicMemoryNotify(imm), + ) + case wasm.OpcodeAtomicFence: + // Skip immediate value + c.pc++ + _ = c.body[c.pc] + c.emit( + newOperationAtomicFence(), + ) + case wasm.OpcodeAtomicI32Load: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32LoadName) + if err != nil { + return err + } + c.emit( + newOperationAtomicLoad(unsignedTypeI32, imm), + ) + case wasm.OpcodeAtomicI64Load: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64LoadName) + if err != nil { + return err + } + c.emit( + newOperationAtomicLoad(unsignedTypeI64, imm), + ) + case wasm.OpcodeAtomicI32Load8U: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Load8UName) + if err != nil { + return err + } + c.emit( + newOperationAtomicLoad8(unsignedTypeI32, imm), + ) + case wasm.OpcodeAtomicI32Load16U: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Load16UName) + if err != nil { + return err + } + c.emit( + newOperationAtomicLoad16(unsignedTypeI32, imm), + ) + case wasm.OpcodeAtomicI64Load8U: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Load8UName) + if err != nil { + return err + } + c.emit( + newOperationAtomicLoad8(unsignedTypeI64, imm), + ) + case wasm.OpcodeAtomicI64Load16U: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Load16UName) + if err != nil { + return err + } + c.emit( + newOperationAtomicLoad16(unsignedTypeI64, imm), + ) + case wasm.OpcodeAtomicI64Load32U: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Load32UName) + if err != nil { + return err + } + c.emit( + newOperationAtomicLoad(unsignedTypeI32, imm), + ) + case wasm.OpcodeAtomicI32Store: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32StoreName) + if err != nil { + return err + } + c.emit( + newOperationAtomicStore(unsignedTypeI32, imm), + ) + case wasm.OpcodeAtomicI32Store8: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Store8Name) + if err != nil { + return err + } + c.emit( + newOperationAtomicStore8(unsignedTypeI32, imm), + ) + case wasm.OpcodeAtomicI32Store16: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Store16Name) + if err != nil { + return err + } + c.emit( + newOperationAtomicStore16(unsignedTypeI32, imm), + ) + case wasm.OpcodeAtomicI64Store: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64StoreName) + if err != nil { + return err + } + c.emit( + newOperationAtomicStore(unsignedTypeI64, imm), + ) + case wasm.OpcodeAtomicI64Store8: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Store8Name) + if err != nil { + return err + } + c.emit( + newOperationAtomicStore8(unsignedTypeI64, imm), + ) + case wasm.OpcodeAtomicI64Store16: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Store16Name) + if err != nil { + return err + } + c.emit( + newOperationAtomicStore16(unsignedTypeI64, imm), + ) + case wasm.OpcodeAtomicI64Store32: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Store32Name) + if err != nil { + return err + } + c.emit( + newOperationAtomicStore(unsignedTypeI32, imm), + ) + case wasm.OpcodeAtomicI32RmwAdd: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32RmwAddName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpAdd), + ) + case wasm.OpcodeAtomicI64RmwAdd: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64RmwAddName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW(unsignedTypeI64, imm, atomicArithmeticOpAdd), + ) + case wasm.OpcodeAtomicI32Rmw8AddU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw8AddUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW8(unsignedTypeI32, imm, atomicArithmeticOpAdd), + ) + case wasm.OpcodeAtomicI64Rmw8AddU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw8AddUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW8(unsignedTypeI64, imm, atomicArithmeticOpAdd), + ) + case wasm.OpcodeAtomicI32Rmw16AddU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw16AddUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW16(unsignedTypeI32, imm, atomicArithmeticOpAdd), + ) + case wasm.OpcodeAtomicI64Rmw16AddU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw16AddUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW16(unsignedTypeI64, imm, atomicArithmeticOpAdd), + ) + case wasm.OpcodeAtomicI64Rmw32AddU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw32AddUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpAdd), + ) + case wasm.OpcodeAtomicI32RmwSub: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32RmwSubName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpSub), + ) + case wasm.OpcodeAtomicI64RmwSub: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64RmwSubName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW(unsignedTypeI64, imm, atomicArithmeticOpSub), + ) + case wasm.OpcodeAtomicI32Rmw8SubU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw8SubUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW8(unsignedTypeI32, imm, atomicArithmeticOpSub), + ) + case wasm.OpcodeAtomicI64Rmw8SubU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw8SubUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW8(unsignedTypeI64, imm, atomicArithmeticOpSub), + ) + case wasm.OpcodeAtomicI32Rmw16SubU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw16SubUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW16(unsignedTypeI32, imm, atomicArithmeticOpSub), + ) + case wasm.OpcodeAtomicI64Rmw16SubU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw16SubUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW16(unsignedTypeI64, imm, atomicArithmeticOpSub), + ) + case wasm.OpcodeAtomicI64Rmw32SubU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw32SubUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpSub), + ) + case wasm.OpcodeAtomicI32RmwAnd: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32RmwAndName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpAnd), + ) + case wasm.OpcodeAtomicI64RmwAnd: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64RmwAndName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW(unsignedTypeI64, imm, atomicArithmeticOpAnd), + ) + case wasm.OpcodeAtomicI32Rmw8AndU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw8AndUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW8(unsignedTypeI32, imm, atomicArithmeticOpAnd), + ) + case wasm.OpcodeAtomicI64Rmw8AndU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw8AndUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW8(unsignedTypeI64, imm, atomicArithmeticOpAnd), + ) + case wasm.OpcodeAtomicI32Rmw16AndU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw16AndUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW16(unsignedTypeI32, imm, atomicArithmeticOpAnd), + ) + case wasm.OpcodeAtomicI64Rmw16AndU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw16AndUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW16(unsignedTypeI64, imm, atomicArithmeticOpAnd), + ) + case wasm.OpcodeAtomicI64Rmw32AndU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw32AndUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpAnd), + ) + case wasm.OpcodeAtomicI32RmwOr: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32RmwOrName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpOr), + ) + case wasm.OpcodeAtomicI64RmwOr: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64RmwOrName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW(unsignedTypeI64, imm, atomicArithmeticOpOr), + ) + case wasm.OpcodeAtomicI32Rmw8OrU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw8OrUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW8(unsignedTypeI32, imm, atomicArithmeticOpOr), + ) + case wasm.OpcodeAtomicI64Rmw8OrU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw8OrUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW8(unsignedTypeI64, imm, atomicArithmeticOpOr), + ) + case wasm.OpcodeAtomicI32Rmw16OrU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw16OrUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW16(unsignedTypeI32, imm, atomicArithmeticOpOr), + ) + case wasm.OpcodeAtomicI64Rmw16OrU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw16OrUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW16(unsignedTypeI64, imm, atomicArithmeticOpOr), + ) + case wasm.OpcodeAtomicI64Rmw32OrU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw32OrUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpOr), + ) + case wasm.OpcodeAtomicI32RmwXor: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32RmwXorName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpXor), + ) + case wasm.OpcodeAtomicI64RmwXor: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64RmwXorName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW(unsignedTypeI64, imm, atomicArithmeticOpXor), + ) + case wasm.OpcodeAtomicI32Rmw8XorU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw8XorUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW8(unsignedTypeI32, imm, atomicArithmeticOpXor), + ) + case wasm.OpcodeAtomicI64Rmw8XorU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw8XorUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW8(unsignedTypeI64, imm, atomicArithmeticOpXor), + ) + case wasm.OpcodeAtomicI32Rmw16XorU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw16XorUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW16(unsignedTypeI32, imm, atomicArithmeticOpXor), + ) + case wasm.OpcodeAtomicI64Rmw16XorU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw16XorUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW16(unsignedTypeI64, imm, atomicArithmeticOpXor), + ) + case wasm.OpcodeAtomicI64Rmw32XorU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw32XorUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpXor), + ) + case wasm.OpcodeAtomicI32RmwXchg: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32RmwXchgName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpNop), + ) + case wasm.OpcodeAtomicI64RmwXchg: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64RmwXchgName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW(unsignedTypeI64, imm, atomicArithmeticOpNop), + ) + case wasm.OpcodeAtomicI32Rmw8XchgU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw8XchgUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW8(unsignedTypeI32, imm, atomicArithmeticOpNop), + ) + case wasm.OpcodeAtomicI64Rmw8XchgU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw8XchgUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW8(unsignedTypeI64, imm, atomicArithmeticOpNop), + ) + case wasm.OpcodeAtomicI32Rmw16XchgU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw16XchgUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW16(unsignedTypeI32, imm, atomicArithmeticOpNop), + ) + case wasm.OpcodeAtomicI64Rmw16XchgU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw16XchgUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW16(unsignedTypeI64, imm, atomicArithmeticOpNop), + ) + case wasm.OpcodeAtomicI64Rmw32XchgU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw32XchgUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW(unsignedTypeI32, imm, atomicArithmeticOpNop), + ) + case wasm.OpcodeAtomicI32RmwCmpxchg: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32RmwCmpxchgName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMWCmpxchg(unsignedTypeI32, imm), + ) + case wasm.OpcodeAtomicI64RmwCmpxchg: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64RmwCmpxchgName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMWCmpxchg(unsignedTypeI64, imm), + ) + case wasm.OpcodeAtomicI32Rmw8CmpxchgU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw8CmpxchgUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW8Cmpxchg(unsignedTypeI32, imm), + ) + case wasm.OpcodeAtomicI64Rmw8CmpxchgU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw8CmpxchgUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW8Cmpxchg(unsignedTypeI64, imm), + ) + case wasm.OpcodeAtomicI32Rmw16CmpxchgU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI32Rmw16CmpxchgUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW16Cmpxchg(unsignedTypeI32, imm), + ) + case wasm.OpcodeAtomicI64Rmw16CmpxchgU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw16CmpxchgUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMW16Cmpxchg(unsignedTypeI64, imm), + ) + case wasm.OpcodeAtomicI64Rmw32CmpxchgU: + imm, err := c.readMemoryArg(wasm.OpcodeAtomicI64Rmw32CmpxchgUName) + if err != nil { + return err + } + c.emit( + newOperationAtomicRMWCmpxchg(unsignedTypeI32, imm), + ) + default: + return fmt.Errorf("unsupported atomic instruction in interpreterir: %s", wasm.AtomicInstructionName(atomicOp)) + } + + case wasm.OpcodeTailCallReturnCall: + fdef := c.module.FunctionDefinition(index) + functionFrame := c.controlFrames.functionFrame() + // Currently we do not support imported functions, we treat them as regular calls. + // For details, see internal/engine/RATIONALE.md + if _, _, isImport := fdef.Import(); isImport { + c.emit(newOperationCall(index)) + dropOp := newOperationDrop(c.getFrameDropRange(functionFrame, false)) + + // Cleanup the stack and then jmp to function frame's continuation (meaning return). + c.emit(dropOp) + c.emit(newOperationBr(functionFrame.asLabel())) + } else { + c.emit(newOperationTailCallReturnCall(index)) + } + + // Return operation is stack-polymorphic, and mark the state as unreachable. + // That means subsequent instructions in the current control frame are "unreachable" + // and can be safely removed. + c.markUnreachable() + + case wasm.OpcodeTailCallReturnCallIndirect: + typeIndex := index + tableIndex, n, err := leb128.LoadUint32(c.body[c.pc+1:]) + if err != nil { + return fmt.Errorf("read target for br_table: %w", err) + } + c.pc += n + + functionFrame := c.controlFrames.functionFrame() + dropRange := c.getFrameDropRange(functionFrame, false) + c.emit(newOperationTailCallReturnCallIndirect(typeIndex, tableIndex, dropRange, functionFrame.asLabel())) + + // Return operation is stack-polymorphic, and mark the state as unreachable. + // That means subsequent instructions in the current control frame are "unreachable" + // and can be safely removed. + c.markUnreachable() + + default: + return fmt.Errorf("unsupported instruction in interpreterir: 0x%x", op) + } + + // Move the program counter to point to the next instruction. + c.pc++ + return nil +} + +func (c *compiler) nextFrameID() (id uint32) { + id = c.currentFrameID + 1 + c.currentFrameID++ + return +} + +func (c *compiler) applyToStack(opcode wasm.Opcode) (index uint32, err error) { + switch opcode { + case + // These are the opcodes that is coupled with "index" immediate + // and it DOES affect the signature of opcode. + wasm.OpcodeCall, + wasm.OpcodeCallIndirect, + wasm.OpcodeLocalGet, + wasm.OpcodeLocalSet, + wasm.OpcodeLocalTee, + wasm.OpcodeGlobalGet, + wasm.OpcodeGlobalSet, + // tail-call proposal + wasm.OpcodeTailCallReturnCall, + wasm.OpcodeTailCallReturnCallIndirect: + // Assumes that we are at the opcode now so skip it before read immediates. + v, num, err := leb128.LoadUint32(c.body[c.pc+1:]) + if err != nil { + return 0, fmt.Errorf("reading immediates: %w", err) + } + c.pc += num + index = v + default: + // Note that other opcodes are free of index + // as it doesn't affect the signature of opt code. + // In other words, the "index" argument of wasmOpcodeSignature + // is ignored there. + } + + if c.unreachableState.on { + return 0, nil + } + + // Retrieve the signature of the opcode. + s, err := c.wasmOpcodeSignature(opcode, index) + if err != nil { + return 0, err + } + + // Manipulate the stack according to the signature. + // Note that the following algorithm assumes that + // the unknown type is unique in the signature, + // and is determined by the actual type on the stack. + // The determined type is stored in this typeParam. + var typeParam unsignedType + var typeParamFound bool + for i := range s.in { + want := s.in[len(s.in)-1-i] + actual := c.stackPop() + if want == unsignedTypeUnknown && typeParamFound { + want = typeParam + } else if want == unsignedTypeUnknown { + want = actual + typeParam = want + typeParamFound = true + } + if want != actual { + return 0, fmt.Errorf("input signature mismatch: want %s but have %s", want, actual) + } + } + + for _, target := range s.out { + if target == unsignedTypeUnknown && !typeParamFound { + return 0, fmt.Errorf("cannot determine type of unknown result") + } else if target == unsignedTypeUnknown { + c.stackPush(typeParam) + } else { + c.stackPush(target) + } + } + + return index, nil +} + +func (c *compiler) stackPeek() (ret unsignedType) { + ret = c.stack[len(c.stack)-1] + return +} + +func (c *compiler) stackSwitchAt(frame *controlFrame) { + c.stack = c.stack[:frame.originalStackLenWithoutParam] + c.stackLenInUint64 = frame.originalStackLenWithoutParamUint64 +} + +func (c *compiler) stackPop() (ret unsignedType) { + // No need to check stack bound + // as we can assume that all the operations + // are valid thanks to validateFunction + // at module validation phase. + ret = c.stack[len(c.stack)-1] + c.stack = c.stack[:len(c.stack)-1] + c.stackLenInUint64 -= 1 + int(unsignedTypeV128&ret>>2) + return +} + +func (c *compiler) stackPush(ts unsignedType) { + c.stack = append(c.stack, ts) + c.stackLenInUint64 += 1 + int(unsignedTypeV128&ts>>2) +} + +// emit adds the operations into the result. +func (c *compiler) emit(op unionOperation) { + if !c.unreachableState.on { + switch op.Kind { + case operationKindDrop: + // If the drop range is nil, + // we could remove such operations. + // That happens when drop operation is unnecessary. + // i.e. when there's no need to adjust stack before jmp. + if int64(op.U1) == -1 { + return + } + } + c.result.Operations = append(c.result.Operations, op) + if c.needSourceOffset { + c.result.IROperationSourceOffsetsInWasmBinary = append(c.result.IROperationSourceOffsetsInWasmBinary, + c.currentOpPC+c.bodyOffsetInCodeSection) + } + } +} + +// Emit const expression with default values of the given type. +func (c *compiler) emitDefaultValue(t wasm.ValueType) { + switch t { + case wasm.ValueTypeI32: + c.stackPush(unsignedTypeI32) + c.emit(newOperationConstI32(0)) + case wasm.ValueTypeI64, wasm.ValueTypeExternref, wasm.ValueTypeFuncref: + c.stackPush(unsignedTypeI64) + c.emit(newOperationConstI64(0)) + case wasm.ValueTypeF32: + c.stackPush(unsignedTypeF32) + c.emit(newOperationConstF32(0)) + case wasm.ValueTypeF64: + c.stackPush(unsignedTypeF64) + c.emit(newOperationConstF64(0)) + case wasm.ValueTypeV128: + c.stackPush(unsignedTypeV128) + c.emit(newOperationV128Const(0, 0)) + } +} + +// Returns the "depth" (starting from top of the stack) +// of the n-th local. +func (c *compiler) localDepth(index wasm.Index) int { + height := c.localIndexToStackHeightInUint64[index] + return c.stackLenInUint64 - 1 - height +} + +func (c *compiler) localType(index wasm.Index) (t wasm.ValueType) { + if params := uint32(len(c.sig.Params)); index < params { + t = c.sig.Params[index] + } else { + t = c.localTypes[index-params] + } + return +} + +// getFrameDropRange returns the range (starting from top of the stack) that spans across the (uint64) stack. The range is +// supposed to be dropped from the stack when the given frame exists or branch into it. +// +// * frame is the control frame which the call-site is trying to branch into or exit. +// * isEnd true if the call-site is handling wasm.OpcodeEnd. +func (c *compiler) getFrameDropRange(frame *controlFrame, isEnd bool) inclusiveRange { + var start int + if !isEnd && frame.kind == controlFrameKindLoop { + // If this is not End and the call-site is trying to branch into the Loop control frame, + // we have to Start executing from the beginning of the loop block. + // Therefore, we have to pass the inputs to the frame. + start = frame.blockType.ParamNumInUint64 + } else { + start = frame.blockType.ResultNumInUint64 + } + end := c.stackLenInUint64 - 1 - frame.originalStackLenWithoutParamUint64 + if start <= end { + return inclusiveRange{Start: int32(start), End: int32(end)} + } else { + return nopinclusiveRange + } +} + +func (c *compiler) readMemoryArg(tag string) (memoryArg, error) { + c.result.UsesMemory = true + alignment, num, err := leb128.LoadUint32(c.body[c.pc+1:]) + if err != nil { + return memoryArg{}, fmt.Errorf("reading alignment for %s: %w", tag, err) + } + c.pc += num + offset, num, err := leb128.LoadUint32(c.body[c.pc+1:]) + if err != nil { + return memoryArg{}, fmt.Errorf("reading offset for %s: %w", tag, err) + } + c.pc += num + return memoryArg{Offset: offset, Alignment: alignment}, nil +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/interpreter/format.go b/vendor/github.com/tetratelabs/wazero/internal/engine/interpreter/format.go new file mode 100644 index 00000000000..8af1d94b0cf --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/interpreter/format.go @@ -0,0 +1,22 @@ +package interpreter + +import ( + "bytes" +) + +func format(ops []unionOperation) string { + buf := bytes.NewBuffer(nil) + + _, _ = buf.WriteString(".entrypoint\n") + for i := range ops { + op := &ops[i] + str := op.String() + isLabel := op.Kind == operationKindLabel + if !isLabel { + const indent = "\t" + str = indent + str + } + _, _ = buf.WriteString(str + "\n") + } + return buf.String() +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/interpreter/interpreter.go b/vendor/github.com/tetratelabs/wazero/internal/engine/interpreter/interpreter.go new file mode 100644 index 00000000000..70183e23b79 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/interpreter/interpreter.go @@ -0,0 +1,4669 @@ +package interpreter + +import ( + "context" + "encoding/binary" + "errors" + "fmt" + "math" + "math/bits" + "slices" + "sync" + "unsafe" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental" + "github.com/tetratelabs/wazero/internal/expctxkeys" + "github.com/tetratelabs/wazero/internal/filecache" + "github.com/tetratelabs/wazero/internal/internalapi" + "github.com/tetratelabs/wazero/internal/moremath" + "github.com/tetratelabs/wazero/internal/wasm" + "github.com/tetratelabs/wazero/internal/wasmdebug" + "github.com/tetratelabs/wazero/internal/wasmruntime" +) + +// callStackCeiling is the maximum WebAssembly call frame stack height. This allows wazero to raise +// wasm.ErrCallStackOverflow instead of overflowing the Go runtime. +// +// The default value should suffice for most use cases. Those wishing to change this can via `go build -ldflags`. +var callStackCeiling = 2000 + +type compiledFunctionWithCount struct { + funcs []compiledFunction + refCount int +} + +// engine is an interpreter implementation of wasm.Engine +type engine struct { + enabledFeatures api.CoreFeatures + compiledFunctions map[wasm.ModuleID]*compiledFunctionWithCount // guarded by mutex. + mux sync.Mutex +} + +func NewEngine(_ context.Context, enabledFeatures api.CoreFeatures, _ filecache.Cache) wasm.Engine { + return &engine{ + enabledFeatures: enabledFeatures, + compiledFunctions: map[wasm.ModuleID]*compiledFunctionWithCount{}, + } +} + +// Close implements the same method as documented on wasm.Engine. +func (e *engine) Close() (err error) { + e.mux.Lock() + defer e.mux.Unlock() + clear(e.compiledFunctions) + return +} + +// CompiledModuleCount implements the same method as documented on wasm.Engine. +func (e *engine) CompiledModuleCount() uint32 { + e.mux.Lock() + defer e.mux.Unlock() + return uint32(len(e.compiledFunctions)) +} + +// DeleteCompiledModule implements the same method as documented on wasm.Engine. +func (e *engine) DeleteCompiledModule(m *wasm.Module) { + e.deleteCompiledFunctions(m) +} + +func (e *engine) deleteCompiledFunctions(module *wasm.Module) { + e.mux.Lock() + defer e.mux.Unlock() + cf, ok := e.compiledFunctions[module.ID] + if !ok { + return + } + cf.refCount-- + if cf.refCount > 0 { + return + } + delete(e.compiledFunctions, module.ID) +} + +func (e *engine) addCompiledFunctions(module *wasm.Module, fs []compiledFunction) { + e.mux.Lock() + defer e.mux.Unlock() + if c, ok := e.compiledFunctions[module.ID]; ok { + c.refCount++ + return + } + e.compiledFunctions[module.ID] = &compiledFunctionWithCount{funcs: fs, refCount: 1} +} + +func (e *engine) getCompiledFunctions(module *wasm.Module, increaseRefCount bool) (fs []compiledFunction, ok bool) { + e.mux.Lock() + defer e.mux.Unlock() + cf, ok := e.compiledFunctions[module.ID] + if ok { + fs = cf.funcs + if increaseRefCount { + cf.refCount++ + } + } + return +} + +// moduleEngine implements wasm.ModuleEngine +type moduleEngine struct { + // codes are the compiled functions in a module instances. + // The index is module instance-scoped. + functions []function + + // parentEngine holds *engine from which this module engine is created from. + parentEngine *engine +} + +// GetGlobalValue implements the same method as documented on wasm.ModuleEngine. +func (e *moduleEngine) GetGlobalValue(wasm.Index) (lo, hi uint64) { + panic("BUG: GetGlobalValue should never be called on interpreter mode") +} + +// SetGlobalValue implements the same method as documented on wasm.ModuleEngine. +func (e *moduleEngine) SetGlobalValue(idx wasm.Index, lo, hi uint64) { + panic("BUG: SetGlobalValue should never be called on interpreter mode") +} + +// OwnsGlobals implements the same method as documented on wasm.ModuleEngine. +func (e *moduleEngine) OwnsGlobals() bool { return false } + +// MemoryGrown implements wasm.ModuleEngine. +func (e *moduleEngine) MemoryGrown() {} + +// callEngine holds context per moduleEngine.Call, and shared across all the +// function calls originating from the same moduleEngine.Call execution. +// +// This implements api.Function. +type callEngine struct { + internalapi.WazeroOnlyType + + // stack contains the operands. + // Note that all the values are represented as uint64. + stack []uint64 + + // frames are the function call stack. + frames []*callFrame + + // f is the initial function for this call engine. + f *function + + // stackiterator for Listeners to walk frames and stack. + stackIterator stackIterator +} + +func (e *moduleEngine) newCallEngine(compiled *function) *callEngine { + return &callEngine{f: compiled} +} + +func (ce *callEngine) pushValue(v uint64) { + ce.stack = append(ce.stack, v) +} + +func (ce *callEngine) pushValues(v []uint64) { + ce.stack = append(ce.stack, v...) +} + +func (ce *callEngine) popValue() (v uint64) { + // No need to check stack bound + // as we can assume that all the operations + // are valid thanks to validateFunction + // at module validation phase + // and interpreterir translation + // before compilation. + stackTopIndex := len(ce.stack) - 1 + v = ce.stack[stackTopIndex] + ce.stack = ce.stack[:stackTopIndex] + return +} + +func (ce *callEngine) popValues(v []uint64) { + stackTopIndex := len(ce.stack) - len(v) + copy(v, ce.stack[stackTopIndex:]) + ce.stack = ce.stack[:stackTopIndex] +} + +// peekValues peeks api.ValueType values from the stack and returns them. +func (ce *callEngine) peekValues(count int) []uint64 { + if count == 0 { + return nil + } + stackLen := len(ce.stack) + return ce.stack[stackLen-count : stackLen] +} + +func (ce *callEngine) drop(raw uint64) { + r := inclusiveRangeFromU64(raw) + if r.Start == -1 { + return + } else if r.Start == 0 { + ce.stack = ce.stack[:int32(len(ce.stack))-1-r.End] + } else { + newStack := ce.stack[:int32(len(ce.stack))-1-r.End] + newStack = append(newStack, ce.stack[int32(len(ce.stack))-r.Start:]...) + ce.stack = newStack + } +} + +func (ce *callEngine) pushFrame(frame *callFrame) { + if callStackCeiling <= len(ce.frames) { + panic(wasmruntime.ErrRuntimeStackOverflow) + } + ce.frames = append(ce.frames, frame) +} + +func (ce *callEngine) popFrame() (frame *callFrame) { + // No need to check stack bound as we can assume that all the operations are valid thanks to validateFunction at + // module validation phase and interpreterir translation before compilation. + oneLess := len(ce.frames) - 1 + frame = ce.frames[oneLess] + ce.frames = ce.frames[:oneLess] + return +} + +type callFrame struct { + // pc is the program counter representing the current position in code.body. + pc uint64 + // f is the compiled function used in this function frame. + f *function + // base index in the frame of this function, used to detect the count of + // values on the stack. + base int +} + +type compiledFunction struct { + source *wasm.Module + body []unionOperation + listener experimental.FunctionListener + offsetsInWasmBinary []uint64 + hostFn interface{} + ensureTermination bool + index wasm.Index +} + +type function struct { + funcType *wasm.FunctionType + moduleInstance *wasm.ModuleInstance + typeID wasm.FunctionTypeID + parent *compiledFunction +} + +// functionFromUintptr resurrects the original *function from the given uintptr +// which comes from either funcref table or OpcodeRefFunc instruction. +func functionFromUintptr(ptr uintptr) *function { + // Wraps ptrs as the double pointer in order to avoid the unsafe access as detected by race detector. + // + // For example, if we have (*function)(unsafe.Pointer(ptr)) instead, then the race detector's "checkptr" + // subroutine wanrs as "checkptr: pointer arithmetic result points to invalid allocation" + // https://github.com/golang/go/blob/go1.24.0/src/runtime/checkptr.go#L69 + var wrapped *uintptr = &ptr + return *(**function)(unsafe.Pointer(wrapped)) +} + +type snapshot struct { + stack []uint64 + frames []*callFrame + pc uint64 + + ret []uint64 + + ce *callEngine +} + +// Snapshot implements the same method as documented on experimental.Snapshotter. +func (ce *callEngine) Snapshot() experimental.Snapshot { + return &snapshot{ + stack: slices.Clone(ce.stack), + frames: slices.Clone(ce.frames), + ce: ce, + } +} + +// Restore implements the same method as documented on experimental.Snapshot. +func (s *snapshot) Restore(ret []uint64) { + s.ret = ret + panic(s) +} + +func (s *snapshot) doRestore() { + ce := s.ce + + ce.stack = s.stack + ce.frames = s.frames + ce.frames[len(ce.frames)-1].pc = s.pc + + copy(ce.stack[len(ce.stack)-len(s.ret):], s.ret) +} + +// Error implements the same method on error. +func (s *snapshot) Error() string { + return "unhandled snapshot restore, this generally indicates restore was called from a different " + + "exported function invocation than snapshot" +} + +// stackIterator implements experimental.StackIterator. +type stackIterator struct { + stack []uint64 + frames []*callFrame + started bool + fn *function + pc uint64 +} + +func (si *stackIterator) reset(stack []uint64, frames []*callFrame, f *function) { + si.fn = f + si.pc = 0 + si.stack = stack + si.frames = frames + si.started = false +} + +func (si *stackIterator) clear() { + si.stack = nil + si.frames = nil + si.started = false + si.fn = nil +} + +// Next implements the same method as documented on experimental.StackIterator. +func (si *stackIterator) Next() bool { + if !si.started { + si.started = true + return true + } + + if len(si.frames) == 0 { + return false + } + + frame := si.frames[len(si.frames)-1] + si.stack = si.stack[:frame.base] + si.fn = frame.f + si.pc = frame.pc + si.frames = si.frames[:len(si.frames)-1] + return true +} + +// Function implements the same method as documented on +// experimental.StackIterator. +func (si *stackIterator) Function() experimental.InternalFunction { + return internalFunction{si.fn} +} + +// ProgramCounter implements the same method as documented on +// experimental.StackIterator. +func (si *stackIterator) ProgramCounter() experimental.ProgramCounter { + return experimental.ProgramCounter(si.pc) +} + +// internalFunction implements experimental.InternalFunction. +type internalFunction struct{ *function } + +// Definition implements the same method as documented on +// experimental.InternalFunction. +func (f internalFunction) Definition() api.FunctionDefinition { + return f.definition() +} + +// SourceOffsetForPC implements the same method as documented on +// experimental.InternalFunction. +func (f internalFunction) SourceOffsetForPC(pc experimental.ProgramCounter) uint64 { + offsetsMap := f.parent.offsetsInWasmBinary + if uint64(pc) < uint64(len(offsetsMap)) { + return offsetsMap[pc] + } + return 0 +} + +// interpreter mode doesn't maintain call frames in the stack, so pass the zero size to the IR. +const callFrameStackSize = 0 + +// CompileModule implements the same method as documented on wasm.Engine. +func (e *engine) CompileModule(_ context.Context, module *wasm.Module, listeners []experimental.FunctionListener, ensureTermination bool) error { + if _, ok := e.getCompiledFunctions(module, true); ok { // cache hit! + return nil + } + + funcs := make([]compiledFunction, len(module.FunctionSection)) + irCompiler, err := newCompiler(e.enabledFeatures, callFrameStackSize, module, ensureTermination) + if err != nil { + return err + } + imported := module.ImportFunctionCount + for i := range module.CodeSection { + var lsn experimental.FunctionListener + if i < len(listeners) { + lsn = listeners[i] + } + + compiled := &funcs[i] + // If this is the host function, there's nothing to do as the runtime representation of + // host function in interpreter is its Go function itself as opposed to Wasm functions, + // which need to be compiled down to + if codeSeg := &module.CodeSection[i]; codeSeg.GoFunc != nil { + compiled.hostFn = codeSeg.GoFunc + } else { + ir, err := irCompiler.Next() + if err != nil { + return err + } + err = e.lowerIR(ir, compiled) + if err != nil { + def := module.FunctionDefinition(uint32(i) + module.ImportFunctionCount) + return fmt.Errorf("failed to lower func[%s] to interpreterir: %w", def.DebugName(), err) + } + } + compiled.source = module + compiled.ensureTermination = ensureTermination + compiled.listener = lsn + compiled.index = imported + uint32(i) + } + e.addCompiledFunctions(module, funcs) + return nil +} + +// NewModuleEngine implements the same method as documented on wasm.Engine. +func (e *engine) NewModuleEngine(module *wasm.Module, instance *wasm.ModuleInstance) (wasm.ModuleEngine, error) { + me := &moduleEngine{ + parentEngine: e, + functions: make([]function, len(module.FunctionSection)+int(module.ImportFunctionCount)), + } + + codes, ok := e.getCompiledFunctions(module, false) + if !ok { + return nil, errors.New("source module must be compiled before instantiation") + } + + for i := range codes { + c := &codes[i] + offset := i + int(module.ImportFunctionCount) + typeIndex := module.FunctionSection[i] + me.functions[offset] = function{ + moduleInstance: instance, + typeID: instance.TypeIDs[typeIndex], + funcType: &module.TypeSection[typeIndex], + parent: c, + } + } + return me, nil +} + +// lowerIR lowers the interpreterir operations to engine friendly struct. +func (e *engine) lowerIR(ir *compilationResult, ret *compiledFunction) error { + // Copy the body from the result. + ret.body = slices.Clone(ir.Operations) + // Also copy the offsets if necessary. + if offsets := ir.IROperationSourceOffsetsInWasmBinary; len(offsets) > 0 { + ret.offsetsInWasmBinary = slices.Clone(offsets) + } + + labelAddressResolutions := [labelKindNum][]uint64{} + + // First, we iterate all labels, and resolve the address. + for i := range ret.body { + op := &ret.body[i] + switch op.Kind { + case operationKindLabel: + label := label(op.U1) + address := uint64(i) + + kind, fid := label.Kind(), label.FrameID() + frameToAddresses := labelAddressResolutions[label.Kind()] + // Expand the slice if necessary. + if diff := fid - len(frameToAddresses) + 1; diff > 0 { + frameToAddresses = append(frameToAddresses, make([]uint64, diff)...) + } + frameToAddresses[fid] = address + labelAddressResolutions[kind] = frameToAddresses + } + } + + // Then resolve the label as the index to the body. + for i := range ret.body { + op := &ret.body[i] + switch op.Kind { + case operationKindBr: + e.setLabelAddress(&op.U1, label(op.U1), labelAddressResolutions) + case operationKindBrIf: + e.setLabelAddress(&op.U1, label(op.U1), labelAddressResolutions) + e.setLabelAddress(&op.U2, label(op.U2), labelAddressResolutions) + case operationKindBrTable: + for j := 0; j < len(op.Us); j += 2 { + target := op.Us[j] + e.setLabelAddress(&op.Us[j], label(target), labelAddressResolutions) + } + case operationKindTailCallReturnCallIndirect: + e.setLabelAddress(&op.Us[1], label(op.Us[1]), labelAddressResolutions) + } + } + return nil +} + +func (e *engine) setLabelAddress(op *uint64, label label, labelAddressResolutions [labelKindNum][]uint64) { + if label.IsReturnTarget() { + // Jmp to the end of the possible binary. + *op = math.MaxUint64 + } else { + *op = labelAddressResolutions[label.Kind()][label.FrameID()] + } +} + +// ResolveImportedFunction implements wasm.ModuleEngine. +func (e *moduleEngine) ResolveImportedFunction(index, descFunc, indexInImportedModule wasm.Index, importedModuleEngine wasm.ModuleEngine) { + imported := importedModuleEngine.(*moduleEngine) + e.functions[index] = imported.functions[indexInImportedModule] +} + +// ResolveImportedMemory implements wasm.ModuleEngine. +func (e *moduleEngine) ResolveImportedMemory(wasm.ModuleEngine) {} + +// DoneInstantiation implements wasm.ModuleEngine. +func (e *moduleEngine) DoneInstantiation() {} + +// FunctionInstanceReference implements the same method as documented on wasm.ModuleEngine. +func (e *moduleEngine) FunctionInstanceReference(funcIndex wasm.Index) wasm.Reference { + return uintptr(unsafe.Pointer(&e.functions[funcIndex])) +} + +// NewFunction implements the same method as documented on wasm.ModuleEngine. +func (e *moduleEngine) NewFunction(index wasm.Index) (ce api.Function) { + // Note: The input parameters are pre-validated, so a compiled function is only absent on close. Updates to + // code on close aren't locked, neither is this read. + compiled := &e.functions[index] + return e.newCallEngine(compiled) +} + +// LookupFunction implements the same method as documented on wasm.ModuleEngine. +func (e *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.FunctionTypeID, tableOffset wasm.Index) (*wasm.ModuleInstance, wasm.Index) { + if tableOffset >= uint32(len(t.References)) { + panic(wasmruntime.ErrRuntimeInvalidTableAccess) + } + rawPtr := t.References[tableOffset] + if rawPtr == 0 { + panic(wasmruntime.ErrRuntimeInvalidTableAccess) + } + + tf := functionFromUintptr(rawPtr) + if tf.typeID != typeId { + panic(wasmruntime.ErrRuntimeIndirectCallTypeMismatch) + } + return tf.moduleInstance, tf.parent.index +} + +// Definition implements the same method as documented on api.Function. +func (ce *callEngine) Definition() api.FunctionDefinition { + return ce.f.definition() +} + +func (f *function) definition() api.FunctionDefinition { + compiled := f.parent + return compiled.source.FunctionDefinition(compiled.index) +} + +// Call implements the same method as documented on api.Function. +func (ce *callEngine) Call(ctx context.Context, params ...uint64) (results []uint64, err error) { + ft := ce.f.funcType + if n := ft.ParamNumInUint64; n != len(params) { + return nil, fmt.Errorf("expected %d params, but passed %d", n, len(params)) + } + return ce.call(ctx, params, nil) +} + +// CallWithStack implements the same method as documented on api.Function. +func (ce *callEngine) CallWithStack(ctx context.Context, stack []uint64) error { + params, results, err := wasm.SplitCallStack(ce.f.funcType, stack) + if err != nil { + return err + } + _, err = ce.call(ctx, params, results) + return err +} + +func (ce *callEngine) call(ctx context.Context, params, results []uint64) (_ []uint64, err error) { + m := ce.f.moduleInstance + if ce.f.parent.ensureTermination { + select { + case <-ctx.Done(): + // If the provided context is already done, close the call context + // and return the error. + m.CloseWithCtxErr(ctx) + return nil, m.FailIfClosed() + default: + } + } + + if ctx.Value(expctxkeys.EnableSnapshotterKey{}) != nil { + ctx = context.WithValue(ctx, expctxkeys.SnapshotterKey{}, ce) + } + + defer func() { + // If the module closed during the call, and the call didn't err for another reason, set an ExitError. + if err == nil { + err = m.FailIfClosed() + } + // TODO: ^^ Will not fail if the function was imported from a closed module. + + if v := recover(); v != nil { + err = ce.recoverOnCall(ctx, m, v) + } + }() + + ce.pushValues(params) + + if ce.f.parent.ensureTermination { + done := m.CloseModuleOnCanceledOrTimeout(ctx) + defer done() + } + + ce.callFunction(ctx, m, ce.f) + + // This returns a safe copy of the results, instead of a slice view. If we + // returned a re-slice, the caller could accidentally or purposefully + // corrupt the stack of subsequent calls. + ft := ce.f.funcType + if results == nil && ft.ResultNumInUint64 > 0 { + results = make([]uint64, ft.ResultNumInUint64) + } + ce.popValues(results) + return results, nil +} + +// functionListenerInvocation captures arguments needed to perform function +// listener invocations when unwinding the call stack. +type functionListenerInvocation struct { + experimental.FunctionListener + def api.FunctionDefinition +} + +// recoverOnCall takes the recovered value `recoverOnCall`, and wraps it +// with the call frame stack traces. Also, reset the state of callEngine +// so that it can be used for the subsequent calls. +func (ce *callEngine) recoverOnCall(ctx context.Context, m *wasm.ModuleInstance, v interface{}) (err error) { + if s, ok := v.(*snapshot); ok { + // A snapshot that wasn't handled was created by a different call engine possibly from a nested wasm invocation, + // let it propagate up to be handled by the caller. + panic(s) + } + + builder := wasmdebug.NewErrorBuilder() + frameCount := len(ce.frames) + functionListeners := make([]functionListenerInvocation, 0, 16) + + if frameCount > wasmdebug.MaxFrames { + frameCount = wasmdebug.MaxFrames + } + for i := 0; i < frameCount; i++ { + frame := ce.popFrame() + f := frame.f + def := f.definition() + var sources []string + if parent := frame.f.parent; parent.body != nil && len(parent.offsetsInWasmBinary) > 0 { + sources = parent.source.DWARFLines.Line(parent.offsetsInWasmBinary[frame.pc]) + } + builder.AddFrame(def.DebugName(), def.ParamTypes(), def.ResultTypes(), sources) + if f.parent.listener != nil { + functionListeners = append(functionListeners, functionListenerInvocation{ + FunctionListener: f.parent.listener, + def: f.definition(), + }) + } + } + + err = builder.FromRecovered(v) + for i := range functionListeners { + functionListeners[i].Abort(ctx, m, functionListeners[i].def, err) + } + + // Allows the reuse of CallEngine. + ce.stack, ce.frames = ce.stack[:0], ce.frames[:0] + return +} + +func (ce *callEngine) callFunction(ctx context.Context, m *wasm.ModuleInstance, f *function) { + if f.parent.hostFn != nil { + ce.callGoFuncWithStack(ctx, m, f) + } else if lsn := f.parent.listener; lsn != nil { + ce.callNativeFuncWithListener(ctx, m, f, lsn) + } else { + ce.callNativeFunc(ctx, m, f) + } +} + +func (ce *callEngine) callGoFunc(ctx context.Context, m *wasm.ModuleInstance, f *function, stack []uint64) { + typ := f.funcType + lsn := f.parent.listener + if lsn != nil { + params := stack[:typ.ParamNumInUint64] + ce.stackIterator.reset(ce.stack, ce.frames, f) + lsn.Before(ctx, m, f.definition(), params, &ce.stackIterator) + ce.stackIterator.clear() + } + frame := &callFrame{f: f, base: len(ce.stack)} + ce.pushFrame(frame) + + fn := f.parent.hostFn + switch fn := fn.(type) { + case api.GoModuleFunction: + fn.Call(ctx, m, stack) + case api.GoFunction: + fn.Call(ctx, stack) + } + + ce.popFrame() + if lsn != nil { + // TODO: This doesn't get the error due to use of panic to propagate them. + results := stack[:typ.ResultNumInUint64] + lsn.After(ctx, m, f.definition(), results) + } +} + +func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance, f *function) { + frame := &callFrame{f: f, base: len(ce.stack)} + moduleInst := f.moduleInstance + functions := moduleInst.Engine.(*moduleEngine).functions + memoryInst := moduleInst.MemoryInstance + globals := moduleInst.Globals + tables := moduleInst.Tables + typeIDs := moduleInst.TypeIDs + dataInstances := moduleInst.DataInstances + elementInstances := moduleInst.ElementInstances + ce.pushFrame(frame) + body := frame.f.parent.body + bodyLen := uint64(len(body)) + for frame.pc < bodyLen { + op := &body[frame.pc] + // TODO: add description of each operation/case + // on, for example, how many args are used, + // how the stack is modified, etc. + switch op.Kind { + case operationKindBuiltinFunctionCheckExitCode: + if err := m.FailIfClosed(); err != nil { + panic(err) + } + frame.pc++ + case operationKindUnreachable: + panic(wasmruntime.ErrRuntimeUnreachable) + case operationKindBr: + frame.pc = op.U1 + case operationKindBrIf: + if ce.popValue() > 0 { + ce.drop(op.U3) + frame.pc = op.U1 + } else { + frame.pc = op.U2 + } + case operationKindBrTable: + v := ce.popValue() + defaultAt := uint64(len(op.Us))/2 - 1 + if v > defaultAt { + v = defaultAt + } + v *= 2 + ce.drop(op.Us[v+1]) + frame.pc = op.Us[v] + case operationKindCall: + func() { + if ctx.Value(expctxkeys.EnableSnapshotterKey{}) != nil { + defer func() { + if r := recover(); r != nil { + if s, ok := r.(*snapshot); ok && s.ce == ce { + s.doRestore() + frame = ce.frames[len(ce.frames)-1] + body = frame.f.parent.body + bodyLen = uint64(len(body)) + } else { + panic(r) + } + } + }() + } + ce.callFunction(ctx, f.moduleInstance, &functions[op.U1]) + }() + frame.pc++ + case operationKindCallIndirect: + offset := ce.popValue() + table := tables[op.U2] + tf := ce.functionForOffset(table, offset, typeIDs[op.U1]) + + ce.callFunction(ctx, f.moduleInstance, tf) + frame.pc++ + case operationKindDrop: + ce.drop(op.U1) + frame.pc++ + case operationKindSelect: + c := ce.popValue() + if op.B3 { // Target is vector. + x2Hi, x2Lo := ce.popValue(), ce.popValue() + if c == 0 { + _, _ = ce.popValue(), ce.popValue() // discard the x1's lo and hi bits. + ce.pushValue(x2Lo) + ce.pushValue(x2Hi) + } + } else { + v2 := ce.popValue() + if c == 0 { + _ = ce.popValue() + ce.pushValue(v2) + } + } + frame.pc++ + case operationKindPick: + index := len(ce.stack) - 1 - int(op.U1) + ce.pushValue(ce.stack[index]) + if op.B3 { // V128 value target. + ce.pushValue(ce.stack[index+1]) + } + frame.pc++ + case operationKindSet: + if op.B3 { // V128 value target. + lowIndex := len(ce.stack) - 1 - int(op.U1) + highIndex := lowIndex + 1 + hi, lo := ce.popValue(), ce.popValue() + ce.stack[lowIndex], ce.stack[highIndex] = lo, hi + } else { + index := len(ce.stack) - 1 - int(op.U1) + ce.stack[index] = ce.popValue() + } + frame.pc++ + case operationKindGlobalGet: + g := globals[op.U1] + ce.pushValue(g.Val) + if g.Type.ValType == wasm.ValueTypeV128 { + ce.pushValue(g.ValHi) + } + frame.pc++ + case operationKindGlobalSet: + g := globals[op.U1] + if g.Type.ValType == wasm.ValueTypeV128 { + g.ValHi = ce.popValue() + } + g.Val = ce.popValue() + frame.pc++ + case operationKindLoad: + offset := ce.popMemoryOffset(op) + switch unsignedType(op.B1) { + case unsignedTypeI32, unsignedTypeF32: + if val, ok := memoryInst.ReadUint32Le(offset); !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } else { + ce.pushValue(uint64(val)) + } + case unsignedTypeI64, unsignedTypeF64: + if val, ok := memoryInst.ReadUint64Le(offset); !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } else { + ce.pushValue(val) + } + } + frame.pc++ + case operationKindLoad8: + val, ok := memoryInst.ReadByte(ce.popMemoryOffset(op)) + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + + switch signedInt(op.B1) { + case signedInt32: + ce.pushValue(uint64(uint32(int8(val)))) + case signedInt64: + ce.pushValue(uint64(int8(val))) + case signedUint32, signedUint64: + ce.pushValue(uint64(val)) + } + frame.pc++ + case operationKindLoad16: + + val, ok := memoryInst.ReadUint16Le(ce.popMemoryOffset(op)) + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + + switch signedInt(op.B1) { + case signedInt32: + ce.pushValue(uint64(uint32(int16(val)))) + case signedInt64: + ce.pushValue(uint64(int16(val))) + case signedUint32, signedUint64: + ce.pushValue(uint64(val)) + } + frame.pc++ + case operationKindLoad32: + val, ok := memoryInst.ReadUint32Le(ce.popMemoryOffset(op)) + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + + if op.B1 == 1 { // Signed + ce.pushValue(uint64(int32(val))) + } else { + ce.pushValue(uint64(val)) + } + frame.pc++ + case operationKindStore: + val := ce.popValue() + offset := ce.popMemoryOffset(op) + switch unsignedType(op.B1) { + case unsignedTypeI32, unsignedTypeF32: + if !memoryInst.WriteUint32Le(offset, uint32(val)) { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + case unsignedTypeI64, unsignedTypeF64: + if !memoryInst.WriteUint64Le(offset, val) { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + } + frame.pc++ + case operationKindStore8: + val := byte(ce.popValue()) + offset := ce.popMemoryOffset(op) + if !memoryInst.WriteByte(offset, val) { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + frame.pc++ + case operationKindStore16: + val := uint16(ce.popValue()) + offset := ce.popMemoryOffset(op) + if !memoryInst.WriteUint16Le(offset, val) { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + frame.pc++ + case operationKindStore32: + val := uint32(ce.popValue()) + offset := ce.popMemoryOffset(op) + if !memoryInst.WriteUint32Le(offset, val) { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + frame.pc++ + case operationKindMemorySize: + ce.pushValue(uint64(memoryInst.Pages())) + frame.pc++ + case operationKindMemoryGrow: + n := ce.popValue() + if res, ok := memoryInst.Grow(uint32(n)); !ok { + ce.pushValue(uint64(0xffffffff)) // = -1 in signed 32-bit integer. + } else { + ce.pushValue(uint64(res)) + } + frame.pc++ + case operationKindConstI32, operationKindConstI64, + operationKindConstF32, operationKindConstF64: + ce.pushValue(op.U1) + frame.pc++ + case operationKindEq: + var b bool + switch unsignedType(op.B1) { + case unsignedTypeI32: + v2, v1 := ce.popValue(), ce.popValue() + b = uint32(v1) == uint32(v2) + case unsignedTypeI64: + v2, v1 := ce.popValue(), ce.popValue() + b = v1 == v2 + case unsignedTypeF32: + v2, v1 := ce.popValue(), ce.popValue() + b = math.Float32frombits(uint32(v2)) == math.Float32frombits(uint32(v1)) + case unsignedTypeF64: + v2, v1 := ce.popValue(), ce.popValue() + b = math.Float64frombits(v2) == math.Float64frombits(v1) + } + if b { + ce.pushValue(1) + } else { + ce.pushValue(0) + } + frame.pc++ + case operationKindNe: + var b bool + switch unsignedType(op.B1) { + case unsignedTypeI32, unsignedTypeI64: + v2, v1 := ce.popValue(), ce.popValue() + b = v1 != v2 + case unsignedTypeF32: + v2, v1 := ce.popValue(), ce.popValue() + b = math.Float32frombits(uint32(v2)) != math.Float32frombits(uint32(v1)) + case unsignedTypeF64: + v2, v1 := ce.popValue(), ce.popValue() + b = math.Float64frombits(v2) != math.Float64frombits(v1) + } + if b { + ce.pushValue(1) + } else { + ce.pushValue(0) + } + frame.pc++ + case operationKindEqz: + if ce.popValue() == 0 { + ce.pushValue(1) + } else { + ce.pushValue(0) + } + frame.pc++ + case operationKindLt: + v2 := ce.popValue() + v1 := ce.popValue() + var b bool + switch signedType(op.B1) { + case signedTypeInt32: + b = int32(v1) < int32(v2) + case signedTypeInt64: + b = int64(v1) < int64(v2) + case signedTypeUint32, signedTypeUint64: + b = v1 < v2 + case signedTypeFloat32: + b = math.Float32frombits(uint32(v1)) < math.Float32frombits(uint32(v2)) + case signedTypeFloat64: + b = math.Float64frombits(v1) < math.Float64frombits(v2) + } + if b { + ce.pushValue(1) + } else { + ce.pushValue(0) + } + frame.pc++ + case operationKindGt: + v2 := ce.popValue() + v1 := ce.popValue() + var b bool + switch signedType(op.B1) { + case signedTypeInt32: + b = int32(v1) > int32(v2) + case signedTypeInt64: + b = int64(v1) > int64(v2) + case signedTypeUint32, signedTypeUint64: + b = v1 > v2 + case signedTypeFloat32: + b = math.Float32frombits(uint32(v1)) > math.Float32frombits(uint32(v2)) + case signedTypeFloat64: + b = math.Float64frombits(v1) > math.Float64frombits(v2) + } + if b { + ce.pushValue(1) + } else { + ce.pushValue(0) + } + frame.pc++ + case operationKindLe: + v2 := ce.popValue() + v1 := ce.popValue() + var b bool + switch signedType(op.B1) { + case signedTypeInt32: + b = int32(v1) <= int32(v2) + case signedTypeInt64: + b = int64(v1) <= int64(v2) + case signedTypeUint32, signedTypeUint64: + b = v1 <= v2 + case signedTypeFloat32: + b = math.Float32frombits(uint32(v1)) <= math.Float32frombits(uint32(v2)) + case signedTypeFloat64: + b = math.Float64frombits(v1) <= math.Float64frombits(v2) + } + if b { + ce.pushValue(1) + } else { + ce.pushValue(0) + } + frame.pc++ + case operationKindGe: + v2 := ce.popValue() + v1 := ce.popValue() + var b bool + switch signedType(op.B1) { + case signedTypeInt32: + b = int32(v1) >= int32(v2) + case signedTypeInt64: + b = int64(v1) >= int64(v2) + case signedTypeUint32, signedTypeUint64: + b = v1 >= v2 + case signedTypeFloat32: + b = math.Float32frombits(uint32(v1)) >= math.Float32frombits(uint32(v2)) + case signedTypeFloat64: + b = math.Float64frombits(v1) >= math.Float64frombits(v2) + } + if b { + ce.pushValue(1) + } else { + ce.pushValue(0) + } + frame.pc++ + case operationKindAdd: + v2 := ce.popValue() + v1 := ce.popValue() + switch unsignedType(op.B1) { + case unsignedTypeI32: + v := uint32(v1) + uint32(v2) + ce.pushValue(uint64(v)) + case unsignedTypeI64: + ce.pushValue(v1 + v2) + case unsignedTypeF32: + ce.pushValue(addFloat32bits(uint32(v1), uint32(v2))) + case unsignedTypeF64: + v := math.Float64frombits(v1) + math.Float64frombits(v2) + ce.pushValue(math.Float64bits(v)) + } + frame.pc++ + case operationKindSub: + v2 := ce.popValue() + v1 := ce.popValue() + switch unsignedType(op.B1) { + case unsignedTypeI32: + ce.pushValue(uint64(uint32(v1) - uint32(v2))) + case unsignedTypeI64: + ce.pushValue(v1 - v2) + case unsignedTypeF32: + ce.pushValue(subFloat32bits(uint32(v1), uint32(v2))) + case unsignedTypeF64: + v := math.Float64frombits(v1) - math.Float64frombits(v2) + ce.pushValue(math.Float64bits(v)) + } + frame.pc++ + case operationKindMul: + v2 := ce.popValue() + v1 := ce.popValue() + switch unsignedType(op.B1) { + case unsignedTypeI32: + ce.pushValue(uint64(uint32(v1) * uint32(v2))) + case unsignedTypeI64: + ce.pushValue(v1 * v2) + case unsignedTypeF32: + ce.pushValue(mulFloat32bits(uint32(v1), uint32(v2))) + case unsignedTypeF64: + v := math.Float64frombits(v2) * math.Float64frombits(v1) + ce.pushValue(math.Float64bits(v)) + } + frame.pc++ + case operationKindClz: + v := ce.popValue() + if op.B1 == 0 { + // unsignedInt32 + ce.pushValue(uint64(bits.LeadingZeros32(uint32(v)))) + } else { + // unsignedInt64 + ce.pushValue(uint64(bits.LeadingZeros64(v))) + } + frame.pc++ + case operationKindCtz: + v := ce.popValue() + if op.B1 == 0 { + // unsignedInt32 + ce.pushValue(uint64(bits.TrailingZeros32(uint32(v)))) + } else { + // unsignedInt64 + ce.pushValue(uint64(bits.TrailingZeros64(v))) + } + frame.pc++ + case operationKindPopcnt: + v := ce.popValue() + if op.B1 == 0 { + // unsignedInt32 + ce.pushValue(uint64(bits.OnesCount32(uint32(v)))) + } else { + // unsignedInt64 + ce.pushValue(uint64(bits.OnesCount64(v))) + } + frame.pc++ + case operationKindDiv: + // If an integer, check we won't divide by zero. + t := signedType(op.B1) + v2, v1 := ce.popValue(), ce.popValue() + switch t { + case signedTypeFloat32, signedTypeFloat64: // not integers + default: + if v2 == 0 { + panic(wasmruntime.ErrRuntimeIntegerDivideByZero) + } + } + + switch t { + case signedTypeInt32: + d := int32(v2) + n := int32(v1) + if n == math.MinInt32 && d == -1 { + panic(wasmruntime.ErrRuntimeIntegerOverflow) + } + ce.pushValue(uint64(uint32(n / d))) + case signedTypeInt64: + d := int64(v2) + n := int64(v1) + if n == math.MinInt64 && d == -1 { + panic(wasmruntime.ErrRuntimeIntegerOverflow) + } + ce.pushValue(uint64(n / d)) + case signedTypeUint32: + d := uint32(v2) + n := uint32(v1) + ce.pushValue(uint64(n / d)) + case signedTypeUint64: + d := v2 + n := v1 + ce.pushValue(n / d) + case signedTypeFloat32: + ce.pushValue(divFloat32bits(uint32(v1), uint32(v2))) + case signedTypeFloat64: + ce.pushValue(math.Float64bits(math.Float64frombits(v1) / math.Float64frombits(v2))) + } + frame.pc++ + case operationKindRem: + v2, v1 := ce.popValue(), ce.popValue() + if v2 == 0 { + panic(wasmruntime.ErrRuntimeIntegerDivideByZero) + } + switch signedInt(op.B1) { + case signedInt32: + d := int32(v2) + n := int32(v1) + ce.pushValue(uint64(uint32(n % d))) + case signedInt64: + d := int64(v2) + n := int64(v1) + ce.pushValue(uint64(n % d)) + case signedUint32: + d := uint32(v2) + n := uint32(v1) + ce.pushValue(uint64(n % d)) + case signedUint64: + d := v2 + n := v1 + ce.pushValue(n % d) + } + frame.pc++ + case operationKindAnd: + v2 := ce.popValue() + v1 := ce.popValue() + if op.B1 == 0 { + // unsignedInt32 + ce.pushValue(uint64(uint32(v2) & uint32(v1))) + } else { + // unsignedInt64 + ce.pushValue(uint64(v2 & v1)) + } + frame.pc++ + case operationKindOr: + v2 := ce.popValue() + v1 := ce.popValue() + if op.B1 == 0 { + // unsignedInt32 + ce.pushValue(uint64(uint32(v2) | uint32(v1))) + } else { + // unsignedInt64 + ce.pushValue(uint64(v2 | v1)) + } + frame.pc++ + case operationKindXor: + v2 := ce.popValue() + v1 := ce.popValue() + if op.B1 == 0 { + // unsignedInt32 + ce.pushValue(uint64(uint32(v2) ^ uint32(v1))) + } else { + // unsignedInt64 + ce.pushValue(uint64(v2 ^ v1)) + } + frame.pc++ + case operationKindShl: + v2 := ce.popValue() + v1 := ce.popValue() + if op.B1 == 0 { + // unsignedInt32 + ce.pushValue(uint64(uint32(v1) << (uint32(v2) % 32))) + } else { + // unsignedInt64 + ce.pushValue(v1 << (v2 % 64)) + } + frame.pc++ + case operationKindShr: + v2 := ce.popValue() + v1 := ce.popValue() + switch signedInt(op.B1) { + case signedInt32: + ce.pushValue(uint64(uint32(int32(v1) >> (uint32(v2) % 32)))) + case signedInt64: + ce.pushValue(uint64(int64(v1) >> (v2 % 64))) + case signedUint32: + ce.pushValue(uint64(uint32(v1) >> (uint32(v2) % 32))) + case signedUint64: + ce.pushValue(v1 >> (v2 % 64)) + } + frame.pc++ + case operationKindRotl: + v2 := ce.popValue() + v1 := ce.popValue() + if op.B1 == 0 { + // unsignedInt32 + ce.pushValue(uint64(bits.RotateLeft32(uint32(v1), int(v2)))) + } else { + // unsignedInt64 + ce.pushValue(uint64(bits.RotateLeft64(v1, int(v2)))) + } + frame.pc++ + case operationKindRotr: + v2 := ce.popValue() + v1 := ce.popValue() + if op.B1 == 0 { + // unsignedInt32 + ce.pushValue(uint64(bits.RotateLeft32(uint32(v1), -int(v2)))) + } else { + // unsignedInt64 + ce.pushValue(uint64(bits.RotateLeft64(v1, -int(v2)))) + } + frame.pc++ + case operationKindAbs: + if op.B1 == 0 { + // float32 + const mask uint32 = 1 << 31 + ce.pushValue(uint64(uint32(ce.popValue()) &^ mask)) + } else { + // float64 + const mask uint64 = 1 << 63 + ce.pushValue(ce.popValue() &^ mask) + } + frame.pc++ + case operationKindNeg: + if op.B1 == 0 { + // float32 + v := -math.Float32frombits(uint32(ce.popValue())) + ce.pushValue(uint64(math.Float32bits(v))) + } else { + // float64 + v := -math.Float64frombits(ce.popValue()) + ce.pushValue(math.Float64bits(v)) + } + frame.pc++ + case operationKindCeil: + if op.B1 == 0 { + // float32 + v := moremath.WasmCompatCeilF32(math.Float32frombits(uint32(ce.popValue()))) + ce.pushValue(uint64(math.Float32bits(v))) + } else { + // float64 + v := moremath.WasmCompatCeilF64(math.Float64frombits(ce.popValue())) + ce.pushValue(math.Float64bits(v)) + } + frame.pc++ + case operationKindFloor: + if op.B1 == 0 { + // float32 + v := moremath.WasmCompatFloorF32(math.Float32frombits(uint32(ce.popValue()))) + ce.pushValue(uint64(math.Float32bits(v))) + } else { + // float64 + v := moremath.WasmCompatFloorF64(math.Float64frombits(ce.popValue())) + ce.pushValue(math.Float64bits(v)) + } + frame.pc++ + case operationKindTrunc: + if op.B1 == 0 { + // float32 + v := moremath.WasmCompatTruncF32(math.Float32frombits(uint32(ce.popValue()))) + ce.pushValue(uint64(math.Float32bits(v))) + } else { + // float64 + v := moremath.WasmCompatTruncF64(math.Float64frombits(ce.popValue())) + ce.pushValue(math.Float64bits(v)) + } + frame.pc++ + case operationKindNearest: + if op.B1 == 0 { + // float32 + f := math.Float32frombits(uint32(ce.popValue())) + ce.pushValue(uint64(math.Float32bits(moremath.WasmCompatNearestF32(f)))) + } else { + // float64 + f := math.Float64frombits(ce.popValue()) + ce.pushValue(math.Float64bits(moremath.WasmCompatNearestF64(f))) + } + frame.pc++ + case operationKindSqrt: + if op.B1 == 0 { + // float32 + v := math.Sqrt(float64(math.Float32frombits(uint32(ce.popValue())))) + ce.pushValue(uint64(math.Float32bits(float32(v)))) + } else { + // float64 + v := math.Sqrt(math.Float64frombits(ce.popValue())) + ce.pushValue(math.Float64bits(v)) + } + frame.pc++ + case operationKindMin: + if op.B1 == 0 { + // float32 + ce.pushValue(wasmCompatMin32bits(uint32(ce.popValue()), uint32(ce.popValue()))) + } else { + v2 := math.Float64frombits(ce.popValue()) + v1 := math.Float64frombits(ce.popValue()) + ce.pushValue(math.Float64bits(moremath.WasmCompatMin64(v1, v2))) + } + frame.pc++ + case operationKindMax: + if op.B1 == 0 { + ce.pushValue(wasmCompatMax32bits(uint32(ce.popValue()), uint32(ce.popValue()))) + } else { + // float64 + v2 := math.Float64frombits(ce.popValue()) + v1 := math.Float64frombits(ce.popValue()) + ce.pushValue(math.Float64bits(moremath.WasmCompatMax64(v1, v2))) + } + frame.pc++ + case operationKindCopysign: + if op.B1 == 0 { + // float32 + v2 := uint32(ce.popValue()) + v1 := uint32(ce.popValue()) + const signbit = 1 << 31 + ce.pushValue(uint64(v1&^signbit | v2&signbit)) + } else { + // float64 + v2 := ce.popValue() + v1 := ce.popValue() + const signbit = 1 << 63 + ce.pushValue(v1&^signbit | v2&signbit) + } + frame.pc++ + case operationKindI32WrapFromI64: + ce.pushValue(uint64(uint32(ce.popValue()))) + frame.pc++ + case operationKindITruncFromF: + if op.B1 == 0 { + // float32 + switch signedInt(op.B2) { + case signedInt32: + v := math.Trunc(float64(math.Float32frombits(uint32(ce.popValue())))) + if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN + if op.B3 { + // non-trapping conversion must cast nan to zero. + v = 0 + } else { + panic(wasmruntime.ErrRuntimeInvalidConversionToInteger) + } + } else if v < math.MinInt32 || v > math.MaxInt32 { + if op.B3 { + // non-trapping conversion must "saturate" the value for overflowing sources. + if v < 0 { + v = math.MinInt32 + } else { + v = math.MaxInt32 + } + } else { + panic(wasmruntime.ErrRuntimeIntegerOverflow) + } + } + ce.pushValue(uint64(uint32(int32(v)))) + case signedInt64: + v := math.Trunc(float64(math.Float32frombits(uint32(ce.popValue())))) + res := int64(v) + if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN + if op.B3 { + // non-trapping conversion must cast nan to zero. + res = 0 + } else { + panic(wasmruntime.ErrRuntimeInvalidConversionToInteger) + } + } else if v < math.MinInt64 || v >= math.MaxInt64 { + // Note: math.MaxInt64 is rounded up to math.MaxInt64+1 in 64-bit float representation, + // and that's why we use '>=' not '>' to check overflow. + if op.B3 { + // non-trapping conversion must "saturate" the value for overflowing sources. + if v < 0 { + res = math.MinInt64 + } else { + res = math.MaxInt64 + } + } else { + panic(wasmruntime.ErrRuntimeIntegerOverflow) + } + } + ce.pushValue(uint64(res)) + case signedUint32: + v := math.Trunc(float64(math.Float32frombits(uint32(ce.popValue())))) + if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN + if op.B3 { + // non-trapping conversion must cast nan to zero. + v = 0 + } else { + panic(wasmruntime.ErrRuntimeInvalidConversionToInteger) + } + } else if v < 0 || v > math.MaxUint32 { + if op.B3 { + // non-trapping conversion must "saturate" the value for overflowing source. + if v < 0 { + v = 0 + } else { + v = math.MaxUint32 + } + } else { + panic(wasmruntime.ErrRuntimeIntegerOverflow) + } + } + ce.pushValue(uint64(uint32(v))) + case signedUint64: + v := math.Trunc(float64(math.Float32frombits(uint32(ce.popValue())))) + res := uint64(v) + if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN + if op.B3 { + // non-trapping conversion must cast nan to zero. + res = 0 + } else { + panic(wasmruntime.ErrRuntimeInvalidConversionToInteger) + } + } else if v < 0 || v >= math.MaxUint64 { + // Note: math.MaxUint64 is rounded up to math.MaxUint64+1 in 64-bit float representation, + // and that's why we use '>=' not '>' to check overflow. + if op.B3 { + // non-trapping conversion must "saturate" the value for overflowing source. + if v < 0 { + res = 0 + } else { + res = math.MaxUint64 + } + } else { + panic(wasmruntime.ErrRuntimeIntegerOverflow) + } + } + ce.pushValue(res) + } + } else { + // float64 + switch signedInt(op.B2) { + case signedInt32: + v := math.Trunc(math.Float64frombits(ce.popValue())) + if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN + if op.B3 { + // non-trapping conversion must cast nan to zero. + v = 0 + } else { + panic(wasmruntime.ErrRuntimeInvalidConversionToInteger) + } + } else if v < math.MinInt32 || v > math.MaxInt32 { + if op.B3 { + // non-trapping conversion must "saturate" the value for overflowing source. + if v < 0 { + v = math.MinInt32 + } else { + v = math.MaxInt32 + } + } else { + panic(wasmruntime.ErrRuntimeIntegerOverflow) + } + } + ce.pushValue(uint64(uint32(int32(v)))) + case signedInt64: + v := math.Trunc(math.Float64frombits(ce.popValue())) + res := int64(v) + if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN + if op.B3 { + // non-trapping conversion must cast nan to zero. + res = 0 + } else { + panic(wasmruntime.ErrRuntimeInvalidConversionToInteger) + } + } else if v < math.MinInt64 || v >= math.MaxInt64 { + // Note: math.MaxInt64 is rounded up to math.MaxInt64+1 in 64-bit float representation, + // and that's why we use '>=' not '>' to check overflow. + if op.B3 { + // non-trapping conversion must "saturate" the value for overflowing source. + if v < 0 { + res = math.MinInt64 + } else { + res = math.MaxInt64 + } + } else { + panic(wasmruntime.ErrRuntimeIntegerOverflow) + } + } + ce.pushValue(uint64(res)) + case signedUint32: + v := math.Trunc(math.Float64frombits(ce.popValue())) + if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN + if op.B3 { + // non-trapping conversion must cast nan to zero. + v = 0 + } else { + panic(wasmruntime.ErrRuntimeInvalidConversionToInteger) + } + } else if v < 0 || v > math.MaxUint32 { + if op.B3 { + // non-trapping conversion must "saturate" the value for overflowing source. + if v < 0 { + v = 0 + } else { + v = math.MaxUint32 + } + } else { + panic(wasmruntime.ErrRuntimeIntegerOverflow) + } + } + ce.pushValue(uint64(uint32(v))) + case signedUint64: + v := math.Trunc(math.Float64frombits(ce.popValue())) + res := uint64(v) + if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN + if op.B3 { + // non-trapping conversion must cast nan to zero. + res = 0 + } else { + panic(wasmruntime.ErrRuntimeInvalidConversionToInteger) + } + } else if v < 0 || v >= math.MaxUint64 { + // Note: math.MaxUint64 is rounded up to math.MaxUint64+1 in 64-bit float representation, + // and that's why we use '>=' not '>' to check overflow. + if op.B3 { + // non-trapping conversion must "saturate" the value for overflowing source. + if v < 0 { + res = 0 + } else { + res = math.MaxUint64 + } + } else { + panic(wasmruntime.ErrRuntimeIntegerOverflow) + } + } + ce.pushValue(res) + } + } + frame.pc++ + case operationKindFConvertFromI: + switch signedInt(op.B1) { + case signedInt32: + if op.B2 == 0 { + // float32 + v := float32(int32(ce.popValue())) + ce.pushValue(uint64(math.Float32bits(v))) + } else { + // float64 + v := float64(int32(ce.popValue())) + ce.pushValue(math.Float64bits(v)) + } + case signedInt64: + if op.B2 == 0 { + // float32 + v := float32(int64(ce.popValue())) + ce.pushValue(uint64(math.Float32bits(v))) + } else { + // float64 + v := float64(int64(ce.popValue())) + ce.pushValue(math.Float64bits(v)) + } + case signedUint32: + if op.B2 == 0 { + // float32 + v := float32(uint32(ce.popValue())) + ce.pushValue(uint64(math.Float32bits(v))) + } else { + // float64 + v := float64(uint32(ce.popValue())) + ce.pushValue(math.Float64bits(v)) + } + case signedUint64: + if op.B2 == 0 { + // float32 + v := float32(ce.popValue()) + ce.pushValue(uint64(math.Float32bits(v))) + } else { + // float64 + v := float64(ce.popValue()) + ce.pushValue(math.Float64bits(v)) + } + } + frame.pc++ + case operationKindF32DemoteFromF64: + v := float32(math.Float64frombits(ce.popValue())) + ce.pushValue(uint64(math.Float32bits(v))) + frame.pc++ + case operationKindF64PromoteFromF32: + v := float64(math.Float32frombits(uint32(ce.popValue()))) + ce.pushValue(math.Float64bits(v)) + frame.pc++ + case operationKindExtend: + if op.B1 == 1 { + // Signed. + v := int64(int32(ce.popValue())) + ce.pushValue(uint64(v)) + } else { + v := uint64(uint32(ce.popValue())) + ce.pushValue(v) + } + frame.pc++ + case operationKindSignExtend32From8: + v := uint32(int8(ce.popValue())) + ce.pushValue(uint64(v)) + frame.pc++ + case operationKindSignExtend32From16: + v := uint32(int16(ce.popValue())) + ce.pushValue(uint64(v)) + frame.pc++ + case operationKindSignExtend64From8: + v := int64(int8(ce.popValue())) + ce.pushValue(uint64(v)) + frame.pc++ + case operationKindSignExtend64From16: + v := int64(int16(ce.popValue())) + ce.pushValue(uint64(v)) + frame.pc++ + case operationKindSignExtend64From32: + v := int64(int32(ce.popValue())) + ce.pushValue(uint64(v)) + frame.pc++ + case operationKindMemoryInit: + dataInstance := dataInstances[op.U1] + copySize := ce.popValue() + inDataOffset := ce.popValue() + inMemoryOffset := ce.popValue() + if inDataOffset+copySize > uint64(len(dataInstance)) || + inMemoryOffset+copySize > uint64(len(memoryInst.Buffer)) { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } else if copySize != 0 { + copy(memoryInst.Buffer[inMemoryOffset:inMemoryOffset+copySize], dataInstance[inDataOffset:]) + } + frame.pc++ + case operationKindDataDrop: + dataInstances[op.U1] = nil + frame.pc++ + case operationKindMemoryCopy: + memLen := uint64(len(memoryInst.Buffer)) + copySize := ce.popValue() + sourceOffset := ce.popValue() + destinationOffset := ce.popValue() + if sourceOffset+copySize > memLen || destinationOffset+copySize > memLen { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } else if copySize != 0 { + copy(memoryInst.Buffer[destinationOffset:], + memoryInst.Buffer[sourceOffset:sourceOffset+copySize]) + } + frame.pc++ + case operationKindMemoryFill: + fillSize := ce.popValue() + value := byte(ce.popValue()) + offset := ce.popValue() + if fillSize+offset > uint64(len(memoryInst.Buffer)) { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } else if fillSize != 0 { + // Uses the copy trick for faster filling the buffer with the value. + // https://github.com/golang/go/blob/go1.24.0/src/bytes/bytes.go#L664-L673 + buf := memoryInst.Buffer[offset : offset+fillSize] + if value == 0 { + clear(buf) + } else { + buf[0] = value + for i := 1; i < len(buf); { + chunk := min(i, 8192) + i += copy(buf[i:], buf[:chunk]) + } + } + } + frame.pc++ + case operationKindTableInit: + elementInstance := elementInstances[op.U1] + copySize := ce.popValue() + inElementOffset := ce.popValue() + inTableOffset := ce.popValue() + table := tables[op.U2] + if inElementOffset+copySize > uint64(len(elementInstance)) || + inTableOffset+copySize > uint64(len(table.References)) { + panic(wasmruntime.ErrRuntimeInvalidTableAccess) + } else if copySize != 0 { + copy(table.References[inTableOffset:inTableOffset+copySize], elementInstance[inElementOffset:]) + } + frame.pc++ + case operationKindElemDrop: + elementInstances[op.U1] = nil + frame.pc++ + case operationKindTableCopy: + srcTable, dstTable := tables[op.U1].References, tables[op.U2].References + copySize := ce.popValue() + sourceOffset := ce.popValue() + destinationOffset := ce.popValue() + if sourceOffset+copySize > uint64(len(srcTable)) || destinationOffset+copySize > uint64(len(dstTable)) { + panic(wasmruntime.ErrRuntimeInvalidTableAccess) + } else if copySize != 0 { + copy(dstTable[destinationOffset:], srcTable[sourceOffset:sourceOffset+copySize]) + } + frame.pc++ + case operationKindRefFunc: + ce.pushValue(uint64(uintptr(unsafe.Pointer(&functions[op.U1])))) + frame.pc++ + case operationKindTableGet: + table := tables[op.U1] + + offset := ce.popValue() + if offset >= uint64(len(table.References)) { + panic(wasmruntime.ErrRuntimeInvalidTableAccess) + } + + ce.pushValue(uint64(table.References[offset])) + frame.pc++ + case operationKindTableSet: + table := tables[op.U1] + ref := ce.popValue() + + offset := ce.popValue() + if offset >= uint64(len(table.References)) { + panic(wasmruntime.ErrRuntimeInvalidTableAccess) + } + + table.References[offset] = uintptr(ref) // externrefs are opaque uint64. + frame.pc++ + case operationKindTableSize: + table := tables[op.U1] + ce.pushValue(uint64(len(table.References))) + frame.pc++ + case operationKindTableGrow: + table := tables[op.U1] + num, ref := ce.popValue(), ce.popValue() + ret := table.Grow(uint32(num), uintptr(ref)) + ce.pushValue(uint64(ret)) + frame.pc++ + case operationKindTableFill: + table := tables[op.U1] + num := ce.popValue() + ref := uintptr(ce.popValue()) + offset := ce.popValue() + if num+offset > uint64(len(table.References)) { + panic(wasmruntime.ErrRuntimeInvalidTableAccess) + } else if num > 0 { + // Uses the copy trick for faster filling the region with the value. + // https://github.com/golang/go/blob/go1.24.0/src/slices/slices.go#L514-L517 + targetRegion := table.References[offset : offset+num] + targetRegion[0] = ref + for i := 1; i < len(targetRegion); i *= 2 { + copy(targetRegion[i:], targetRegion[:i]) + } + } + frame.pc++ + case operationKindV128Const: + lo, hi := op.U1, op.U2 + ce.pushValue(lo) + ce.pushValue(hi) + frame.pc++ + case operationKindV128Add: + yHigh, yLow := ce.popValue(), ce.popValue() + xHigh, xLow := ce.popValue(), ce.popValue() + switch op.B1 { + case shapeI8x16: + ce.pushValue( + uint64(uint8(xLow>>8)+uint8(yLow>>8))<<8 | uint64(uint8(xLow)+uint8(yLow)) | + uint64(uint8(xLow>>24)+uint8(yLow>>24))<<24 | uint64(uint8(xLow>>16)+uint8(yLow>>16))<<16 | + uint64(uint8(xLow>>40)+uint8(yLow>>40))<<40 | uint64(uint8(xLow>>32)+uint8(yLow>>32))<<32 | + uint64(uint8(xLow>>56)+uint8(yLow>>56))<<56 | uint64(uint8(xLow>>48)+uint8(yLow>>48))<<48, + ) + ce.pushValue( + uint64(uint8(xHigh>>8)+uint8(yHigh>>8))<<8 | uint64(uint8(xHigh)+uint8(yHigh)) | + uint64(uint8(xHigh>>24)+uint8(yHigh>>24))<<24 | uint64(uint8(xHigh>>16)+uint8(yHigh>>16))<<16 | + uint64(uint8(xHigh>>40)+uint8(yHigh>>40))<<40 | uint64(uint8(xHigh>>32)+uint8(yHigh>>32))<<32 | + uint64(uint8(xHigh>>56)+uint8(yHigh>>56))<<56 | uint64(uint8(xHigh>>48)+uint8(yHigh>>48))<<48, + ) + case shapeI16x8: + ce.pushValue( + uint64(uint16(xLow>>16+yLow>>16))<<16 | uint64(uint16(xLow)+uint16(yLow)) | + uint64(uint16(xLow>>48+yLow>>48))<<48 | uint64(uint16(xLow>>32+yLow>>32))<<32, + ) + ce.pushValue( + uint64(uint16(xHigh>>16)+uint16(yHigh>>16))<<16 | uint64(uint16(xHigh)+uint16(yHigh)) | + uint64(uint16(xHigh>>48)+uint16(yHigh>>48))<<48 | uint64(uint16(xHigh>>32)+uint16(yHigh>>32))<<32, + ) + case shapeI32x4: + ce.pushValue(uint64(uint32(xLow>>32)+uint32(yLow>>32))<<32 | uint64(uint32(xLow)+uint32(yLow))) + ce.pushValue(uint64(uint32(xHigh>>32)+uint32(yHigh>>32))<<32 | uint64(uint32(xHigh)+uint32(yHigh))) + case shapeI64x2: + ce.pushValue(xLow + yLow) + ce.pushValue(xHigh + yHigh) + case shapeF32x4: + ce.pushValue( + addFloat32bits(uint32(xLow), uint32(yLow)) | addFloat32bits(uint32(xLow>>32), uint32(yLow>>32))<<32, + ) + ce.pushValue( + addFloat32bits(uint32(xHigh), uint32(yHigh)) | addFloat32bits(uint32(xHigh>>32), uint32(yHigh>>32))<<32, + ) + case shapeF64x2: + ce.pushValue(math.Float64bits(math.Float64frombits(xLow) + math.Float64frombits(yLow))) + ce.pushValue(math.Float64bits(math.Float64frombits(xHigh) + math.Float64frombits(yHigh))) + } + frame.pc++ + case operationKindV128Sub: + yHigh, yLow := ce.popValue(), ce.popValue() + xHigh, xLow := ce.popValue(), ce.popValue() + switch op.B1 { + case shapeI8x16: + ce.pushValue( + uint64(uint8(xLow>>8)-uint8(yLow>>8))<<8 | uint64(uint8(xLow)-uint8(yLow)) | + uint64(uint8(xLow>>24)-uint8(yLow>>24))<<24 | uint64(uint8(xLow>>16)-uint8(yLow>>16))<<16 | + uint64(uint8(xLow>>40)-uint8(yLow>>40))<<40 | uint64(uint8(xLow>>32)-uint8(yLow>>32))<<32 | + uint64(uint8(xLow>>56)-uint8(yLow>>56))<<56 | uint64(uint8(xLow>>48)-uint8(yLow>>48))<<48, + ) + ce.pushValue( + uint64(uint8(xHigh>>8)-uint8(yHigh>>8))<<8 | uint64(uint8(xHigh)-uint8(yHigh)) | + uint64(uint8(xHigh>>24)-uint8(yHigh>>24))<<24 | uint64(uint8(xHigh>>16)-uint8(yHigh>>16))<<16 | + uint64(uint8(xHigh>>40)-uint8(yHigh>>40))<<40 | uint64(uint8(xHigh>>32)-uint8(yHigh>>32))<<32 | + uint64(uint8(xHigh>>56)-uint8(yHigh>>56))<<56 | uint64(uint8(xHigh>>48)-uint8(yHigh>>48))<<48, + ) + case shapeI16x8: + ce.pushValue( + uint64(uint16(xLow>>16)-uint16(yLow>>16))<<16 | uint64(uint16(xLow)-uint16(yLow)) | + uint64(uint16(xLow>>48)-uint16(yLow>>48))<<48 | uint64(uint16(xLow>>32)-uint16(yLow>>32))<<32, + ) + ce.pushValue( + uint64(uint16(xHigh>>16)-uint16(yHigh>>16))<<16 | uint64(uint16(xHigh)-uint16(yHigh)) | + uint64(uint16(xHigh>>48)-uint16(yHigh>>48))<<48 | uint64(uint16(xHigh>>32)-uint16(yHigh>>32))<<32, + ) + case shapeI32x4: + ce.pushValue(uint64(uint32(xLow>>32-yLow>>32))<<32 | uint64(uint32(xLow)-uint32(yLow))) + ce.pushValue(uint64(uint32(xHigh>>32-yHigh>>32))<<32 | uint64(uint32(xHigh)-uint32(yHigh))) + case shapeI64x2: + ce.pushValue(xLow - yLow) + ce.pushValue(xHigh - yHigh) + case shapeF32x4: + ce.pushValue( + subFloat32bits(uint32(xLow), uint32(yLow)) | subFloat32bits(uint32(xLow>>32), uint32(yLow>>32))<<32, + ) + ce.pushValue( + subFloat32bits(uint32(xHigh), uint32(yHigh)) | subFloat32bits(uint32(xHigh>>32), uint32(yHigh>>32))<<32, + ) + case shapeF64x2: + ce.pushValue(math.Float64bits(math.Float64frombits(xLow) - math.Float64frombits(yLow))) + ce.pushValue(math.Float64bits(math.Float64frombits(xHigh) - math.Float64frombits(yHigh))) + } + frame.pc++ + case operationKindV128Load: + offset := ce.popMemoryOffset(op) + switch op.B1 { + case v128LoadType128: + lo, ok := memoryInst.ReadUint64Le(offset) + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + ce.pushValue(lo) + hi, ok := memoryInst.ReadUint64Le(offset + 8) + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + ce.pushValue(hi) + case v128LoadType8x8s: + data, ok := memoryInst.Read(offset, 8) + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + ce.pushValue( + uint64(uint16(int8(data[3])))<<48 | uint64(uint16(int8(data[2])))<<32 | uint64(uint16(int8(data[1])))<<16 | uint64(uint16(int8(data[0]))), + ) + ce.pushValue( + uint64(uint16(int8(data[7])))<<48 | uint64(uint16(int8(data[6])))<<32 | uint64(uint16(int8(data[5])))<<16 | uint64(uint16(int8(data[4]))), + ) + case v128LoadType8x8u: + data, ok := memoryInst.Read(offset, 8) + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + ce.pushValue( + uint64(data[3])<<48 | uint64(data[2])<<32 | uint64(data[1])<<16 | uint64(data[0]), + ) + ce.pushValue( + uint64(data[7])<<48 | uint64(data[6])<<32 | uint64(data[5])<<16 | uint64(data[4]), + ) + case v128LoadType16x4s: + data, ok := memoryInst.Read(offset, 8) + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + ce.pushValue( + uint64(int16(binary.LittleEndian.Uint16(data[2:])))<<32 | + uint64(uint32(int16(binary.LittleEndian.Uint16(data)))), + ) + ce.pushValue( + uint64(uint32(int16(binary.LittleEndian.Uint16(data[6:]))))<<32 | + uint64(uint32(int16(binary.LittleEndian.Uint16(data[4:])))), + ) + case v128LoadType16x4u: + data, ok := memoryInst.Read(offset, 8) + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + ce.pushValue( + uint64(binary.LittleEndian.Uint16(data[2:]))<<32 | uint64(binary.LittleEndian.Uint16(data)), + ) + ce.pushValue( + uint64(binary.LittleEndian.Uint16(data[6:]))<<32 | uint64(binary.LittleEndian.Uint16(data[4:])), + ) + case v128LoadType32x2s: + data, ok := memoryInst.Read(offset, 8) + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + ce.pushValue(uint64(int32(binary.LittleEndian.Uint32(data)))) + ce.pushValue(uint64(int32(binary.LittleEndian.Uint32(data[4:])))) + case v128LoadType32x2u: + data, ok := memoryInst.Read(offset, 8) + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + ce.pushValue(uint64(binary.LittleEndian.Uint32(data))) + ce.pushValue(uint64(binary.LittleEndian.Uint32(data[4:]))) + case v128LoadType8Splat: + v, ok := memoryInst.ReadByte(offset) + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + v8 := uint64(v)<<56 | uint64(v)<<48 | uint64(v)<<40 | uint64(v)<<32 | + uint64(v)<<24 | uint64(v)<<16 | uint64(v)<<8 | uint64(v) + ce.pushValue(v8) + ce.pushValue(v8) + case v128LoadType16Splat: + v, ok := memoryInst.ReadUint16Le(offset) + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + v4 := uint64(v)<<48 | uint64(v)<<32 | uint64(v)<<16 | uint64(v) + ce.pushValue(v4) + ce.pushValue(v4) + case v128LoadType32Splat: + v, ok := memoryInst.ReadUint32Le(offset) + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + vv := uint64(v)<<32 | uint64(v) + ce.pushValue(vv) + ce.pushValue(vv) + case v128LoadType64Splat: + lo, ok := memoryInst.ReadUint64Le(offset) + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + ce.pushValue(lo) + ce.pushValue(lo) + case v128LoadType32zero: + lo, ok := memoryInst.ReadUint32Le(offset) + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + ce.pushValue(uint64(lo)) + ce.pushValue(0) + case v128LoadType64zero: + lo, ok := memoryInst.ReadUint64Le(offset) + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + ce.pushValue(lo) + ce.pushValue(0) + } + frame.pc++ + case operationKindV128LoadLane: + hi, lo := ce.popValue(), ce.popValue() + offset := ce.popMemoryOffset(op) + switch op.B1 { + case 8: + b, ok := memoryInst.ReadByte(offset) + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + if op.B2 < 8 { + s := op.B2 << 3 + lo = (lo & ^(0xff << s)) | uint64(b)< math.MaxUint32 { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + if ok := memoryInst.WriteUint64Le(offset+8, hi); !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + if ok := memoryInst.WriteUint64Le(offset, lo); !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + frame.pc++ + case operationKindV128StoreLane: + hi, lo := ce.popValue(), ce.popValue() + offset := ce.popMemoryOffset(op) + var ok bool + switch op.B1 { + case 8: + if op.B2 < 8 { + ok = memoryInst.WriteByte(offset, byte(lo>>(op.B2*8))) + } else { + ok = memoryInst.WriteByte(offset, byte(hi>>((op.B2-8)*8))) + } + case 16: + if op.B2 < 4 { + ok = memoryInst.WriteUint16Le(offset, uint16(lo>>(op.B2*16))) + } else { + ok = memoryInst.WriteUint16Le(offset, uint16(hi>>((op.B2-4)*16))) + } + case 32: + if op.B2 < 2 { + ok = memoryInst.WriteUint32Le(offset, uint32(lo>>(op.B2*32))) + } else { + ok = memoryInst.WriteUint32Le(offset, uint32(hi>>((op.B2-2)*32))) + } + case 64: + if op.B2 == 0 { + ok = memoryInst.WriteUint64Le(offset, lo) + } else { + ok = memoryInst.WriteUint64Le(offset, hi) + } + } + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + frame.pc++ + case operationKindV128ReplaceLane: + v := ce.popValue() + hi, lo := ce.popValue(), ce.popValue() + switch op.B1 { + case shapeI8x16: + if op.B2 < 8 { + s := op.B2 << 3 + lo = (lo & ^(0xff << s)) | uint64(byte(v))<> (op.B2 * 8)) + } else { + u8 = byte(hi >> ((op.B2 - 8) * 8)) + } + if op.B3 { + // sign-extend. + v = uint64(uint32(int8(u8))) + } else { + v = uint64(u8) + } + case shapeI16x8: + var u16 uint16 + if op.B2 < 4 { + u16 = uint16(lo >> (op.B2 * 16)) + } else { + u16 = uint16(hi >> ((op.B2 - 4) * 16)) + } + if op.B3 { + // sign-extend. + v = uint64(uint32(int16(u16))) + } else { + v = uint64(u16) + } + case shapeI32x4, shapeF32x4: + if op.B2 < 2 { + v = uint64(uint32(lo >> (op.B2 * 32))) + } else { + v = uint64(uint32(hi >> ((op.B2 - 2) * 32))) + } + case shapeI64x2, shapeF64x2: + if op.B2 == 0 { + v = lo + } else { + v = hi + } + } + ce.pushValue(v) + frame.pc++ + case operationKindV128Splat: + v := ce.popValue() + var hi, lo uint64 + switch op.B1 { + case shapeI8x16: + v8 := uint64(byte(v))<<56 | uint64(byte(v))<<48 | uint64(byte(v))<<40 | uint64(byte(v))<<32 | + uint64(byte(v))<<24 | uint64(byte(v))<<16 | uint64(byte(v))<<8 | uint64(byte(v)) + hi, lo = v8, v8 + case shapeI16x8: + v4 := uint64(uint16(v))<<48 | uint64(uint16(v))<<32 | uint64(uint16(v))<<16 | uint64(uint16(v)) + hi, lo = v4, v4 + case shapeI32x4, shapeF32x4: + v2 := uint64(uint32(v))<<32 | uint64(uint32(v)) + lo, hi = v2, v2 + case shapeI64x2, shapeF64x2: + lo, hi = v, v + } + ce.pushValue(lo) + ce.pushValue(hi) + frame.pc++ + case operationKindV128Swizzle: + idxHi, idxLo := ce.popValue(), ce.popValue() + baseHi, baseLo := ce.popValue(), ce.popValue() + var newVal [16]byte + for i := 0; i < 16; i++ { + var id byte + if i < 8 { + id = byte(idxLo >> (i * 8)) + } else { + id = byte(idxHi >> ((i - 8) * 8)) + } + if id < 8 { + newVal[i] = byte(baseLo >> (id * 8)) + } else if id < 16 { + newVal[i] = byte(baseHi >> ((id - 8) * 8)) + } + } + ce.pushValue(binary.LittleEndian.Uint64(newVal[:8])) + ce.pushValue(binary.LittleEndian.Uint64(newVal[8:])) + frame.pc++ + case operationKindV128Shuffle: + xHi, xLo, yHi, yLo := ce.popValue(), ce.popValue(), ce.popValue(), ce.popValue() + var newVal [16]byte + for i, l := range op.Us { + if l < 8 { + newVal[i] = byte(yLo >> (l * 8)) + } else if l < 16 { + newVal[i] = byte(yHi >> ((l - 8) * 8)) + } else if l < 24 { + newVal[i] = byte(xLo >> ((l - 16) * 8)) + } else if l < 32 { + newVal[i] = byte(xHi >> ((l - 24) * 8)) + } + } + ce.pushValue(binary.LittleEndian.Uint64(newVal[:8])) + ce.pushValue(binary.LittleEndian.Uint64(newVal[8:])) + frame.pc++ + case operationKindV128AnyTrue: + hi, lo := ce.popValue(), ce.popValue() + if hi != 0 || lo != 0 { + ce.pushValue(1) + } else { + ce.pushValue(0) + } + frame.pc++ + case operationKindV128AllTrue: + hi, lo := ce.popValue(), ce.popValue() + var ret bool + switch op.B1 { + case shapeI8x16: + ret = (uint8(lo) != 0) && (uint8(lo>>8) != 0) && (uint8(lo>>16) != 0) && (uint8(lo>>24) != 0) && + (uint8(lo>>32) != 0) && (uint8(lo>>40) != 0) && (uint8(lo>>48) != 0) && (uint8(lo>>56) != 0) && + (uint8(hi) != 0) && (uint8(hi>>8) != 0) && (uint8(hi>>16) != 0) && (uint8(hi>>24) != 0) && + (uint8(hi>>32) != 0) && (uint8(hi>>40) != 0) && (uint8(hi>>48) != 0) && (uint8(hi>>56) != 0) + case shapeI16x8: + ret = (uint16(lo) != 0) && (uint16(lo>>16) != 0) && (uint16(lo>>32) != 0) && (uint16(lo>>48) != 0) && + (uint16(hi) != 0) && (uint16(hi>>16) != 0) && (uint16(hi>>32) != 0) && (uint16(hi>>48) != 0) + case shapeI32x4: + ret = (uint32(lo) != 0) && (uint32(lo>>32) != 0) && + (uint32(hi) != 0) && (uint32(hi>>32) != 0) + case shapeI64x2: + ret = (lo != 0) && + (hi != 0) + } + if ret { + ce.pushValue(1) + } else { + ce.pushValue(0) + } + frame.pc++ + case operationKindV128BitMask: + // https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md#bitmask-extraction + hi, lo := ce.popValue(), ce.popValue() + var res uint64 + switch op.B1 { + case shapeI8x16: + for i := 0; i < 8; i++ { + if int8(lo>>(i*8)) < 0 { + res |= 1 << i + } + } + for i := 0; i < 8; i++ { + if int8(hi>>(i*8)) < 0 { + res |= 1 << (i + 8) + } + } + case shapeI16x8: + for i := 0; i < 4; i++ { + if int16(lo>>(i*16)) < 0 { + res |= 1 << i + } + } + for i := 0; i < 4; i++ { + if int16(hi>>(i*16)) < 0 { + res |= 1 << (i + 4) + } + } + case shapeI32x4: + for i := 0; i < 2; i++ { + if int32(lo>>(i*32)) < 0 { + res |= 1 << i + } + } + for i := 0; i < 2; i++ { + if int32(hi>>(i*32)) < 0 { + res |= 1 << (i + 2) + } + } + case shapeI64x2: + if int64(lo) < 0 { + res |= 0b01 + } + if int(hi) < 0 { + res |= 0b10 + } + } + ce.pushValue(res) + frame.pc++ + case operationKindV128And: + x2Hi, x2Lo := ce.popValue(), ce.popValue() + x1Hi, x1Lo := ce.popValue(), ce.popValue() + ce.pushValue(x1Lo & x2Lo) + ce.pushValue(x1Hi & x2Hi) + frame.pc++ + case operationKindV128Not: + hi, lo := ce.popValue(), ce.popValue() + ce.pushValue(^lo) + ce.pushValue(^hi) + frame.pc++ + case operationKindV128Or: + x2Hi, x2Lo := ce.popValue(), ce.popValue() + x1Hi, x1Lo := ce.popValue(), ce.popValue() + ce.pushValue(x1Lo | x2Lo) + ce.pushValue(x1Hi | x2Hi) + frame.pc++ + case operationKindV128Xor: + x2Hi, x2Lo := ce.popValue(), ce.popValue() + x1Hi, x1Lo := ce.popValue(), ce.popValue() + ce.pushValue(x1Lo ^ x2Lo) + ce.pushValue(x1Hi ^ x2Hi) + frame.pc++ + case operationKindV128Bitselect: + // https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md#bitwise-select + cHi, cLo := ce.popValue(), ce.popValue() + x2Hi, x2Lo := ce.popValue(), ce.popValue() + x1Hi, x1Lo := ce.popValue(), ce.popValue() + // v128.or(v128.and(v1, c), v128.and(v2, v128.not(c))) + ce.pushValue((x1Lo & cLo) | (x2Lo & (^cLo))) + ce.pushValue((x1Hi & cHi) | (x2Hi & (^cHi))) + frame.pc++ + case operationKindV128AndNot: + x2Hi, x2Lo := ce.popValue(), ce.popValue() + x1Hi, x1Lo := ce.popValue(), ce.popValue() + ce.pushValue(x1Lo & (^x2Lo)) + ce.pushValue(x1Hi & (^x2Hi)) + frame.pc++ + case operationKindV128Shl: + s := ce.popValue() + hi, lo := ce.popValue(), ce.popValue() + switch op.B1 { + case shapeI8x16: + s = s % 8 + lo = uint64(uint8(lo<>8)<>16)<>24)<>32)<>40)<>48)<>56)<>8)<>16)<>24)<>32)<>40)<>48)<>56)<>16)<>32)<>48)<>16)<>32)<>48)<>32)<>32)<>s)) | + uint64(uint8(int8(lo>>8)>>s))<<8 | + uint64(uint8(int8(lo>>16)>>s))<<16 | + uint64(uint8(int8(lo>>24)>>s))<<24 | + uint64(uint8(int8(lo>>32)>>s))<<32 | + uint64(uint8(int8(lo>>40)>>s))<<40 | + uint64(uint8(int8(lo>>48)>>s))<<48 | + uint64(uint8(int8(lo>>56)>>s))<<56 + hi = uint64(uint8(int8(hi)>>s)) | + uint64(uint8(int8(hi>>8)>>s))<<8 | + uint64(uint8(int8(hi>>16)>>s))<<16 | + uint64(uint8(int8(hi>>24)>>s))<<24 | + uint64(uint8(int8(hi>>32)>>s))<<32 | + uint64(uint8(int8(hi>>40)>>s))<<40 | + uint64(uint8(int8(hi>>48)>>s))<<48 | + uint64(uint8(int8(hi>>56)>>s))<<56 + } else { + lo = uint64(uint8(lo)>>s) | + uint64(uint8(lo>>8)>>s)<<8 | + uint64(uint8(lo>>16)>>s)<<16 | + uint64(uint8(lo>>24)>>s)<<24 | + uint64(uint8(lo>>32)>>s)<<32 | + uint64(uint8(lo>>40)>>s)<<40 | + uint64(uint8(lo>>48)>>s)<<48 | + uint64(uint8(lo>>56)>>s)<<56 + hi = uint64(uint8(hi)>>s) | + uint64(uint8(hi>>8)>>s)<<8 | + uint64(uint8(hi>>16)>>s)<<16 | + uint64(uint8(hi>>24)>>s)<<24 | + uint64(uint8(hi>>32)>>s)<<32 | + uint64(uint8(hi>>40)>>s)<<40 | + uint64(uint8(hi>>48)>>s)<<48 | + uint64(uint8(hi>>56)>>s)<<56 + } + case shapeI16x8: + s = s % 16 + if op.B3 { // signed + lo = uint64(uint16(int16(lo)>>s)) | + uint64(uint16(int16(lo>>16)>>s))<<16 | + uint64(uint16(int16(lo>>32)>>s))<<32 | + uint64(uint16(int16(lo>>48)>>s))<<48 + hi = uint64(uint16(int16(hi)>>s)) | + uint64(uint16(int16(hi>>16)>>s))<<16 | + uint64(uint16(int16(hi>>32)>>s))<<32 | + uint64(uint16(int16(hi>>48)>>s))<<48 + } else { + lo = uint64(uint16(lo)>>s) | + uint64(uint16(lo>>16)>>s)<<16 | + uint64(uint16(lo>>32)>>s)<<32 | + uint64(uint16(lo>>48)>>s)<<48 + hi = uint64(uint16(hi)>>s) | + uint64(uint16(hi>>16)>>s)<<16 | + uint64(uint16(hi>>32)>>s)<<32 | + uint64(uint16(hi>>48)>>s)<<48 + } + case shapeI32x4: + s = s % 32 + if op.B3 { + lo = uint64(uint32(int32(lo)>>s)) | uint64(uint32(int32(lo>>32)>>s))<<32 + hi = uint64(uint32(int32(hi)>>s)) | uint64(uint32(int32(hi>>32)>>s))<<32 + } else { + lo = uint64(uint32(lo)>>s) | uint64(uint32(lo>>32)>>s)<<32 + hi = uint64(uint32(hi)>>s) | uint64(uint32(hi>>32)>>s)<<32 + } + case shapeI64x2: + s = s % 64 + if op.B3 { // signed + lo = uint64(int64(lo) >> s) + hi = uint64(int64(hi) >> s) + } else { + lo = lo >> s + hi = hi >> s + } + + } + ce.pushValue(lo) + ce.pushValue(hi) + frame.pc++ + case operationKindV128Cmp: + x2Hi, x2Lo := ce.popValue(), ce.popValue() + x1Hi, x1Lo := ce.popValue(), ce.popValue() + var result []bool + switch op.B1 { + case v128CmpTypeI8x16Eq: + result = []bool{ + byte(x1Lo>>0) == byte(x2Lo>>0), byte(x1Lo>>8) == byte(x2Lo>>8), + byte(x1Lo>>16) == byte(x2Lo>>16), byte(x1Lo>>24) == byte(x2Lo>>24), + byte(x1Lo>>32) == byte(x2Lo>>32), byte(x1Lo>>40) == byte(x2Lo>>40), + byte(x1Lo>>48) == byte(x2Lo>>48), byte(x1Lo>>56) == byte(x2Lo>>56), + byte(x1Hi>>0) == byte(x2Hi>>0), byte(x1Hi>>8) == byte(x2Hi>>8), + byte(x1Hi>>16) == byte(x2Hi>>16), byte(x1Hi>>24) == byte(x2Hi>>24), + byte(x1Hi>>32) == byte(x2Hi>>32), byte(x1Hi>>40) == byte(x2Hi>>40), + byte(x1Hi>>48) == byte(x2Hi>>48), byte(x1Hi>>56) == byte(x2Hi>>56), + } + case v128CmpTypeI8x16Ne: + result = []bool{ + byte(x1Lo>>0) != byte(x2Lo>>0), byte(x1Lo>>8) != byte(x2Lo>>8), + byte(x1Lo>>16) != byte(x2Lo>>16), byte(x1Lo>>24) != byte(x2Lo>>24), + byte(x1Lo>>32) != byte(x2Lo>>32), byte(x1Lo>>40) != byte(x2Lo>>40), + byte(x1Lo>>48) != byte(x2Lo>>48), byte(x1Lo>>56) != byte(x2Lo>>56), + byte(x1Hi>>0) != byte(x2Hi>>0), byte(x1Hi>>8) != byte(x2Hi>>8), + byte(x1Hi>>16) != byte(x2Hi>>16), byte(x1Hi>>24) != byte(x2Hi>>24), + byte(x1Hi>>32) != byte(x2Hi>>32), byte(x1Hi>>40) != byte(x2Hi>>40), + byte(x1Hi>>48) != byte(x2Hi>>48), byte(x1Hi>>56) != byte(x2Hi>>56), + } + case v128CmpTypeI8x16LtS: + result = []bool{ + int8(x1Lo>>0) < int8(x2Lo>>0), int8(x1Lo>>8) < int8(x2Lo>>8), + int8(x1Lo>>16) < int8(x2Lo>>16), int8(x1Lo>>24) < int8(x2Lo>>24), + int8(x1Lo>>32) < int8(x2Lo>>32), int8(x1Lo>>40) < int8(x2Lo>>40), + int8(x1Lo>>48) < int8(x2Lo>>48), int8(x1Lo>>56) < int8(x2Lo>>56), + int8(x1Hi>>0) < int8(x2Hi>>0), int8(x1Hi>>8) < int8(x2Hi>>8), + int8(x1Hi>>16) < int8(x2Hi>>16), int8(x1Hi>>24) < int8(x2Hi>>24), + int8(x1Hi>>32) < int8(x2Hi>>32), int8(x1Hi>>40) < int8(x2Hi>>40), + int8(x1Hi>>48) < int8(x2Hi>>48), int8(x1Hi>>56) < int8(x2Hi>>56), + } + case v128CmpTypeI8x16LtU: + result = []bool{ + byte(x1Lo>>0) < byte(x2Lo>>0), byte(x1Lo>>8) < byte(x2Lo>>8), + byte(x1Lo>>16) < byte(x2Lo>>16), byte(x1Lo>>24) < byte(x2Lo>>24), + byte(x1Lo>>32) < byte(x2Lo>>32), byte(x1Lo>>40) < byte(x2Lo>>40), + byte(x1Lo>>48) < byte(x2Lo>>48), byte(x1Lo>>56) < byte(x2Lo>>56), + byte(x1Hi>>0) < byte(x2Hi>>0), byte(x1Hi>>8) < byte(x2Hi>>8), + byte(x1Hi>>16) < byte(x2Hi>>16), byte(x1Hi>>24) < byte(x2Hi>>24), + byte(x1Hi>>32) < byte(x2Hi>>32), byte(x1Hi>>40) < byte(x2Hi>>40), + byte(x1Hi>>48) < byte(x2Hi>>48), byte(x1Hi>>56) < byte(x2Hi>>56), + } + case v128CmpTypeI8x16GtS: + result = []bool{ + int8(x1Lo>>0) > int8(x2Lo>>0), int8(x1Lo>>8) > int8(x2Lo>>8), + int8(x1Lo>>16) > int8(x2Lo>>16), int8(x1Lo>>24) > int8(x2Lo>>24), + int8(x1Lo>>32) > int8(x2Lo>>32), int8(x1Lo>>40) > int8(x2Lo>>40), + int8(x1Lo>>48) > int8(x2Lo>>48), int8(x1Lo>>56) > int8(x2Lo>>56), + int8(x1Hi>>0) > int8(x2Hi>>0), int8(x1Hi>>8) > int8(x2Hi>>8), + int8(x1Hi>>16) > int8(x2Hi>>16), int8(x1Hi>>24) > int8(x2Hi>>24), + int8(x1Hi>>32) > int8(x2Hi>>32), int8(x1Hi>>40) > int8(x2Hi>>40), + int8(x1Hi>>48) > int8(x2Hi>>48), int8(x1Hi>>56) > int8(x2Hi>>56), + } + case v128CmpTypeI8x16GtU: + result = []bool{ + byte(x1Lo>>0) > byte(x2Lo>>0), byte(x1Lo>>8) > byte(x2Lo>>8), + byte(x1Lo>>16) > byte(x2Lo>>16), byte(x1Lo>>24) > byte(x2Lo>>24), + byte(x1Lo>>32) > byte(x2Lo>>32), byte(x1Lo>>40) > byte(x2Lo>>40), + byte(x1Lo>>48) > byte(x2Lo>>48), byte(x1Lo>>56) > byte(x2Lo>>56), + byte(x1Hi>>0) > byte(x2Hi>>0), byte(x1Hi>>8) > byte(x2Hi>>8), + byte(x1Hi>>16) > byte(x2Hi>>16), byte(x1Hi>>24) > byte(x2Hi>>24), + byte(x1Hi>>32) > byte(x2Hi>>32), byte(x1Hi>>40) > byte(x2Hi>>40), + byte(x1Hi>>48) > byte(x2Hi>>48), byte(x1Hi>>56) > byte(x2Hi>>56), + } + case v128CmpTypeI8x16LeS: + result = []bool{ + int8(x1Lo>>0) <= int8(x2Lo>>0), int8(x1Lo>>8) <= int8(x2Lo>>8), + int8(x1Lo>>16) <= int8(x2Lo>>16), int8(x1Lo>>24) <= int8(x2Lo>>24), + int8(x1Lo>>32) <= int8(x2Lo>>32), int8(x1Lo>>40) <= int8(x2Lo>>40), + int8(x1Lo>>48) <= int8(x2Lo>>48), int8(x1Lo>>56) <= int8(x2Lo>>56), + int8(x1Hi>>0) <= int8(x2Hi>>0), int8(x1Hi>>8) <= int8(x2Hi>>8), + int8(x1Hi>>16) <= int8(x2Hi>>16), int8(x1Hi>>24) <= int8(x2Hi>>24), + int8(x1Hi>>32) <= int8(x2Hi>>32), int8(x1Hi>>40) <= int8(x2Hi>>40), + int8(x1Hi>>48) <= int8(x2Hi>>48), int8(x1Hi>>56) <= int8(x2Hi>>56), + } + case v128CmpTypeI8x16LeU: + result = []bool{ + byte(x1Lo>>0) <= byte(x2Lo>>0), byte(x1Lo>>8) <= byte(x2Lo>>8), + byte(x1Lo>>16) <= byte(x2Lo>>16), byte(x1Lo>>24) <= byte(x2Lo>>24), + byte(x1Lo>>32) <= byte(x2Lo>>32), byte(x1Lo>>40) <= byte(x2Lo>>40), + byte(x1Lo>>48) <= byte(x2Lo>>48), byte(x1Lo>>56) <= byte(x2Lo>>56), + byte(x1Hi>>0) <= byte(x2Hi>>0), byte(x1Hi>>8) <= byte(x2Hi>>8), + byte(x1Hi>>16) <= byte(x2Hi>>16), byte(x1Hi>>24) <= byte(x2Hi>>24), + byte(x1Hi>>32) <= byte(x2Hi>>32), byte(x1Hi>>40) <= byte(x2Hi>>40), + byte(x1Hi>>48) <= byte(x2Hi>>48), byte(x1Hi>>56) <= byte(x2Hi>>56), + } + case v128CmpTypeI8x16GeS: + result = []bool{ + int8(x1Lo>>0) >= int8(x2Lo>>0), int8(x1Lo>>8) >= int8(x2Lo>>8), + int8(x1Lo>>16) >= int8(x2Lo>>16), int8(x1Lo>>24) >= int8(x2Lo>>24), + int8(x1Lo>>32) >= int8(x2Lo>>32), int8(x1Lo>>40) >= int8(x2Lo>>40), + int8(x1Lo>>48) >= int8(x2Lo>>48), int8(x1Lo>>56) >= int8(x2Lo>>56), + int8(x1Hi>>0) >= int8(x2Hi>>0), int8(x1Hi>>8) >= int8(x2Hi>>8), + int8(x1Hi>>16) >= int8(x2Hi>>16), int8(x1Hi>>24) >= int8(x2Hi>>24), + int8(x1Hi>>32) >= int8(x2Hi>>32), int8(x1Hi>>40) >= int8(x2Hi>>40), + int8(x1Hi>>48) >= int8(x2Hi>>48), int8(x1Hi>>56) >= int8(x2Hi>>56), + } + case v128CmpTypeI8x16GeU: + result = []bool{ + byte(x1Lo>>0) >= byte(x2Lo>>0), byte(x1Lo>>8) >= byte(x2Lo>>8), + byte(x1Lo>>16) >= byte(x2Lo>>16), byte(x1Lo>>24) >= byte(x2Lo>>24), + byte(x1Lo>>32) >= byte(x2Lo>>32), byte(x1Lo>>40) >= byte(x2Lo>>40), + byte(x1Lo>>48) >= byte(x2Lo>>48), byte(x1Lo>>56) >= byte(x2Lo>>56), + byte(x1Hi>>0) >= byte(x2Hi>>0), byte(x1Hi>>8) >= byte(x2Hi>>8), + byte(x1Hi>>16) >= byte(x2Hi>>16), byte(x1Hi>>24) >= byte(x2Hi>>24), + byte(x1Hi>>32) >= byte(x2Hi>>32), byte(x1Hi>>40) >= byte(x2Hi>>40), + byte(x1Hi>>48) >= byte(x2Hi>>48), byte(x1Hi>>56) >= byte(x2Hi>>56), + } + case v128CmpTypeI16x8Eq: + result = []bool{ + uint16(x1Lo>>0) == uint16(x2Lo>>0), uint16(x1Lo>>16) == uint16(x2Lo>>16), + uint16(x1Lo>>32) == uint16(x2Lo>>32), uint16(x1Lo>>48) == uint16(x2Lo>>48), + uint16(x1Hi>>0) == uint16(x2Hi>>0), uint16(x1Hi>>16) == uint16(x2Hi>>16), + uint16(x1Hi>>32) == uint16(x2Hi>>32), uint16(x1Hi>>48) == uint16(x2Hi>>48), + } + case v128CmpTypeI16x8Ne: + result = []bool{ + uint16(x1Lo>>0) != uint16(x2Lo>>0), uint16(x1Lo>>16) != uint16(x2Lo>>16), + uint16(x1Lo>>32) != uint16(x2Lo>>32), uint16(x1Lo>>48) != uint16(x2Lo>>48), + uint16(x1Hi>>0) != uint16(x2Hi>>0), uint16(x1Hi>>16) != uint16(x2Hi>>16), + uint16(x1Hi>>32) != uint16(x2Hi>>32), uint16(x1Hi>>48) != uint16(x2Hi>>48), + } + case v128CmpTypeI16x8LtS: + result = []bool{ + int16(x1Lo>>0) < int16(x2Lo>>0), int16(x1Lo>>16) < int16(x2Lo>>16), + int16(x1Lo>>32) < int16(x2Lo>>32), int16(x1Lo>>48) < int16(x2Lo>>48), + int16(x1Hi>>0) < int16(x2Hi>>0), int16(x1Hi>>16) < int16(x2Hi>>16), + int16(x1Hi>>32) < int16(x2Hi>>32), int16(x1Hi>>48) < int16(x2Hi>>48), + } + case v128CmpTypeI16x8LtU: + result = []bool{ + uint16(x1Lo>>0) < uint16(x2Lo>>0), uint16(x1Lo>>16) < uint16(x2Lo>>16), + uint16(x1Lo>>32) < uint16(x2Lo>>32), uint16(x1Lo>>48) < uint16(x2Lo>>48), + uint16(x1Hi>>0) < uint16(x2Hi>>0), uint16(x1Hi>>16) < uint16(x2Hi>>16), + uint16(x1Hi>>32) < uint16(x2Hi>>32), uint16(x1Hi>>48) < uint16(x2Hi>>48), + } + case v128CmpTypeI16x8GtS: + result = []bool{ + int16(x1Lo>>0) > int16(x2Lo>>0), int16(x1Lo>>16) > int16(x2Lo>>16), + int16(x1Lo>>32) > int16(x2Lo>>32), int16(x1Lo>>48) > int16(x2Lo>>48), + int16(x1Hi>>0) > int16(x2Hi>>0), int16(x1Hi>>16) > int16(x2Hi>>16), + int16(x1Hi>>32) > int16(x2Hi>>32), int16(x1Hi>>48) > int16(x2Hi>>48), + } + case v128CmpTypeI16x8GtU: + result = []bool{ + uint16(x1Lo>>0) > uint16(x2Lo>>0), uint16(x1Lo>>16) > uint16(x2Lo>>16), + uint16(x1Lo>>32) > uint16(x2Lo>>32), uint16(x1Lo>>48) > uint16(x2Lo>>48), + uint16(x1Hi>>0) > uint16(x2Hi>>0), uint16(x1Hi>>16) > uint16(x2Hi>>16), + uint16(x1Hi>>32) > uint16(x2Hi>>32), uint16(x1Hi>>48) > uint16(x2Hi>>48), + } + case v128CmpTypeI16x8LeS: + result = []bool{ + int16(x1Lo>>0) <= int16(x2Lo>>0), int16(x1Lo>>16) <= int16(x2Lo>>16), + int16(x1Lo>>32) <= int16(x2Lo>>32), int16(x1Lo>>48) <= int16(x2Lo>>48), + int16(x1Hi>>0) <= int16(x2Hi>>0), int16(x1Hi>>16) <= int16(x2Hi>>16), + int16(x1Hi>>32) <= int16(x2Hi>>32), int16(x1Hi>>48) <= int16(x2Hi>>48), + } + case v128CmpTypeI16x8LeU: + result = []bool{ + uint16(x1Lo>>0) <= uint16(x2Lo>>0), uint16(x1Lo>>16) <= uint16(x2Lo>>16), + uint16(x1Lo>>32) <= uint16(x2Lo>>32), uint16(x1Lo>>48) <= uint16(x2Lo>>48), + uint16(x1Hi>>0) <= uint16(x2Hi>>0), uint16(x1Hi>>16) <= uint16(x2Hi>>16), + uint16(x1Hi>>32) <= uint16(x2Hi>>32), uint16(x1Hi>>48) <= uint16(x2Hi>>48), + } + case v128CmpTypeI16x8GeS: + result = []bool{ + int16(x1Lo>>0) >= int16(x2Lo>>0), int16(x1Lo>>16) >= int16(x2Lo>>16), + int16(x1Lo>>32) >= int16(x2Lo>>32), int16(x1Lo>>48) >= int16(x2Lo>>48), + int16(x1Hi>>0) >= int16(x2Hi>>0), int16(x1Hi>>16) >= int16(x2Hi>>16), + int16(x1Hi>>32) >= int16(x2Hi>>32), int16(x1Hi>>48) >= int16(x2Hi>>48), + } + case v128CmpTypeI16x8GeU: + result = []bool{ + uint16(x1Lo>>0) >= uint16(x2Lo>>0), uint16(x1Lo>>16) >= uint16(x2Lo>>16), + uint16(x1Lo>>32) >= uint16(x2Lo>>32), uint16(x1Lo>>48) >= uint16(x2Lo>>48), + uint16(x1Hi>>0) >= uint16(x2Hi>>0), uint16(x1Hi>>16) >= uint16(x2Hi>>16), + uint16(x1Hi>>32) >= uint16(x2Hi>>32), uint16(x1Hi>>48) >= uint16(x2Hi>>48), + } + case v128CmpTypeI32x4Eq: + result = []bool{ + uint32(x1Lo>>0) == uint32(x2Lo>>0), uint32(x1Lo>>32) == uint32(x2Lo>>32), + uint32(x1Hi>>0) == uint32(x2Hi>>0), uint32(x1Hi>>32) == uint32(x2Hi>>32), + } + case v128CmpTypeI32x4Ne: + result = []bool{ + uint32(x1Lo>>0) != uint32(x2Lo>>0), uint32(x1Lo>>32) != uint32(x2Lo>>32), + uint32(x1Hi>>0) != uint32(x2Hi>>0), uint32(x1Hi>>32) != uint32(x2Hi>>32), + } + case v128CmpTypeI32x4LtS: + result = []bool{ + int32(x1Lo>>0) < int32(x2Lo>>0), int32(x1Lo>>32) < int32(x2Lo>>32), + int32(x1Hi>>0) < int32(x2Hi>>0), int32(x1Hi>>32) < int32(x2Hi>>32), + } + case v128CmpTypeI32x4LtU: + result = []bool{ + uint32(x1Lo>>0) < uint32(x2Lo>>0), uint32(x1Lo>>32) < uint32(x2Lo>>32), + uint32(x1Hi>>0) < uint32(x2Hi>>0), uint32(x1Hi>>32) < uint32(x2Hi>>32), + } + case v128CmpTypeI32x4GtS: + result = []bool{ + int32(x1Lo>>0) > int32(x2Lo>>0), int32(x1Lo>>32) > int32(x2Lo>>32), + int32(x1Hi>>0) > int32(x2Hi>>0), int32(x1Hi>>32) > int32(x2Hi>>32), + } + case v128CmpTypeI32x4GtU: + result = []bool{ + uint32(x1Lo>>0) > uint32(x2Lo>>0), uint32(x1Lo>>32) > uint32(x2Lo>>32), + uint32(x1Hi>>0) > uint32(x2Hi>>0), uint32(x1Hi>>32) > uint32(x2Hi>>32), + } + case v128CmpTypeI32x4LeS: + result = []bool{ + int32(x1Lo>>0) <= int32(x2Lo>>0), int32(x1Lo>>32) <= int32(x2Lo>>32), + int32(x1Hi>>0) <= int32(x2Hi>>0), int32(x1Hi>>32) <= int32(x2Hi>>32), + } + case v128CmpTypeI32x4LeU: + result = []bool{ + uint32(x1Lo>>0) <= uint32(x2Lo>>0), uint32(x1Lo>>32) <= uint32(x2Lo>>32), + uint32(x1Hi>>0) <= uint32(x2Hi>>0), uint32(x1Hi>>32) <= uint32(x2Hi>>32), + } + case v128CmpTypeI32x4GeS: + result = []bool{ + int32(x1Lo>>0) >= int32(x2Lo>>0), int32(x1Lo>>32) >= int32(x2Lo>>32), + int32(x1Hi>>0) >= int32(x2Hi>>0), int32(x1Hi>>32) >= int32(x2Hi>>32), + } + case v128CmpTypeI32x4GeU: + result = []bool{ + uint32(x1Lo>>0) >= uint32(x2Lo>>0), uint32(x1Lo>>32) >= uint32(x2Lo>>32), + uint32(x1Hi>>0) >= uint32(x2Hi>>0), uint32(x1Hi>>32) >= uint32(x2Hi>>32), + } + case v128CmpTypeI64x2Eq: + result = []bool{x1Lo == x2Lo, x1Hi == x2Hi} + case v128CmpTypeI64x2Ne: + result = []bool{x1Lo != x2Lo, x1Hi != x2Hi} + case v128CmpTypeI64x2LtS: + result = []bool{int64(x1Lo) < int64(x2Lo), int64(x1Hi) < int64(x2Hi)} + case v128CmpTypeI64x2GtS: + result = []bool{int64(x1Lo) > int64(x2Lo), int64(x1Hi) > int64(x2Hi)} + case v128CmpTypeI64x2LeS: + result = []bool{int64(x1Lo) <= int64(x2Lo), int64(x1Hi) <= int64(x2Hi)} + case v128CmpTypeI64x2GeS: + result = []bool{int64(x1Lo) >= int64(x2Lo), int64(x1Hi) >= int64(x2Hi)} + case v128CmpTypeF32x4Eq: + result = []bool{ + math.Float32frombits(uint32(x1Lo>>0)) == math.Float32frombits(uint32(x2Lo>>0)), + math.Float32frombits(uint32(x1Lo>>32)) == math.Float32frombits(uint32(x2Lo>>32)), + math.Float32frombits(uint32(x1Hi>>0)) == math.Float32frombits(uint32(x2Hi>>0)), + math.Float32frombits(uint32(x1Hi>>32)) == math.Float32frombits(uint32(x2Hi>>32)), + } + case v128CmpTypeF32x4Ne: + result = []bool{ + math.Float32frombits(uint32(x1Lo>>0)) != math.Float32frombits(uint32(x2Lo>>0)), + math.Float32frombits(uint32(x1Lo>>32)) != math.Float32frombits(uint32(x2Lo>>32)), + math.Float32frombits(uint32(x1Hi>>0)) != math.Float32frombits(uint32(x2Hi>>0)), + math.Float32frombits(uint32(x1Hi>>32)) != math.Float32frombits(uint32(x2Hi>>32)), + } + case v128CmpTypeF32x4Lt: + result = []bool{ + math.Float32frombits(uint32(x1Lo>>0)) < math.Float32frombits(uint32(x2Lo>>0)), + math.Float32frombits(uint32(x1Lo>>32)) < math.Float32frombits(uint32(x2Lo>>32)), + math.Float32frombits(uint32(x1Hi>>0)) < math.Float32frombits(uint32(x2Hi>>0)), + math.Float32frombits(uint32(x1Hi>>32)) < math.Float32frombits(uint32(x2Hi>>32)), + } + case v128CmpTypeF32x4Gt: + result = []bool{ + math.Float32frombits(uint32(x1Lo>>0)) > math.Float32frombits(uint32(x2Lo>>0)), + math.Float32frombits(uint32(x1Lo>>32)) > math.Float32frombits(uint32(x2Lo>>32)), + math.Float32frombits(uint32(x1Hi>>0)) > math.Float32frombits(uint32(x2Hi>>0)), + math.Float32frombits(uint32(x1Hi>>32)) > math.Float32frombits(uint32(x2Hi>>32)), + } + case v128CmpTypeF32x4Le: + result = []bool{ + math.Float32frombits(uint32(x1Lo>>0)) <= math.Float32frombits(uint32(x2Lo>>0)), + math.Float32frombits(uint32(x1Lo>>32)) <= math.Float32frombits(uint32(x2Lo>>32)), + math.Float32frombits(uint32(x1Hi>>0)) <= math.Float32frombits(uint32(x2Hi>>0)), + math.Float32frombits(uint32(x1Hi>>32)) <= math.Float32frombits(uint32(x2Hi>>32)), + } + case v128CmpTypeF32x4Ge: + result = []bool{ + math.Float32frombits(uint32(x1Lo>>0)) >= math.Float32frombits(uint32(x2Lo>>0)), + math.Float32frombits(uint32(x1Lo>>32)) >= math.Float32frombits(uint32(x2Lo>>32)), + math.Float32frombits(uint32(x1Hi>>0)) >= math.Float32frombits(uint32(x2Hi>>0)), + math.Float32frombits(uint32(x1Hi>>32)) >= math.Float32frombits(uint32(x2Hi>>32)), + } + case v128CmpTypeF64x2Eq: + result = []bool{ + math.Float64frombits(x1Lo) == math.Float64frombits(x2Lo), + math.Float64frombits(x1Hi) == math.Float64frombits(x2Hi), + } + case v128CmpTypeF64x2Ne: + result = []bool{ + math.Float64frombits(x1Lo) != math.Float64frombits(x2Lo), + math.Float64frombits(x1Hi) != math.Float64frombits(x2Hi), + } + case v128CmpTypeF64x2Lt: + result = []bool{ + math.Float64frombits(x1Lo) < math.Float64frombits(x2Lo), + math.Float64frombits(x1Hi) < math.Float64frombits(x2Hi), + } + case v128CmpTypeF64x2Gt: + result = []bool{ + math.Float64frombits(x1Lo) > math.Float64frombits(x2Lo), + math.Float64frombits(x1Hi) > math.Float64frombits(x2Hi), + } + case v128CmpTypeF64x2Le: + result = []bool{ + math.Float64frombits(x1Lo) <= math.Float64frombits(x2Lo), + math.Float64frombits(x1Hi) <= math.Float64frombits(x2Hi), + } + case v128CmpTypeF64x2Ge: + result = []bool{ + math.Float64frombits(x1Lo) >= math.Float64frombits(x2Lo), + math.Float64frombits(x1Hi) >= math.Float64frombits(x2Hi), + } + } + + var retLo, retHi uint64 + laneNum := len(result) + switch laneNum { + case 16: + for i, b := range result { + if b { + if i < 8 { + retLo |= 0xff << (i * 8) + } else { + retHi |= 0xff << ((i - 8) * 8) + } + } + } + case 8: + for i, b := range result { + if b { + if i < 4 { + retLo |= 0xffff << (i * 16) + } else { + retHi |= 0xffff << ((i - 4) * 16) + } + } + } + case 4: + for i, b := range result { + if b { + if i < 2 { + retLo |= 0xffff_ffff << (i * 32) + } else { + retHi |= 0xffff_ffff << ((i - 2) * 32) + } + } + } + case 2: + if result[0] { + retLo = ^uint64(0) + } + if result[1] { + retHi = ^uint64(0) + } + } + + ce.pushValue(retLo) + ce.pushValue(retHi) + frame.pc++ + case operationKindV128AddSat: + x2hi, x2Lo := ce.popValue(), ce.popValue() + x1hi, x1Lo := ce.popValue(), ce.popValue() + + var retLo, retHi uint64 + + // Lane-wise addition while saturating the overflowing values. + // https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md#saturating-integer-addition + switch op.B1 { + case shapeI8x16: + for i := 0; i < 16; i++ { + var v, w byte + if i < 8 { + v, w = byte(x1Lo>>(i*8)), byte(x2Lo>>(i*8)) + } else { + v, w = byte(x1hi>>((i-8)*8)), byte(x2hi>>((i-8)*8)) + } + + var uv uint64 + if op.B3 { // signed + if subbed := int64(int8(v)) + int64(int8(w)); subbed < math.MinInt8 { + uv = uint64(byte(0x80)) + } else if subbed > math.MaxInt8 { + uv = uint64(byte(0x7f)) + } else { + uv = uint64(byte(int8(subbed))) + } + } else { + if subbed := int64(v) + int64(w); subbed < 0 { + uv = uint64(byte(0)) + } else if subbed > math.MaxUint8 { + uv = uint64(byte(0xff)) + } else { + uv = uint64(byte(subbed)) + } + } + + if i < 8 { // first 8 lanes are on lower 64bits. + retLo |= uv << (i * 8) + } else { + retHi |= uv << ((i - 8) * 8) + } + } + case shapeI16x8: + for i := 0; i < 8; i++ { + var v, w uint16 + if i < 4 { + v, w = uint16(x1Lo>>(i*16)), uint16(x2Lo>>(i*16)) + } else { + v, w = uint16(x1hi>>((i-4)*16)), uint16(x2hi>>((i-4)*16)) + } + + var uv uint64 + if op.B3 { // signed + if added := int64(int16(v)) + int64(int16(w)); added < math.MinInt16 { + uv = uint64(uint16(0x8000)) + } else if added > math.MaxInt16 { + uv = uint64(uint16(0x7fff)) + } else { + uv = uint64(uint16(int16(added))) + } + } else { + if added := int64(v) + int64(w); added < 0 { + uv = uint64(uint16(0)) + } else if added > math.MaxUint16 { + uv = uint64(uint16(0xffff)) + } else { + uv = uint64(uint16(added)) + } + } + + if i < 4 { // first 4 lanes are on lower 64bits. + retLo |= uv << (i * 16) + } else { + retHi |= uv << ((i - 4) * 16) + } + } + } + + ce.pushValue(retLo) + ce.pushValue(retHi) + frame.pc++ + case operationKindV128SubSat: + x2hi, x2Lo := ce.popValue(), ce.popValue() + x1hi, x1Lo := ce.popValue(), ce.popValue() + + var retLo, retHi uint64 + + // Lane-wise subtraction while saturating the overflowing values. + // https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md#saturating-integer-subtraction + switch op.B1 { + case shapeI8x16: + for i := 0; i < 16; i++ { + var v, w byte + if i < 8 { + v, w = byte(x1Lo>>(i*8)), byte(x2Lo>>(i*8)) + } else { + v, w = byte(x1hi>>((i-8)*8)), byte(x2hi>>((i-8)*8)) + } + + var uv uint64 + if op.B3 { // signed + if subbed := int64(int8(v)) - int64(int8(w)); subbed < math.MinInt8 { + uv = uint64(byte(0x80)) + } else if subbed > math.MaxInt8 { + uv = uint64(byte(0x7f)) + } else { + uv = uint64(byte(int8(subbed))) + } + } else { + if subbed := int64(v) - int64(w); subbed < 0 { + uv = uint64(byte(0)) + } else if subbed > math.MaxUint8 { + uv = uint64(byte(0xff)) + } else { + uv = uint64(byte(subbed)) + } + } + + if i < 8 { + retLo |= uv << (i * 8) + } else { + retHi |= uv << ((i - 8) * 8) + } + } + case shapeI16x8: + for i := 0; i < 8; i++ { + var v, w uint16 + if i < 4 { + v, w = uint16(x1Lo>>(i*16)), uint16(x2Lo>>(i*16)) + } else { + v, w = uint16(x1hi>>((i-4)*16)), uint16(x2hi>>((i-4)*16)) + } + + var uv uint64 + if op.B3 { // signed + if subbed := int64(int16(v)) - int64(int16(w)); subbed < math.MinInt16 { + uv = uint64(uint16(0x8000)) + } else if subbed > math.MaxInt16 { + uv = uint64(uint16(0x7fff)) + } else { + uv = uint64(uint16(int16(subbed))) + } + } else { + if subbed := int64(v) - int64(w); subbed < 0 { + uv = uint64(uint16(0)) + } else if subbed > math.MaxUint16 { + uv = uint64(uint16(0xffff)) + } else { + uv = uint64(uint16(subbed)) + } + } + + if i < 4 { + retLo |= uv << (i * 16) + } else { + retHi |= uv << ((i - 4) * 16) + } + } + } + + ce.pushValue(retLo) + ce.pushValue(retHi) + frame.pc++ + case operationKindV128Mul: + x2hi, x2lo := ce.popValue(), ce.popValue() + x1hi, x1lo := ce.popValue(), ce.popValue() + var retLo, retHi uint64 + switch op.B1 { + case shapeI16x8: + retHi = uint64(uint16(x1hi)*uint16(x2hi)) | (uint64(uint16(x1hi>>16)*uint16(x2hi>>16)) << 16) | + (uint64(uint16(x1hi>>32)*uint16(x2hi>>32)) << 32) | (uint64(uint16(x1hi>>48)*uint16(x2hi>>48)) << 48) + retLo = uint64(uint16(x1lo)*uint16(x2lo)) | (uint64(uint16(x1lo>>16)*uint16(x2lo>>16)) << 16) | + (uint64(uint16(x1lo>>32)*uint16(x2lo>>32)) << 32) | (uint64(uint16(x1lo>>48)*uint16(x2lo>>48)) << 48) + case shapeI32x4: + retHi = uint64(uint32(x1hi)*uint32(x2hi)) | (uint64(uint32(x1hi>>32)*uint32(x2hi>>32)) << 32) + retLo = uint64(uint32(x1lo)*uint32(x2lo)) | (uint64(uint32(x1lo>>32)*uint32(x2lo>>32)) << 32) + case shapeI64x2: + retHi = x1hi * x2hi + retLo = x1lo * x2lo + case shapeF32x4: + retHi = mulFloat32bits(uint32(x1hi), uint32(x2hi)) | mulFloat32bits(uint32(x1hi>>32), uint32(x2hi>>32))<<32 + retLo = mulFloat32bits(uint32(x1lo), uint32(x2lo)) | mulFloat32bits(uint32(x1lo>>32), uint32(x2lo>>32))<<32 + case shapeF64x2: + retHi = math.Float64bits(math.Float64frombits(x1hi) * math.Float64frombits(x2hi)) + retLo = math.Float64bits(math.Float64frombits(x1lo) * math.Float64frombits(x2lo)) + } + ce.pushValue(retLo) + ce.pushValue(retHi) + frame.pc++ + case operationKindV128Div: + x2hi, x2lo := ce.popValue(), ce.popValue() + x1hi, x1lo := ce.popValue(), ce.popValue() + var retLo, retHi uint64 + if op.B1 == shapeF64x2 { + retHi = math.Float64bits(math.Float64frombits(x1hi) / math.Float64frombits(x2hi)) + retLo = math.Float64bits(math.Float64frombits(x1lo) / math.Float64frombits(x2lo)) + } else { + retHi = divFloat32bits(uint32(x1hi), uint32(x2hi)) | divFloat32bits(uint32(x1hi>>32), uint32(x2hi>>32))<<32 + retLo = divFloat32bits(uint32(x1lo), uint32(x2lo)) | divFloat32bits(uint32(x1lo>>32), uint32(x2lo>>32))<<32 + } + ce.pushValue(retLo) + ce.pushValue(retHi) + frame.pc++ + case operationKindV128Neg: + hi, lo := ce.popValue(), ce.popValue() + switch op.B1 { + case shapeI8x16: + lo = uint64(-byte(lo)) | (uint64(-byte(lo>>8)) << 8) | + (uint64(-byte(lo>>16)) << 16) | (uint64(-byte(lo>>24)) << 24) | + (uint64(-byte(lo>>32)) << 32) | (uint64(-byte(lo>>40)) << 40) | + (uint64(-byte(lo>>48)) << 48) | (uint64(-byte(lo>>56)) << 56) + hi = uint64(-byte(hi)) | (uint64(-byte(hi>>8)) << 8) | + (uint64(-byte(hi>>16)) << 16) | (uint64(-byte(hi>>24)) << 24) | + (uint64(-byte(hi>>32)) << 32) | (uint64(-byte(hi>>40)) << 40) | + (uint64(-byte(hi>>48)) << 48) | (uint64(-byte(hi>>56)) << 56) + case shapeI16x8: + hi = uint64(-uint16(hi)) | (uint64(-uint16(hi>>16)) << 16) | + (uint64(-uint16(hi>>32)) << 32) | (uint64(-uint16(hi>>48)) << 48) + lo = uint64(-uint16(lo)) | (uint64(-uint16(lo>>16)) << 16) | + (uint64(-uint16(lo>>32)) << 32) | (uint64(-uint16(lo>>48)) << 48) + case shapeI32x4: + hi = uint64(-uint32(hi)) | (uint64(-uint32(hi>>32)) << 32) + lo = uint64(-uint32(lo)) | (uint64(-uint32(lo>>32)) << 32) + case shapeI64x2: + hi = -hi + lo = -lo + case shapeF32x4: + hi = uint64(math.Float32bits(-math.Float32frombits(uint32(hi)))) | + (uint64(math.Float32bits(-math.Float32frombits(uint32(hi>>32)))) << 32) + lo = uint64(math.Float32bits(-math.Float32frombits(uint32(lo)))) | + (uint64(math.Float32bits(-math.Float32frombits(uint32(lo>>32)))) << 32) + case shapeF64x2: + hi = math.Float64bits(-math.Float64frombits(hi)) + lo = math.Float64bits(-math.Float64frombits(lo)) + } + ce.pushValue(lo) + ce.pushValue(hi) + frame.pc++ + case operationKindV128Sqrt: + hi, lo := ce.popValue(), ce.popValue() + if op.B1 == shapeF64x2 { + hi = math.Float64bits(math.Sqrt(math.Float64frombits(hi))) + lo = math.Float64bits(math.Sqrt(math.Float64frombits(lo))) + } else { + hi = uint64(math.Float32bits(float32(math.Sqrt(float64(math.Float32frombits(uint32(hi))))))) | + (uint64(math.Float32bits(float32(math.Sqrt(float64(math.Float32frombits(uint32(hi>>32))))))) << 32) + lo = uint64(math.Float32bits(float32(math.Sqrt(float64(math.Float32frombits(uint32(lo))))))) | + (uint64(math.Float32bits(float32(math.Sqrt(float64(math.Float32frombits(uint32(lo>>32))))))) << 32) + } + ce.pushValue(lo) + ce.pushValue(hi) + frame.pc++ + case operationKindV128Abs: + hi, lo := ce.popValue(), ce.popValue() + switch op.B1 { + case shapeI8x16: + lo = uint64(i8Abs(byte(lo))) | (uint64(i8Abs(byte(lo>>8))) << 8) | + (uint64(i8Abs(byte(lo>>16))) << 16) | (uint64(i8Abs(byte(lo>>24))) << 24) | + (uint64(i8Abs(byte(lo>>32))) << 32) | (uint64(i8Abs(byte(lo>>40))) << 40) | + (uint64(i8Abs(byte(lo>>48))) << 48) | (uint64(i8Abs(byte(lo>>56))) << 56) + hi = uint64(i8Abs(byte(hi))) | (uint64(i8Abs(byte(hi>>8))) << 8) | + (uint64(i8Abs(byte(hi>>16))) << 16) | (uint64(i8Abs(byte(hi>>24))) << 24) | + (uint64(i8Abs(byte(hi>>32))) << 32) | (uint64(i8Abs(byte(hi>>40))) << 40) | + (uint64(i8Abs(byte(hi>>48))) << 48) | (uint64(i8Abs(byte(hi>>56))) << 56) + case shapeI16x8: + hi = uint64(i16Abs(uint16(hi))) | (uint64(i16Abs(uint16(hi>>16))) << 16) | + (uint64(i16Abs(uint16(hi>>32))) << 32) | (uint64(i16Abs(uint16(hi>>48))) << 48) + lo = uint64(i16Abs(uint16(lo))) | (uint64(i16Abs(uint16(lo>>16))) << 16) | + (uint64(i16Abs(uint16(lo>>32))) << 32) | (uint64(i16Abs(uint16(lo>>48))) << 48) + case shapeI32x4: + hi = uint64(i32Abs(uint32(hi))) | (uint64(i32Abs(uint32(hi>>32))) << 32) + lo = uint64(i32Abs(uint32(lo))) | (uint64(i32Abs(uint32(lo>>32))) << 32) + case shapeI64x2: + if int64(hi) < 0 { + hi = -hi + } + if int64(lo) < 0 { + lo = -lo + } + case shapeF32x4: + hi = hi &^ (1<<31 | 1<<63) + lo = lo &^ (1<<31 | 1<<63) + case shapeF64x2: + hi = hi &^ (1 << 63) + lo = lo &^ (1 << 63) + } + ce.pushValue(lo) + ce.pushValue(hi) + frame.pc++ + case operationKindV128Popcnt: + hi, lo := ce.popValue(), ce.popValue() + var retLo, retHi uint64 + for i := 0; i < 16; i++ { + var v byte + if i < 8 { + v = byte(lo >> (i * 8)) + } else { + v = byte(hi >> ((i - 8) * 8)) + } + + var cnt uint64 + for i := 0; i < 8; i++ { + if (v>>i)&0b1 != 0 { + cnt++ + } + } + + if i < 8 { + retLo |= cnt << (i * 8) + } else { + retHi |= cnt << ((i - 8) * 8) + } + } + ce.pushValue(retLo) + ce.pushValue(retHi) + frame.pc++ + case operationKindV128Min: + x2hi, x2lo := ce.popValue(), ce.popValue() + x1hi, x1lo := ce.popValue(), ce.popValue() + var retLo, retHi uint64 + switch op.B1 { + case shapeI8x16: + if op.B3 { // signed + retLo = uint64(i8MinS(uint8(x1lo>>8), uint8(x2lo>>8)))<<8 | uint64(i8MinS(uint8(x1lo), uint8(x2lo))) | + uint64(i8MinS(uint8(x1lo>>24), uint8(x2lo>>24)))<<24 | uint64(i8MinS(uint8(x1lo>>16), uint8(x2lo>>16)))<<16 | + uint64(i8MinS(uint8(x1lo>>40), uint8(x2lo>>40)))<<40 | uint64(i8MinS(uint8(x1lo>>32), uint8(x2lo>>32)))<<32 | + uint64(i8MinS(uint8(x1lo>>56), uint8(x2lo>>56)))<<56 | uint64(i8MinS(uint8(x1lo>>48), uint8(x2lo>>48)))<<48 + retHi = uint64(i8MinS(uint8(x1hi>>8), uint8(x2hi>>8)))<<8 | uint64(i8MinS(uint8(x1hi), uint8(x2hi))) | + uint64(i8MinS(uint8(x1hi>>24), uint8(x2hi>>24)))<<24 | uint64(i8MinS(uint8(x1hi>>16), uint8(x2hi>>16)))<<16 | + uint64(i8MinS(uint8(x1hi>>40), uint8(x2hi>>40)))<<40 | uint64(i8MinS(uint8(x1hi>>32), uint8(x2hi>>32)))<<32 | + uint64(i8MinS(uint8(x1hi>>56), uint8(x2hi>>56)))<<56 | uint64(i8MinS(uint8(x1hi>>48), uint8(x2hi>>48)))<<48 + } else { + retLo = uint64(i8MinU(uint8(x1lo>>8), uint8(x2lo>>8)))<<8 | uint64(i8MinU(uint8(x1lo), uint8(x2lo))) | + uint64(i8MinU(uint8(x1lo>>24), uint8(x2lo>>24)))<<24 | uint64(i8MinU(uint8(x1lo>>16), uint8(x2lo>>16)))<<16 | + uint64(i8MinU(uint8(x1lo>>40), uint8(x2lo>>40)))<<40 | uint64(i8MinU(uint8(x1lo>>32), uint8(x2lo>>32)))<<32 | + uint64(i8MinU(uint8(x1lo>>56), uint8(x2lo>>56)))<<56 | uint64(i8MinU(uint8(x1lo>>48), uint8(x2lo>>48)))<<48 + retHi = uint64(i8MinU(uint8(x1hi>>8), uint8(x2hi>>8)))<<8 | uint64(i8MinU(uint8(x1hi), uint8(x2hi))) | + uint64(i8MinU(uint8(x1hi>>24), uint8(x2hi>>24)))<<24 | uint64(i8MinU(uint8(x1hi>>16), uint8(x2hi>>16)))<<16 | + uint64(i8MinU(uint8(x1hi>>40), uint8(x2hi>>40)))<<40 | uint64(i8MinU(uint8(x1hi>>32), uint8(x2hi>>32)))<<32 | + uint64(i8MinU(uint8(x1hi>>56), uint8(x2hi>>56)))<<56 | uint64(i8MinU(uint8(x1hi>>48), uint8(x2hi>>48)))<<48 + } + case shapeI16x8: + if op.B3 { // signed + retLo = uint64(i16MinS(uint16(x1lo), uint16(x2lo))) | + uint64(i16MinS(uint16(x1lo>>16), uint16(x2lo>>16)))<<16 | + uint64(i16MinS(uint16(x1lo>>32), uint16(x2lo>>32)))<<32 | + uint64(i16MinS(uint16(x1lo>>48), uint16(x2lo>>48)))<<48 + retHi = uint64(i16MinS(uint16(x1hi), uint16(x2hi))) | + uint64(i16MinS(uint16(x1hi>>16), uint16(x2hi>>16)))<<16 | + uint64(i16MinS(uint16(x1hi>>32), uint16(x2hi>>32)))<<32 | + uint64(i16MinS(uint16(x1hi>>48), uint16(x2hi>>48)))<<48 + } else { + retLo = uint64(i16MinU(uint16(x1lo), uint16(x2lo))) | + uint64(i16MinU(uint16(x1lo>>16), uint16(x2lo>>16)))<<16 | + uint64(i16MinU(uint16(x1lo>>32), uint16(x2lo>>32)))<<32 | + uint64(i16MinU(uint16(x1lo>>48), uint16(x2lo>>48)))<<48 + retHi = uint64(i16MinU(uint16(x1hi), uint16(x2hi))) | + uint64(i16MinU(uint16(x1hi>>16), uint16(x2hi>>16)))<<16 | + uint64(i16MinU(uint16(x1hi>>32), uint16(x2hi>>32)))<<32 | + uint64(i16MinU(uint16(x1hi>>48), uint16(x2hi>>48)))<<48 + } + case shapeI32x4: + if op.B3 { // signed + retLo = uint64(i32MinS(uint32(x1lo), uint32(x2lo))) | + uint64(i32MinS(uint32(x1lo>>32), uint32(x2lo>>32)))<<32 + retHi = uint64(i32MinS(uint32(x1hi), uint32(x2hi))) | + uint64(i32MinS(uint32(x1hi>>32), uint32(x2hi>>32)))<<32 + } else { + retLo = uint64(i32MinU(uint32(x1lo), uint32(x2lo))) | + uint64(i32MinU(uint32(x1lo>>32), uint32(x2lo>>32)))<<32 + retHi = uint64(i32MinU(uint32(x1hi), uint32(x2hi))) | + uint64(i32MinU(uint32(x1hi>>32), uint32(x2hi>>32)))<<32 + } + case shapeF32x4: + retHi = wasmCompatMin32bits(uint32(x1hi), uint32(x2hi)) | + wasmCompatMin32bits(uint32(x1hi>>32), uint32(x2hi>>32))<<32 + retLo = wasmCompatMin32bits(uint32(x1lo), uint32(x2lo)) | + wasmCompatMin32bits(uint32(x1lo>>32), uint32(x2lo>>32))<<32 + case shapeF64x2: + retHi = math.Float64bits(moremath.WasmCompatMin64( + math.Float64frombits(x1hi), + math.Float64frombits(x2hi), + )) + retLo = math.Float64bits(moremath.WasmCompatMin64( + math.Float64frombits(x1lo), + math.Float64frombits(x2lo), + )) + } + ce.pushValue(retLo) + ce.pushValue(retHi) + frame.pc++ + case operationKindV128Max: + x2hi, x2lo := ce.popValue(), ce.popValue() + x1hi, x1lo := ce.popValue(), ce.popValue() + var retLo, retHi uint64 + switch op.B1 { + case shapeI8x16: + if op.B3 { // signed + retLo = uint64(i8MaxS(uint8(x1lo>>8), uint8(x2lo>>8)))<<8 | uint64(i8MaxS(uint8(x1lo), uint8(x2lo))) | + uint64(i8MaxS(uint8(x1lo>>24), uint8(x2lo>>24)))<<24 | uint64(i8MaxS(uint8(x1lo>>16), uint8(x2lo>>16)))<<16 | + uint64(i8MaxS(uint8(x1lo>>40), uint8(x2lo>>40)))<<40 | uint64(i8MaxS(uint8(x1lo>>32), uint8(x2lo>>32)))<<32 | + uint64(i8MaxS(uint8(x1lo>>56), uint8(x2lo>>56)))<<56 | uint64(i8MaxS(uint8(x1lo>>48), uint8(x2lo>>48)))<<48 + retHi = uint64(i8MaxS(uint8(x1hi>>8), uint8(x2hi>>8)))<<8 | uint64(i8MaxS(uint8(x1hi), uint8(x2hi))) | + uint64(i8MaxS(uint8(x1hi>>24), uint8(x2hi>>24)))<<24 | uint64(i8MaxS(uint8(x1hi>>16), uint8(x2hi>>16)))<<16 | + uint64(i8MaxS(uint8(x1hi>>40), uint8(x2hi>>40)))<<40 | uint64(i8MaxS(uint8(x1hi>>32), uint8(x2hi>>32)))<<32 | + uint64(i8MaxS(uint8(x1hi>>56), uint8(x2hi>>56)))<<56 | uint64(i8MaxS(uint8(x1hi>>48), uint8(x2hi>>48)))<<48 + } else { + retLo = uint64(i8MaxU(uint8(x1lo>>8), uint8(x2lo>>8)))<<8 | uint64(i8MaxU(uint8(x1lo), uint8(x2lo))) | + uint64(i8MaxU(uint8(x1lo>>24), uint8(x2lo>>24)))<<24 | uint64(i8MaxU(uint8(x1lo>>16), uint8(x2lo>>16)))<<16 | + uint64(i8MaxU(uint8(x1lo>>40), uint8(x2lo>>40)))<<40 | uint64(i8MaxU(uint8(x1lo>>32), uint8(x2lo>>32)))<<32 | + uint64(i8MaxU(uint8(x1lo>>56), uint8(x2lo>>56)))<<56 | uint64(i8MaxU(uint8(x1lo>>48), uint8(x2lo>>48)))<<48 + retHi = uint64(i8MaxU(uint8(x1hi>>8), uint8(x2hi>>8)))<<8 | uint64(i8MaxU(uint8(x1hi), uint8(x2hi))) | + uint64(i8MaxU(uint8(x1hi>>24), uint8(x2hi>>24)))<<24 | uint64(i8MaxU(uint8(x1hi>>16), uint8(x2hi>>16)))<<16 | + uint64(i8MaxU(uint8(x1hi>>40), uint8(x2hi>>40)))<<40 | uint64(i8MaxU(uint8(x1hi>>32), uint8(x2hi>>32)))<<32 | + uint64(i8MaxU(uint8(x1hi>>56), uint8(x2hi>>56)))<<56 | uint64(i8MaxU(uint8(x1hi>>48), uint8(x2hi>>48)))<<48 + } + case shapeI16x8: + if op.B3 { // signed + retLo = uint64(i16MaxS(uint16(x1lo), uint16(x2lo))) | + uint64(i16MaxS(uint16(x1lo>>16), uint16(x2lo>>16)))<<16 | + uint64(i16MaxS(uint16(x1lo>>32), uint16(x2lo>>32)))<<32 | + uint64(i16MaxS(uint16(x1lo>>48), uint16(x2lo>>48)))<<48 + retHi = uint64(i16MaxS(uint16(x1hi), uint16(x2hi))) | + uint64(i16MaxS(uint16(x1hi>>16), uint16(x2hi>>16)))<<16 | + uint64(i16MaxS(uint16(x1hi>>32), uint16(x2hi>>32)))<<32 | + uint64(i16MaxS(uint16(x1hi>>48), uint16(x2hi>>48)))<<48 + } else { + retLo = uint64(i16MaxU(uint16(x1lo), uint16(x2lo))) | + uint64(i16MaxU(uint16(x1lo>>16), uint16(x2lo>>16)))<<16 | + uint64(i16MaxU(uint16(x1lo>>32), uint16(x2lo>>32)))<<32 | + uint64(i16MaxU(uint16(x1lo>>48), uint16(x2lo>>48)))<<48 + retHi = uint64(i16MaxU(uint16(x1hi), uint16(x2hi))) | + uint64(i16MaxU(uint16(x1hi>>16), uint16(x2hi>>16)))<<16 | + uint64(i16MaxU(uint16(x1hi>>32), uint16(x2hi>>32)))<<32 | + uint64(i16MaxU(uint16(x1hi>>48), uint16(x2hi>>48)))<<48 + } + case shapeI32x4: + if op.B3 { // signed + retLo = uint64(i32MaxS(uint32(x1lo), uint32(x2lo))) | + uint64(i32MaxS(uint32(x1lo>>32), uint32(x2lo>>32)))<<32 + retHi = uint64(i32MaxS(uint32(x1hi), uint32(x2hi))) | + uint64(i32MaxS(uint32(x1hi>>32), uint32(x2hi>>32)))<<32 + } else { + retLo = uint64(i32MaxU(uint32(x1lo), uint32(x2lo))) | + uint64(i32MaxU(uint32(x1lo>>32), uint32(x2lo>>32)))<<32 + retHi = uint64(i32MaxU(uint32(x1hi), uint32(x2hi))) | + uint64(i32MaxU(uint32(x1hi>>32), uint32(x2hi>>32)))<<32 + } + case shapeF32x4: + retHi = wasmCompatMax32bits(uint32(x1hi), uint32(x2hi)) | + wasmCompatMax32bits(uint32(x1hi>>32), uint32(x2hi>>32))<<32 + retLo = wasmCompatMax32bits(uint32(x1lo), uint32(x2lo)) | + wasmCompatMax32bits(uint32(x1lo>>32), uint32(x2lo>>32))<<32 + case shapeF64x2: + retHi = math.Float64bits(moremath.WasmCompatMax64( + math.Float64frombits(x1hi), + math.Float64frombits(x2hi), + )) + retLo = math.Float64bits(moremath.WasmCompatMax64( + math.Float64frombits(x1lo), + math.Float64frombits(x2lo), + )) + } + ce.pushValue(retLo) + ce.pushValue(retHi) + frame.pc++ + case operationKindV128AvgrU: + x2hi, x2lo := ce.popValue(), ce.popValue() + x1hi, x1lo := ce.popValue(), ce.popValue() + var retLo, retHi uint64 + switch op.B1 { + case shapeI8x16: + retLo = uint64(i8RoundingAverage(uint8(x1lo>>8), uint8(x2lo>>8)))<<8 | uint64(i8RoundingAverage(uint8(x1lo), uint8(x2lo))) | + uint64(i8RoundingAverage(uint8(x1lo>>24), uint8(x2lo>>24)))<<24 | uint64(i8RoundingAverage(uint8(x1lo>>16), uint8(x2lo>>16)))<<16 | + uint64(i8RoundingAverage(uint8(x1lo>>40), uint8(x2lo>>40)))<<40 | uint64(i8RoundingAverage(uint8(x1lo>>32), uint8(x2lo>>32)))<<32 | + uint64(i8RoundingAverage(uint8(x1lo>>56), uint8(x2lo>>56)))<<56 | uint64(i8RoundingAverage(uint8(x1lo>>48), uint8(x2lo>>48)))<<48 + retHi = uint64(i8RoundingAverage(uint8(x1hi>>8), uint8(x2hi>>8)))<<8 | uint64(i8RoundingAverage(uint8(x1hi), uint8(x2hi))) | + uint64(i8RoundingAverage(uint8(x1hi>>24), uint8(x2hi>>24)))<<24 | uint64(i8RoundingAverage(uint8(x1hi>>16), uint8(x2hi>>16)))<<16 | + uint64(i8RoundingAverage(uint8(x1hi>>40), uint8(x2hi>>40)))<<40 | uint64(i8RoundingAverage(uint8(x1hi>>32), uint8(x2hi>>32)))<<32 | + uint64(i8RoundingAverage(uint8(x1hi>>56), uint8(x2hi>>56)))<<56 | uint64(i8RoundingAverage(uint8(x1hi>>48), uint8(x2hi>>48)))<<48 + case shapeI16x8: + retLo = uint64(i16RoundingAverage(uint16(x1lo), uint16(x2lo))) | + uint64(i16RoundingAverage(uint16(x1lo>>16), uint16(x2lo>>16)))<<16 | + uint64(i16RoundingAverage(uint16(x1lo>>32), uint16(x2lo>>32)))<<32 | + uint64(i16RoundingAverage(uint16(x1lo>>48), uint16(x2lo>>48)))<<48 + retHi = uint64(i16RoundingAverage(uint16(x1hi), uint16(x2hi))) | + uint64(i16RoundingAverage(uint16(x1hi>>16), uint16(x2hi>>16)))<<16 | + uint64(i16RoundingAverage(uint16(x1hi>>32), uint16(x2hi>>32)))<<32 | + uint64(i16RoundingAverage(uint16(x1hi>>48), uint16(x2hi>>48)))<<48 + } + ce.pushValue(retLo) + ce.pushValue(retHi) + frame.pc++ + case operationKindV128Pmin: + x2hi, x2lo := ce.popValue(), ce.popValue() + x1hi, x1lo := ce.popValue(), ce.popValue() + var retLo, retHi uint64 + if op.B1 == shapeF32x4 { + if flt32(math.Float32frombits(uint32(x2lo)), math.Float32frombits(uint32(x1lo))) { + retLo = x2lo & 0x00000000_ffffffff + } else { + retLo = x1lo & 0x00000000_ffffffff + } + if flt32(math.Float32frombits(uint32(x2lo>>32)), math.Float32frombits(uint32(x1lo>>32))) { + retLo |= x2lo & 0xffffffff_00000000 + } else { + retLo |= x1lo & 0xffffffff_00000000 + } + if flt32(math.Float32frombits(uint32(x2hi)), math.Float32frombits(uint32(x1hi))) { + retHi = x2hi & 0x00000000_ffffffff + } else { + retHi = x1hi & 0x00000000_ffffffff + } + if flt32(math.Float32frombits(uint32(x2hi>>32)), math.Float32frombits(uint32(x1hi>>32))) { + retHi |= x2hi & 0xffffffff_00000000 + } else { + retHi |= x1hi & 0xffffffff_00000000 + } + } else { + if flt64(math.Float64frombits(x2lo), math.Float64frombits(x1lo)) { + retLo = x2lo + } else { + retLo = x1lo + } + if flt64(math.Float64frombits(x2hi), math.Float64frombits(x1hi)) { + retHi = x2hi + } else { + retHi = x1hi + } + } + ce.pushValue(retLo) + ce.pushValue(retHi) + frame.pc++ + case operationKindV128Pmax: + x2hi, x2lo := ce.popValue(), ce.popValue() + x1hi, x1lo := ce.popValue(), ce.popValue() + var retLo, retHi uint64 + if op.B1 == shapeF32x4 { + if flt32(math.Float32frombits(uint32(x1lo)), math.Float32frombits(uint32(x2lo))) { + retLo = x2lo & 0x00000000_ffffffff + } else { + retLo = x1lo & 0x00000000_ffffffff + } + if flt32(math.Float32frombits(uint32(x1lo>>32)), math.Float32frombits(uint32(x2lo>>32))) { + retLo |= x2lo & 0xffffffff_00000000 + } else { + retLo |= x1lo & 0xffffffff_00000000 + } + if flt32(math.Float32frombits(uint32(x1hi)), math.Float32frombits(uint32(x2hi))) { + retHi = x2hi & 0x00000000_ffffffff + } else { + retHi = x1hi & 0x00000000_ffffffff + } + if flt32(math.Float32frombits(uint32(x1hi>>32)), math.Float32frombits(uint32(x2hi>>32))) { + retHi |= x2hi & 0xffffffff_00000000 + } else { + retHi |= x1hi & 0xffffffff_00000000 + } + } else { + if flt64(math.Float64frombits(x1lo), math.Float64frombits(x2lo)) { + retLo = x2lo + } else { + retLo = x1lo + } + if flt64(math.Float64frombits(x1hi), math.Float64frombits(x2hi)) { + retHi = x2hi + } else { + retHi = x1hi + } + } + ce.pushValue(retLo) + ce.pushValue(retHi) + frame.pc++ + case operationKindV128Ceil: + hi, lo := ce.popValue(), ce.popValue() + if op.B1 == shapeF32x4 { + lo = uint64(math.Float32bits(moremath.WasmCompatCeilF32(math.Float32frombits(uint32(lo))))) | + (uint64(math.Float32bits(moremath.WasmCompatCeilF32(math.Float32frombits(uint32(lo>>32))))) << 32) + hi = uint64(math.Float32bits(moremath.WasmCompatCeilF32(math.Float32frombits(uint32(hi))))) | + (uint64(math.Float32bits(moremath.WasmCompatCeilF32(math.Float32frombits(uint32(hi>>32))))) << 32) + } else { + lo = math.Float64bits(moremath.WasmCompatCeilF64(math.Float64frombits(lo))) + hi = math.Float64bits(moremath.WasmCompatCeilF64(math.Float64frombits(hi))) + } + ce.pushValue(lo) + ce.pushValue(hi) + frame.pc++ + case operationKindV128Floor: + hi, lo := ce.popValue(), ce.popValue() + if op.B1 == shapeF32x4 { + lo = uint64(math.Float32bits(moremath.WasmCompatFloorF32(math.Float32frombits(uint32(lo))))) | + (uint64(math.Float32bits(moremath.WasmCompatFloorF32(math.Float32frombits(uint32(lo>>32))))) << 32) + hi = uint64(math.Float32bits(moremath.WasmCompatFloorF32(math.Float32frombits(uint32(hi))))) | + (uint64(math.Float32bits(moremath.WasmCompatFloorF32(math.Float32frombits(uint32(hi>>32))))) << 32) + } else { + lo = math.Float64bits(moremath.WasmCompatFloorF64(math.Float64frombits(lo))) + hi = math.Float64bits(moremath.WasmCompatFloorF64(math.Float64frombits(hi))) + } + ce.pushValue(lo) + ce.pushValue(hi) + frame.pc++ + case operationKindV128Trunc: + hi, lo := ce.popValue(), ce.popValue() + if op.B1 == shapeF32x4 { + lo = uint64(math.Float32bits(moremath.WasmCompatTruncF32(math.Float32frombits(uint32(lo))))) | + (uint64(math.Float32bits(moremath.WasmCompatTruncF32(math.Float32frombits(uint32(lo>>32))))) << 32) + hi = uint64(math.Float32bits(moremath.WasmCompatTruncF32(math.Float32frombits(uint32(hi))))) | + (uint64(math.Float32bits(moremath.WasmCompatTruncF32(math.Float32frombits(uint32(hi>>32))))) << 32) + } else { + lo = math.Float64bits(moremath.WasmCompatTruncF64(math.Float64frombits(lo))) + hi = math.Float64bits(moremath.WasmCompatTruncF64(math.Float64frombits(hi))) + } + ce.pushValue(lo) + ce.pushValue(hi) + frame.pc++ + case operationKindV128Nearest: + hi, lo := ce.popValue(), ce.popValue() + if op.B1 == shapeF32x4 { + lo = uint64(math.Float32bits(moremath.WasmCompatNearestF32(math.Float32frombits(uint32(lo))))) | + (uint64(math.Float32bits(moremath.WasmCompatNearestF32(math.Float32frombits(uint32(lo>>32))))) << 32) + hi = uint64(math.Float32bits(moremath.WasmCompatNearestF32(math.Float32frombits(uint32(hi))))) | + (uint64(math.Float32bits(moremath.WasmCompatNearestF32(math.Float32frombits(uint32(hi>>32))))) << 32) + } else { + lo = math.Float64bits(moremath.WasmCompatNearestF64(math.Float64frombits(lo))) + hi = math.Float64bits(moremath.WasmCompatNearestF64(math.Float64frombits(hi))) + } + ce.pushValue(lo) + ce.pushValue(hi) + frame.pc++ + case operationKindV128Extend: + hi, lo := ce.popValue(), ce.popValue() + var origin uint64 + if op.B3 { // use lower 64 bits + origin = lo + } else { + origin = hi + } + + signed := op.B2 == 1 + + var retHi, retLo uint64 + switch op.B1 { + case shapeI8x16: + for i := 0; i < 8; i++ { + v8 := byte(origin >> (i * 8)) + + var v16 uint16 + if signed { + v16 = uint16(int8(v8)) + } else { + v16 = uint16(v8) + } + + if i < 4 { + retLo |= uint64(v16) << (i * 16) + } else { + retHi |= uint64(v16) << ((i - 4) * 16) + } + } + case shapeI16x8: + for i := 0; i < 4; i++ { + v16 := uint16(origin >> (i * 16)) + + var v32 uint32 + if signed { + v32 = uint32(int16(v16)) + } else { + v32 = uint32(v16) + } + + if i < 2 { + retLo |= uint64(v32) << (i * 32) + } else { + retHi |= uint64(v32) << ((i - 2) * 32) + } + } + case shapeI32x4: + v32Lo := uint32(origin) + v32Hi := uint32(origin >> 32) + if signed { + retLo = uint64(int32(v32Lo)) + retHi = uint64(int32(v32Hi)) + } else { + retLo = uint64(v32Lo) + retHi = uint64(v32Hi) + } + } + ce.pushValue(retLo) + ce.pushValue(retHi) + frame.pc++ + case operationKindV128ExtMul: + x2Hi, x2Lo := ce.popValue(), ce.popValue() + x1Hi, x1Lo := ce.popValue(), ce.popValue() + var x1, x2 uint64 + if op.B3 { // use lower 64 bits + x1, x2 = x1Lo, x2Lo + } else { + x1, x2 = x1Hi, x2Hi + } + + signed := op.B2 == 1 + + var retLo, retHi uint64 + switch op.B1 { + case shapeI8x16: + for i := 0; i < 8; i++ { + v1, v2 := byte(x1>>(i*8)), byte(x2>>(i*8)) + + var v16 uint16 + if signed { + v16 = uint16(int16(int8(v1)) * int16(int8(v2))) + } else { + v16 = uint16(v1) * uint16(v2) + } + + if i < 4 { + retLo |= uint64(v16) << (i * 16) + } else { + retHi |= uint64(v16) << ((i - 4) * 16) + } + } + case shapeI16x8: + for i := 0; i < 4; i++ { + v1, v2 := uint16(x1>>(i*16)), uint16(x2>>(i*16)) + + var v32 uint32 + if signed { + v32 = uint32(int32(int16(v1)) * int32(int16(v2))) + } else { + v32 = uint32(v1) * uint32(v2) + } + + if i < 2 { + retLo |= uint64(v32) << (i * 32) + } else { + retHi |= uint64(v32) << ((i - 2) * 32) + } + } + case shapeI32x4: + v1Lo, v2Lo := uint32(x1), uint32(x2) + v1Hi, v2Hi := uint32(x1>>32), uint32(x2>>32) + if signed { + retLo = uint64(int64(int32(v1Lo)) * int64(int32(v2Lo))) + retHi = uint64(int64(int32(v1Hi)) * int64(int32(v2Hi))) + } else { + retLo = uint64(v1Lo) * uint64(v2Lo) + retHi = uint64(v1Hi) * uint64(v2Hi) + } + } + + ce.pushValue(retLo) + ce.pushValue(retHi) + frame.pc++ + case operationKindV128Q15mulrSatS: + x2hi, x2Lo := ce.popValue(), ce.popValue() + x1hi, x1Lo := ce.popValue(), ce.popValue() + var retLo, retHi uint64 + for i := 0; i < 8; i++ { + var v, w int16 + if i < 4 { + v, w = int16(uint16(x1Lo>>(i*16))), int16(uint16(x2Lo>>(i*16))) + } else { + v, w = int16(uint16(x1hi>>((i-4)*16))), int16(uint16(x2hi>>((i-4)*16))) + } + + var uv uint64 + // https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md#saturating-integer-q-format-rounding-multiplication + if calc := ((int32(v) * int32(w)) + 0x4000) >> 15; calc < math.MinInt16 { + uv = uint64(uint16(0x8000)) + } else if calc > math.MaxInt16 { + uv = uint64(uint16(0x7fff)) + } else { + uv = uint64(uint16(int16(calc))) + } + + if i < 4 { + retLo |= uv << (i * 16) + } else { + retHi |= uv << ((i - 4) * 16) + } + } + + ce.pushValue(retLo) + ce.pushValue(retHi) + frame.pc++ + case operationKindV128ExtAddPairwise: + hi, lo := ce.popValue(), ce.popValue() + + signed := op.B3 + + var retLo, retHi uint64 + switch op.B1 { + case shapeI8x16: + for i := 0; i < 8; i++ { + var v1, v2 byte + if i < 4 { + v1, v2 = byte(lo>>((i*2)*8)), byte(lo>>((i*2+1)*8)) + } else { + v1, v2 = byte(hi>>(((i-4)*2)*8)), byte(hi>>(((i-4)*2+1)*8)) + } + + var v16 uint16 + if signed { + v16 = uint16(int16(int8(v1)) + int16(int8(v2))) + } else { + v16 = uint16(v1) + uint16(v2) + } + + if i < 4 { + retLo |= uint64(v16) << (i * 16) + } else { + retHi |= uint64(v16) << ((i - 4) * 16) + } + } + case shapeI16x8: + for i := 0; i < 4; i++ { + var v1, v2 uint16 + if i < 2 { + v1, v2 = uint16(lo>>((i*2)*16)), uint16(lo>>((i*2+1)*16)) + } else { + v1, v2 = uint16(hi>>(((i-2)*2)*16)), uint16(hi>>(((i-2)*2+1)*16)) + } + + var v32 uint32 + if signed { + v32 = uint32(int32(int16(v1)) + int32(int16(v2))) + } else { + v32 = uint32(v1) + uint32(v2) + } + + if i < 2 { + retLo |= uint64(v32) << (i * 32) + } else { + retHi |= uint64(v32) << ((i - 2) * 32) + } + } + } + ce.pushValue(retLo) + ce.pushValue(retHi) + frame.pc++ + case operationKindV128FloatPromote: + _, toPromote := ce.popValue(), ce.popValue() + ce.pushValue(math.Float64bits(float64(math.Float32frombits(uint32(toPromote))))) + ce.pushValue(math.Float64bits(float64(math.Float32frombits(uint32(toPromote >> 32))))) + frame.pc++ + case operationKindV128FloatDemote: + hi, lo := ce.popValue(), ce.popValue() + ce.pushValue( + uint64(math.Float32bits(float32(math.Float64frombits(lo)))) | + (uint64(math.Float32bits(float32(math.Float64frombits(hi)))) << 32), + ) + ce.pushValue(0) + frame.pc++ + case operationKindV128FConvertFromI: + hi, lo := ce.popValue(), ce.popValue() + v1, v2, v3, v4 := uint32(lo), uint32(lo>>32), uint32(hi), uint32(hi>>32) + signed := op.B3 + + var retLo, retHi uint64 + switch op.B1 { // Destination shape. + case shapeF32x4: // f32x4 from signed/unsigned i32x4 + if signed { + retLo = uint64(math.Float32bits(float32(int32(v1)))) | + (uint64(math.Float32bits(float32(int32(v2)))) << 32) + retHi = uint64(math.Float32bits(float32(int32(v3)))) | + (uint64(math.Float32bits(float32(int32(v4)))) << 32) + } else { + retLo = uint64(math.Float32bits(float32(v1))) | + (uint64(math.Float32bits(float32(v2))) << 32) + retHi = uint64(math.Float32bits(float32(v3))) | + (uint64(math.Float32bits(float32(v4))) << 32) + } + case shapeF64x2: // f64x2 from signed/unsigned i32x4 + if signed { + retLo, retHi = math.Float64bits(float64(int32(v1))), math.Float64bits(float64(int32(v2))) + } else { + retLo, retHi = math.Float64bits(float64(v1)), math.Float64bits(float64(v2)) + } + } + + ce.pushValue(retLo) + ce.pushValue(retHi) + frame.pc++ + case operationKindV128Narrow: + x2Hi, x2Lo := ce.popValue(), ce.popValue() + x1Hi, x1Lo := ce.popValue(), ce.popValue() + signed := op.B3 + + var retLo, retHi uint64 + switch op.B1 { + case shapeI16x8: // signed/unsigned i16x8 to i8x16 + for i := 0; i < 8; i++ { + var v16 uint16 + if i < 4 { + v16 = uint16(x1Lo >> (i * 16)) + } else { + v16 = uint16(x1Hi >> ((i - 4) * 16)) + } + + var v byte + if signed { + if s := int16(v16); s > math.MaxInt8 { + v = math.MaxInt8 + } else if s < math.MinInt8 { + s = math.MinInt8 + v = byte(s) + } else { + v = byte(v16) + } + } else { + if s := int16(v16); s > math.MaxUint8 { + v = math.MaxUint8 + } else if s < 0 { + v = 0 + } else { + v = byte(v16) + } + } + retLo |= uint64(v) << (i * 8) + } + for i := 0; i < 8; i++ { + var v16 uint16 + if i < 4 { + v16 = uint16(x2Lo >> (i * 16)) + } else { + v16 = uint16(x2Hi >> ((i - 4) * 16)) + } + + var v byte + if signed { + if s := int16(v16); s > math.MaxInt8 { + v = math.MaxInt8 + } else if s < math.MinInt8 { + s = math.MinInt8 + v = byte(s) + } else { + v = byte(v16) + } + } else { + if s := int16(v16); s > math.MaxUint8 { + v = math.MaxUint8 + } else if s < 0 { + v = 0 + } else { + v = byte(v16) + } + } + retHi |= uint64(v) << (i * 8) + } + case shapeI32x4: // signed/unsigned i32x4 to i16x8 + for i := 0; i < 4; i++ { + var v32 uint32 + if i < 2 { + v32 = uint32(x1Lo >> (i * 32)) + } else { + v32 = uint32(x1Hi >> ((i - 2) * 32)) + } + + var v uint16 + if signed { + if s := int32(v32); s > math.MaxInt16 { + v = math.MaxInt16 + } else if s < math.MinInt16 { + s = math.MinInt16 + v = uint16(s) + } else { + v = uint16(v32) + } + } else { + if s := int32(v32); s > math.MaxUint16 { + v = math.MaxUint16 + } else if s < 0 { + v = 0 + } else { + v = uint16(v32) + } + } + retLo |= uint64(v) << (i * 16) + } + + for i := 0; i < 4; i++ { + var v32 uint32 + if i < 2 { + v32 = uint32(x2Lo >> (i * 32)) + } else { + v32 = uint32(x2Hi >> ((i - 2) * 32)) + } + + var v uint16 + if signed { + if s := int32(v32); s > math.MaxInt16 { + v = math.MaxInt16 + } else if s < math.MinInt16 { + s = math.MinInt16 + v = uint16(s) + } else { + v = uint16(v32) + } + } else { + if s := int32(v32); s > math.MaxUint16 { + v = math.MaxUint16 + } else if s < 0 { + v = 0 + } else { + v = uint16(v32) + } + } + retHi |= uint64(v) << (i * 16) + } + } + ce.pushValue(retLo) + ce.pushValue(retHi) + frame.pc++ + case operationKindV128Dot: + x2Hi, x2Lo := ce.popValue(), ce.popValue() + x1Hi, x1Lo := ce.popValue(), ce.popValue() + lo, hi := v128Dot(x1Hi, x1Lo, x2Hi, x2Lo) + ce.pushValue(lo) + ce.pushValue(hi) + frame.pc++ + case operationKindV128ITruncSatFromF: + hi, lo := ce.popValue(), ce.popValue() + signed := op.B3 + var retLo, retHi uint64 + + switch op.B1 { + case shapeF32x4: // f32x4 to i32x4 + for i, f64 := range [4]float64{ + math.Trunc(float64(math.Float32frombits(uint32(lo)))), + math.Trunc(float64(math.Float32frombits(uint32(lo >> 32)))), + math.Trunc(float64(math.Float32frombits(uint32(hi)))), + math.Trunc(float64(math.Float32frombits(uint32(hi >> 32)))), + } { + + var v uint32 + if math.IsNaN(f64) { + v = 0 + } else if signed { + if f64 < math.MinInt32 { + f64 = math.MinInt32 + } else if f64 > math.MaxInt32 { + f64 = math.MaxInt32 + } + v = uint32(int32(f64)) + } else { + if f64 < 0 { + f64 = 0 + } else if f64 > math.MaxUint32 { + f64 = math.MaxUint32 + } + v = uint32(f64) + } + + if i < 2 { + retLo |= uint64(v) << (i * 32) + } else { + retHi |= uint64(v) << ((i - 2) * 32) + } + } + + case shapeF64x2: // f64x2 to i32x4 + for i, f := range [2]float64{ + math.Trunc(math.Float64frombits(lo)), + math.Trunc(math.Float64frombits(hi)), + } { + var v uint32 + if math.IsNaN(f) { + v = 0 + } else if signed { + if f < math.MinInt32 { + f = math.MinInt32 + } else if f > math.MaxInt32 { + f = math.MaxInt32 + } + v = uint32(int32(f)) + } else { + if f < 0 { + f = 0 + } else if f > math.MaxUint32 { + f = math.MaxUint32 + } + v = uint32(f) + } + + retLo |= uint64(v) << (i * 32) + } + } + + ce.pushValue(retLo) + ce.pushValue(retHi) + frame.pc++ + case operationKindAtomicMemoryWait: + timeout := int64(ce.popValue()) + exp := ce.popValue() + offset := ce.popMemoryOffset(op) + // Runtime instead of validation error because the spec intends to allow binaries to include + // such instructions as long as they are not executed. + if !memoryInst.Shared { + panic(wasmruntime.ErrRuntimeExpectedSharedMemory) + } + + switch unsignedType(op.B1) { + case unsignedTypeI32: + if offset%4 != 0 { + panic(wasmruntime.ErrRuntimeUnalignedAtomic) + } + if int(offset) > len(memoryInst.Buffer)-4 { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + ce.pushValue(memoryInst.Wait32(offset, uint32(exp), timeout, func(mem *wasm.MemoryInstance, offset uint32) uint32 { + mem.Mux.Lock() + defer mem.Mux.Unlock() + value, _ := mem.ReadUint32Le(offset) + return value + })) + case unsignedTypeI64: + if offset%8 != 0 { + panic(wasmruntime.ErrRuntimeUnalignedAtomic) + } + if int(offset) > len(memoryInst.Buffer)-8 { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + ce.pushValue(memoryInst.Wait64(offset, exp, timeout, func(mem *wasm.MemoryInstance, offset uint32) uint64 { + mem.Mux.Lock() + defer mem.Mux.Unlock() + value, _ := mem.ReadUint64Le(offset) + return value + })) + } + frame.pc++ + case operationKindAtomicMemoryNotify: + count := ce.popValue() + offset := ce.popMemoryOffset(op) + if offset%4 != 0 { + panic(wasmruntime.ErrRuntimeUnalignedAtomic) + } + // Just a bounds check + if offset >= memoryInst.Size() { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + res := memoryInst.Notify(offset, uint32(count)) + ce.pushValue(uint64(res)) + frame.pc++ + case operationKindAtomicFence: + // Memory not required for fence only + if memoryInst != nil { + // An empty critical section can be used as a synchronization primitive, which is what + // fence is. Probably, there are no spectests or defined behavior to confirm this yet. + memoryInst.Mux.Lock() + memoryInst.Mux.Unlock() //nolint:staticcheck + } + frame.pc++ + case operationKindAtomicLoad: + offset := ce.popMemoryOffset(op) + switch unsignedType(op.B1) { + case unsignedTypeI32: + if offset%4 != 0 { + panic(wasmruntime.ErrRuntimeUnalignedAtomic) + } + memoryInst.Mux.Lock() + val, ok := memoryInst.ReadUint32Le(offset) + memoryInst.Mux.Unlock() + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + ce.pushValue(uint64(val)) + case unsignedTypeI64: + if offset%8 != 0 { + panic(wasmruntime.ErrRuntimeUnalignedAtomic) + } + memoryInst.Mux.Lock() + val, ok := memoryInst.ReadUint64Le(offset) + memoryInst.Mux.Unlock() + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + ce.pushValue(val) + } + frame.pc++ + case operationKindAtomicLoad8: + offset := ce.popMemoryOffset(op) + memoryInst.Mux.Lock() + val, ok := memoryInst.ReadByte(offset) + memoryInst.Mux.Unlock() + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + ce.pushValue(uint64(val)) + frame.pc++ + case operationKindAtomicLoad16: + offset := ce.popMemoryOffset(op) + if offset%2 != 0 { + panic(wasmruntime.ErrRuntimeUnalignedAtomic) + } + memoryInst.Mux.Lock() + val, ok := memoryInst.ReadUint16Le(offset) + memoryInst.Mux.Unlock() + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + ce.pushValue(uint64(val)) + frame.pc++ + case operationKindAtomicStore: + val := ce.popValue() + offset := ce.popMemoryOffset(op) + switch unsignedType(op.B1) { + case unsignedTypeI32: + if offset%4 != 0 { + panic(wasmruntime.ErrRuntimeUnalignedAtomic) + } + memoryInst.Mux.Lock() + ok := memoryInst.WriteUint32Le(offset, uint32(val)) + memoryInst.Mux.Unlock() + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + case unsignedTypeI64: + if offset%8 != 0 { + panic(wasmruntime.ErrRuntimeUnalignedAtomic) + } + memoryInst.Mux.Lock() + ok := memoryInst.WriteUint64Le(offset, val) + memoryInst.Mux.Unlock() + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + } + frame.pc++ + case operationKindAtomicStore8: + val := byte(ce.popValue()) + offset := ce.popMemoryOffset(op) + memoryInst.Mux.Lock() + ok := memoryInst.WriteByte(offset, val) + memoryInst.Mux.Unlock() + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + frame.pc++ + case operationKindAtomicStore16: + val := uint16(ce.popValue()) + offset := ce.popMemoryOffset(op) + if offset%2 != 0 { + panic(wasmruntime.ErrRuntimeUnalignedAtomic) + } + memoryInst.Mux.Lock() + ok := memoryInst.WriteUint16Le(offset, val) + memoryInst.Mux.Unlock() + if !ok { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + frame.pc++ + case operationKindAtomicRMW: + val := ce.popValue() + offset := ce.popMemoryOffset(op) + switch unsignedType(op.B1) { + case unsignedTypeI32: + if offset%4 != 0 { + panic(wasmruntime.ErrRuntimeUnalignedAtomic) + } + memoryInst.Mux.Lock() + old, ok := memoryInst.ReadUint32Le(offset) + if !ok { + memoryInst.Mux.Unlock() + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + var newVal uint32 + switch atomicArithmeticOp(op.B2) { + case atomicArithmeticOpAdd: + newVal = old + uint32(val) + case atomicArithmeticOpSub: + newVal = old - uint32(val) + case atomicArithmeticOpAnd: + newVal = old & uint32(val) + case atomicArithmeticOpOr: + newVal = old | uint32(val) + case atomicArithmeticOpXor: + newVal = old ^ uint32(val) + case atomicArithmeticOpNop: + newVal = uint32(val) + } + memoryInst.WriteUint32Le(offset, newVal) + memoryInst.Mux.Unlock() + ce.pushValue(uint64(old)) + case unsignedTypeI64: + if offset%8 != 0 { + panic(wasmruntime.ErrRuntimeUnalignedAtomic) + } + memoryInst.Mux.Lock() + old, ok := memoryInst.ReadUint64Le(offset) + if !ok { + memoryInst.Mux.Unlock() + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + var newVal uint64 + switch atomicArithmeticOp(op.B2) { + case atomicArithmeticOpAdd: + newVal = old + val + case atomicArithmeticOpSub: + newVal = old - val + case atomicArithmeticOpAnd: + newVal = old & val + case atomicArithmeticOpOr: + newVal = old | val + case atomicArithmeticOpXor: + newVal = old ^ val + case atomicArithmeticOpNop: + newVal = val + } + memoryInst.WriteUint64Le(offset, newVal) + memoryInst.Mux.Unlock() + ce.pushValue(old) + } + frame.pc++ + case operationKindAtomicRMW8: + val := ce.popValue() + offset := ce.popMemoryOffset(op) + memoryInst.Mux.Lock() + old, ok := memoryInst.ReadByte(offset) + if !ok { + memoryInst.Mux.Unlock() + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + arg := byte(val) + var newVal byte + switch atomicArithmeticOp(op.B2) { + case atomicArithmeticOpAdd: + newVal = old + arg + case atomicArithmeticOpSub: + newVal = old - arg + case atomicArithmeticOpAnd: + newVal = old & arg + case atomicArithmeticOpOr: + newVal = old | arg + case atomicArithmeticOpXor: + newVal = old ^ arg + case atomicArithmeticOpNop: + newVal = arg + } + memoryInst.WriteByte(offset, newVal) + memoryInst.Mux.Unlock() + ce.pushValue(uint64(old)) + frame.pc++ + case operationKindAtomicRMW16: + val := ce.popValue() + offset := ce.popMemoryOffset(op) + if offset%2 != 0 { + panic(wasmruntime.ErrRuntimeUnalignedAtomic) + } + memoryInst.Mux.Lock() + old, ok := memoryInst.ReadUint16Le(offset) + if !ok { + memoryInst.Mux.Unlock() + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + arg := uint16(val) + var newVal uint16 + switch atomicArithmeticOp(op.B2) { + case atomicArithmeticOpAdd: + newVal = old + arg + case atomicArithmeticOpSub: + newVal = old - arg + case atomicArithmeticOpAnd: + newVal = old & arg + case atomicArithmeticOpOr: + newVal = old | arg + case atomicArithmeticOpXor: + newVal = old ^ arg + case atomicArithmeticOpNop: + newVal = arg + } + memoryInst.WriteUint16Le(offset, newVal) + memoryInst.Mux.Unlock() + ce.pushValue(uint64(old)) + frame.pc++ + case operationKindAtomicRMWCmpxchg: + rep := ce.popValue() + exp := ce.popValue() + offset := ce.popMemoryOffset(op) + switch unsignedType(op.B1) { + case unsignedTypeI32: + if offset%4 != 0 { + panic(wasmruntime.ErrRuntimeUnalignedAtomic) + } + memoryInst.Mux.Lock() + old, ok := memoryInst.ReadUint32Le(offset) + if !ok { + memoryInst.Mux.Unlock() + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + if old == uint32(exp) { + memoryInst.WriteUint32Le(offset, uint32(rep)) + } + memoryInst.Mux.Unlock() + ce.pushValue(uint64(old)) + case unsignedTypeI64: + if offset%8 != 0 { + panic(wasmruntime.ErrRuntimeUnalignedAtomic) + } + memoryInst.Mux.Lock() + old, ok := memoryInst.ReadUint64Le(offset) + if !ok { + memoryInst.Mux.Unlock() + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + if old == exp { + memoryInst.WriteUint64Le(offset, rep) + } + memoryInst.Mux.Unlock() + ce.pushValue(old) + } + frame.pc++ + case operationKindAtomicRMW8Cmpxchg: + rep := byte(ce.popValue()) + exp := byte(ce.popValue()) + offset := ce.popMemoryOffset(op) + memoryInst.Mux.Lock() + old, ok := memoryInst.ReadByte(offset) + if !ok { + memoryInst.Mux.Unlock() + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + if old == exp { + memoryInst.WriteByte(offset, rep) + } + memoryInst.Mux.Unlock() + ce.pushValue(uint64(old)) + frame.pc++ + case operationKindAtomicRMW16Cmpxchg: + rep := uint16(ce.popValue()) + exp := uint16(ce.popValue()) + offset := ce.popMemoryOffset(op) + if offset%2 != 0 { + panic(wasmruntime.ErrRuntimeUnalignedAtomic) + } + memoryInst.Mux.Lock() + old, ok := memoryInst.ReadUint16Le(offset) + if !ok { + memoryInst.Mux.Unlock() + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + if old == exp { + memoryInst.WriteUint16Le(offset, rep) + } + memoryInst.Mux.Unlock() + ce.pushValue(uint64(old)) + frame.pc++ + case operationKindTailCallReturnCall: + f := &functions[op.U1] + ce.dropForTailCall(frame, f) + body, bodyLen = ce.resetPc(frame, f) + + case operationKindTailCallReturnCallIndirect: + offset := ce.popValue() + table := tables[op.U2] + tf := ce.functionForOffset(table, offset, typeIDs[op.U1]) + + // We are allowing proper tail calls only across functions that belong to the same + // module; for indirect calls, we have to enforce it at run-time. + // For details, see internal/engine/RATIONALE.md + if tf.moduleInstance != f.moduleInstance { + // Revert to a normal call. + ce.callFunction(ctx, f.moduleInstance, tf) + // Return + ce.drop(op.Us[0]) + // Jump to the function frame (return) + frame.pc = op.Us[1] + continue + } + + ce.dropForTailCall(frame, tf) + body, bodyLen = ce.resetPc(frame, tf) + + default: + frame.pc++ + } + } + ce.popFrame() +} + +func (ce *callEngine) dropForTailCall(frame *callFrame, f *function) { + base := frame.base - frame.f.funcType.ParamNumInUint64 + paramCount := f.funcType.ParamNumInUint64 + ce.stack = append(ce.stack[:base], ce.stack[len(ce.stack)-paramCount:]...) +} + +func (ce *callEngine) resetPc(frame *callFrame, f *function) (body []unionOperation, bodyLen uint64) { + // The compiler is currently allowing proper tail call only across functions + // that belong to the same module; thus, we can overwrite the frame in-place. + // For details, see internal/engine/RATIONALE.md + frame.f = f + frame.base = len(ce.stack) + frame.pc = 0 + body = frame.f.parent.body + bodyLen = uint64(len(body)) + return body, bodyLen +} + +func (ce *callEngine) functionForOffset(table *wasm.TableInstance, offset uint64, expectedTypeID wasm.FunctionTypeID) *function { + if offset >= uint64(len(table.References)) { + panic(wasmruntime.ErrRuntimeInvalidTableAccess) + } + rawPtr := table.References[offset] + if rawPtr == 0 { + panic(wasmruntime.ErrRuntimeInvalidTableAccess) + } + + tf := functionFromUintptr(rawPtr) + if tf.typeID != expectedTypeID { + panic(wasmruntime.ErrRuntimeIndirectCallTypeMismatch) + } + return tf +} + +func wasmCompatMax32bits(v1, v2 uint32) uint64 { + return uint64(math.Float32bits(moremath.WasmCompatMax32( + math.Float32frombits(v1), + math.Float32frombits(v2), + ))) +} + +func wasmCompatMin32bits(v1, v2 uint32) uint64 { + return uint64(math.Float32bits(moremath.WasmCompatMin32( + math.Float32frombits(v1), + math.Float32frombits(v2), + ))) +} + +func addFloat32bits(v1, v2 uint32) uint64 { + return uint64(math.Float32bits(math.Float32frombits(v1) + math.Float32frombits(v2))) +} + +func subFloat32bits(v1, v2 uint32) uint64 { + return uint64(math.Float32bits(math.Float32frombits(v1) - math.Float32frombits(v2))) +} + +func mulFloat32bits(v1, v2 uint32) uint64 { + return uint64(math.Float32bits(math.Float32frombits(v1) * math.Float32frombits(v2))) +} + +func divFloat32bits(v1, v2 uint32) uint64 { + return uint64(math.Float32bits(math.Float32frombits(v1) / math.Float32frombits(v2))) +} + +// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#xref-exec-numerics-op-flt-mathrm-flt-n-z-1-z-2 +func flt32(z1, z2 float32) bool { + if z1 != z1 || z2 != z2 { + return false + } else if z1 == z2 { + return false + } else if math.IsInf(float64(z1), 1) { + return false + } else if math.IsInf(float64(z1), -1) { + return true + } else if math.IsInf(float64(z2), 1) { + return true + } else if math.IsInf(float64(z2), -1) { + return false + } + return z1 < z2 +} + +// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#xref-exec-numerics-op-flt-mathrm-flt-n-z-1-z-2 +func flt64(z1, z2 float64) bool { + if z1 != z1 || z2 != z2 { + return false + } else if z1 == z2 { + return false + } else if math.IsInf(z1, 1) { + return false + } else if math.IsInf(z1, -1) { + return true + } else if math.IsInf(z2, 1) { + return true + } else if math.IsInf(z2, -1) { + return false + } + return z1 < z2 +} + +func i8RoundingAverage(v1, v2 byte) byte { + // https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md#lane-wise-integer-rounding-average + return byte((uint16(v1) + uint16(v2) + uint16(1)) / 2) +} + +func i16RoundingAverage(v1, v2 uint16) uint16 { + // https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md#lane-wise-integer-rounding-average + return uint16((uint32(v1) + uint32(v2) + 1) / 2) +} + +func i8Abs(v byte) byte { + if i := int8(v); i < 0 { + return byte(-i) + } else { + return byte(i) + } +} + +func i8MaxU(v1, v2 byte) byte { + if v1 < v2 { + return v2 + } else { + return v1 + } +} + +func i8MinU(v1, v2 byte) byte { + if v1 > v2 { + return v2 + } else { + return v1 + } +} + +func i8MaxS(v1, v2 byte) byte { + if int8(v1) < int8(v2) { + return v2 + } else { + return v1 + } +} + +func i8MinS(v1, v2 byte) byte { + if int8(v1) > int8(v2) { + return v2 + } else { + return v1 + } +} + +func i16MaxU(v1, v2 uint16) uint16 { + if v1 < v2 { + return v2 + } else { + return v1 + } +} + +func i16MinU(v1, v2 uint16) uint16 { + if v1 > v2 { + return v2 + } else { + return v1 + } +} + +func i16MaxS(v1, v2 uint16) uint16 { + if int16(v1) < int16(v2) { + return v2 + } else { + return v1 + } +} + +func i16MinS(v1, v2 uint16) uint16 { + if int16(v1) > int16(v2) { + return v2 + } else { + return v1 + } +} + +func i32MaxU(v1, v2 uint32) uint32 { + if v1 < v2 { + return v2 + } else { + return v1 + } +} + +func i32MinU(v1, v2 uint32) uint32 { + if v1 > v2 { + return v2 + } else { + return v1 + } +} + +func i32MaxS(v1, v2 uint32) uint32 { + if int32(v1) < int32(v2) { + return v2 + } else { + return v1 + } +} + +func i32MinS(v1, v2 uint32) uint32 { + if int32(v1) > int32(v2) { + return v2 + } else { + return v1 + } +} + +func i16Abs(v uint16) uint16 { + if i := int16(v); i < 0 { + return uint16(-i) + } else { + return uint16(i) + } +} + +func i32Abs(v uint32) uint32 { + if i := int32(v); i < 0 { + return uint32(-i) + } else { + return uint32(i) + } +} + +func (ce *callEngine) callNativeFuncWithListener(ctx context.Context, m *wasm.ModuleInstance, f *function, fnl experimental.FunctionListener) context.Context { + def, typ := f.definition(), f.funcType + + ce.stackIterator.reset(ce.stack, ce.frames, f) + fnl.Before(ctx, m, def, ce.peekValues(typ.ParamNumInUint64), &ce.stackIterator) + ce.stackIterator.clear() + ce.callNativeFunc(ctx, m, f) + fnl.After(ctx, m, def, ce.peekValues(typ.ResultNumInUint64)) + return ctx +} + +// popMemoryOffset takes a memory offset off the stack for use in load and store instructions. +// As the top of stack value is 64-bit, this ensures it is in range before returning it. +func (ce *callEngine) popMemoryOffset(op *unionOperation) uint32 { + offset := op.U2 + ce.popValue() + if offset > math.MaxUint32 { + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + } + return uint32(offset) +} + +func (ce *callEngine) callGoFuncWithStack(ctx context.Context, m *wasm.ModuleInstance, f *function) { + typ := f.funcType + paramLen := typ.ParamNumInUint64 + resultLen := typ.ResultNumInUint64 + stackLen := paramLen + + // In the interpreter engine, ce.stack may only have capacity to store + // parameters. Grow when there are more results than parameters. + if growLen := resultLen - paramLen; growLen > 0 { + ce.stack = append(ce.stack, make([]uint64, growLen)...) + stackLen += growLen + } + + // Pass the stack elements to the go function. + stack := ce.stack[len(ce.stack)-stackLen:] + ce.callGoFunc(ctx, m, f, stack) + + // Shrink the stack when there were more parameters than results. + if shrinkLen := paramLen - resultLen; shrinkLen > 0 { + ce.stack = ce.stack[0 : len(ce.stack)-shrinkLen] + } +} + +// v128Dot performs a dot product of two 64-bit vectors. +// Note: for some reason (which I suspect is due to a bug in Go compiler's regalloc), +// inlining this function causes a bug which happens **only when** we run with -race AND arm64 AND Go 1.22. +func v128Dot(x1Hi, x1Lo, x2Hi, x2Lo uint64) (uint64, uint64) { + r1 := int32(int16(x1Lo>>0)) * int32(int16(x2Lo>>0)) + r2 := int32(int16(x1Lo>>16)) * int32(int16(x2Lo>>16)) + r3 := int32(int16(x1Lo>>32)) * int32(int16(x2Lo>>32)) + r4 := int32(int16(x1Lo>>48)) * int32(int16(x2Lo>>48)) + r5 := int32(int16(x1Hi>>0)) * int32(int16(x2Hi>>0)) + r6 := int32(int16(x1Hi>>16)) * int32(int16(x2Hi>>16)) + r7 := int32(int16(x1Hi>>32)) * int32(int16(x2Hi>>32)) + r8 := int32(int16(x1Hi>>48)) * int32(int16(x2Hi>>48)) + return uint64(uint32(r1+r2)) | (uint64(uint32(r3+r4)) << 32), uint64(uint32(r5+r6)) | (uint64(uint32(r7+r8)) << 32) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/interpreter/operations.go b/vendor/github.com/tetratelabs/wazero/internal/engine/interpreter/operations.go new file mode 100644 index 00000000000..db3cfa250f6 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/interpreter/operations.go @@ -0,0 +1,2845 @@ +package interpreter + +import ( + "fmt" + "math" + "strings" +) + +// unsignedInt represents unsigned 32-bit or 64-bit integers. +type unsignedInt byte + +const ( + unsignedInt32 unsignedInt = iota + unsignedInt64 +) + +// String implements fmt.Stringer. +func (s unsignedInt) String() (ret string) { + switch s { + case unsignedInt32: + ret = "i32" + case unsignedInt64: + ret = "i64" + } + return +} + +// signedInt represents signed or unsigned integers. +type signedInt byte + +const ( + signedInt32 signedInt = iota + signedInt64 + signedUint32 + signedUint64 +) + +// String implements fmt.Stringer. +func (s signedInt) String() (ret string) { + switch s { + case signedUint32: + ret = "u32" + case signedUint64: + ret = "u64" + case signedInt32: + ret = "s32" + case signedInt64: + ret = "s64" + } + return +} + +// float represents the scalar double or single precision floating points. +type float byte + +const ( + f32 float = iota + f64 +) + +// String implements fmt.Stringer. +func (s float) String() (ret string) { + switch s { + case f32: + ret = "f32" + case f64: + ret = "f64" + } + return +} + +// unsignedType is the union of unsignedInt, float and V128 vector type. +type unsignedType byte + +const ( + unsignedTypeI32 unsignedType = iota + unsignedTypeI64 + unsignedTypeF32 + unsignedTypeF64 + unsignedTypeV128 + unsignedTypeUnknown +) + +// String implements fmt.Stringer. +func (s unsignedType) String() (ret string) { + switch s { + case unsignedTypeI32: + ret = "i32" + case unsignedTypeI64: + ret = "i64" + case unsignedTypeF32: + ret = "f32" + case unsignedTypeF64: + ret = "f64" + case unsignedTypeV128: + ret = "v128" + case unsignedTypeUnknown: + ret = "unknown" + } + return +} + +// signedType is the union of signedInt and float types. +type signedType byte + +const ( + signedTypeInt32 signedType = iota + signedTypeUint32 + signedTypeInt64 + signedTypeUint64 + signedTypeFloat32 + signedTypeFloat64 +) + +// String implements fmt.Stringer. +func (s signedType) String() (ret string) { + switch s { + case signedTypeInt32: + ret = "s32" + case signedTypeUint32: + ret = "u32" + case signedTypeInt64: + ret = "s64" + case signedTypeUint64: + ret = "u64" + case signedTypeFloat32: + ret = "f32" + case signedTypeFloat64: + ret = "f64" + } + return +} + +// operationKind is the Kind of each implementation of Operation interface. +type operationKind uint16 + +// String implements fmt.Stringer. +func (o operationKind) String() (ret string) { + switch o { + case operationKindUnreachable: + ret = "Unreachable" + case operationKindLabel: + ret = "label" + case operationKindBr: + ret = "Br" + case operationKindBrIf: + ret = "BrIf" + case operationKindBrTable: + ret = "BrTable" + case operationKindCall: + ret = "Call" + case operationKindCallIndirect: + ret = "CallIndirect" + case operationKindDrop: + ret = "Drop" + case operationKindSelect: + ret = "Select" + case operationKindPick: + ret = "Pick" + case operationKindSet: + ret = "Swap" + case operationKindGlobalGet: + ret = "GlobalGet" + case operationKindGlobalSet: + ret = "GlobalSet" + case operationKindLoad: + ret = "Load" + case operationKindLoad8: + ret = "Load8" + case operationKindLoad16: + ret = "Load16" + case operationKindLoad32: + ret = "Load32" + case operationKindStore: + ret = "Store" + case operationKindStore8: + ret = "Store8" + case operationKindStore16: + ret = "Store16" + case operationKindStore32: + ret = "Store32" + case operationKindMemorySize: + ret = "MemorySize" + case operationKindMemoryGrow: + ret = "MemoryGrow" + case operationKindConstI32: + ret = "ConstI32" + case operationKindConstI64: + ret = "ConstI64" + case operationKindConstF32: + ret = "ConstF32" + case operationKindConstF64: + ret = "ConstF64" + case operationKindEq: + ret = "Eq" + case operationKindNe: + ret = "Ne" + case operationKindEqz: + ret = "Eqz" + case operationKindLt: + ret = "Lt" + case operationKindGt: + ret = "Gt" + case operationKindLe: + ret = "Le" + case operationKindGe: + ret = "Ge" + case operationKindAdd: + ret = "Add" + case operationKindSub: + ret = "Sub" + case operationKindMul: + ret = "Mul" + case operationKindClz: + ret = "Clz" + case operationKindCtz: + ret = "Ctz" + case operationKindPopcnt: + ret = "Popcnt" + case operationKindDiv: + ret = "Div" + case operationKindRem: + ret = "Rem" + case operationKindAnd: + ret = "And" + case operationKindOr: + ret = "Or" + case operationKindXor: + ret = "Xor" + case operationKindShl: + ret = "Shl" + case operationKindShr: + ret = "Shr" + case operationKindRotl: + ret = "Rotl" + case operationKindRotr: + ret = "Rotr" + case operationKindAbs: + ret = "Abs" + case operationKindNeg: + ret = "Neg" + case operationKindCeil: + ret = "Ceil" + case operationKindFloor: + ret = "Floor" + case operationKindTrunc: + ret = "Trunc" + case operationKindNearest: + ret = "Nearest" + case operationKindSqrt: + ret = "Sqrt" + case operationKindMin: + ret = "Min" + case operationKindMax: + ret = "Max" + case operationKindCopysign: + ret = "Copysign" + case operationKindI32WrapFromI64: + ret = "I32WrapFromI64" + case operationKindITruncFromF: + ret = "ITruncFromF" + case operationKindFConvertFromI: + ret = "FConvertFromI" + case operationKindF32DemoteFromF64: + ret = "F32DemoteFromF64" + case operationKindF64PromoteFromF32: + ret = "F64PromoteFromF32" + case operationKindI32ReinterpretFromF32: + ret = "I32ReinterpretFromF32" + case operationKindI64ReinterpretFromF64: + ret = "I64ReinterpretFromF64" + case operationKindF32ReinterpretFromI32: + ret = "F32ReinterpretFromI32" + case operationKindF64ReinterpretFromI64: + ret = "F64ReinterpretFromI64" + case operationKindExtend: + ret = "Extend" + case operationKindMemoryInit: + ret = "MemoryInit" + case operationKindDataDrop: + ret = "DataDrop" + case operationKindMemoryCopy: + ret = "MemoryCopy" + case operationKindMemoryFill: + ret = "MemoryFill" + case operationKindTableInit: + ret = "TableInit" + case operationKindElemDrop: + ret = "ElemDrop" + case operationKindTableCopy: + ret = "TableCopy" + case operationKindRefFunc: + ret = "RefFunc" + case operationKindTableGet: + ret = "TableGet" + case operationKindTableSet: + ret = "TableSet" + case operationKindTableSize: + ret = "TableSize" + case operationKindTableGrow: + ret = "TableGrow" + case operationKindTableFill: + ret = "TableFill" + case operationKindV128Const: + ret = "ConstV128" + case operationKindV128Add: + ret = "V128Add" + case operationKindV128Sub: + ret = "V128Sub" + case operationKindV128Load: + ret = "V128Load" + case operationKindV128LoadLane: + ret = "V128LoadLane" + case operationKindV128Store: + ret = "V128Store" + case operationKindV128StoreLane: + ret = "V128StoreLane" + case operationKindV128ExtractLane: + ret = "V128ExtractLane" + case operationKindV128ReplaceLane: + ret = "V128ReplaceLane" + case operationKindV128Splat: + ret = "V128Splat" + case operationKindV128Shuffle: + ret = "V128Shuffle" + case operationKindV128Swizzle: + ret = "V128Swizzle" + case operationKindV128AnyTrue: + ret = "V128AnyTrue" + case operationKindV128AllTrue: + ret = "V128AllTrue" + case operationKindV128And: + ret = "V128And" + case operationKindV128Not: + ret = "V128Not" + case operationKindV128Or: + ret = "V128Or" + case operationKindV128Xor: + ret = "V128Xor" + case operationKindV128Bitselect: + ret = "V128Bitselect" + case operationKindV128AndNot: + ret = "V128AndNot" + case operationKindV128BitMask: + ret = "V128BitMask" + case operationKindV128Shl: + ret = "V128Shl" + case operationKindV128Shr: + ret = "V128Shr" + case operationKindV128Cmp: + ret = "V128Cmp" + case operationKindSignExtend32From8: + ret = "SignExtend32From8" + case operationKindSignExtend32From16: + ret = "SignExtend32From16" + case operationKindSignExtend64From8: + ret = "SignExtend64From8" + case operationKindSignExtend64From16: + ret = "SignExtend64From16" + case operationKindSignExtend64From32: + ret = "SignExtend64From32" + case operationKindV128AddSat: + ret = "V128AddSat" + case operationKindV128SubSat: + ret = "V128SubSat" + case operationKindV128Mul: + ret = "V128Mul" + case operationKindV128Div: + ret = "V128Div" + case operationKindV128Neg: + ret = "V128Neg" + case operationKindV128Sqrt: + ret = "V128Sqrt" + case operationKindV128Abs: + ret = "V128Abs" + case operationKindV128Popcnt: + ret = "V128Popcnt" + case operationKindV128Min: + ret = "V128Min" + case operationKindV128Max: + ret = "V128Max" + case operationKindV128AvgrU: + ret = "V128AvgrU" + case operationKindV128Ceil: + ret = "V128Ceil" + case operationKindV128Floor: + ret = "V128Floor" + case operationKindV128Trunc: + ret = "V128Trunc" + case operationKindV128Nearest: + ret = "V128Nearest" + case operationKindV128Pmin: + ret = "V128Pmin" + case operationKindV128Pmax: + ret = "V128Pmax" + case operationKindV128Extend: + ret = "V128Extend" + case operationKindV128ExtMul: + ret = "V128ExtMul" + case operationKindV128Q15mulrSatS: + ret = "V128Q15mulrSatS" + case operationKindV128ExtAddPairwise: + ret = "V128ExtAddPairwise" + case operationKindV128FloatPromote: + ret = "V128FloatPromote" + case operationKindV128FloatDemote: + ret = "V128FloatDemote" + case operationKindV128FConvertFromI: + ret = "V128FConvertFromI" + case operationKindV128Dot: + ret = "V128Dot" + case operationKindV128Narrow: + ret = "V128Narrow" + case operationKindV128ITruncSatFromF: + ret = "V128ITruncSatFromF" + case operationKindBuiltinFunctionCheckExitCode: + ret = "BuiltinFunctionCheckExitCode" + case operationKindAtomicMemoryWait: + ret = "operationKindAtomicMemoryWait" + case operationKindAtomicMemoryNotify: + ret = "operationKindAtomicMemoryNotify" + case operationKindAtomicFence: + ret = "operationKindAtomicFence" + case operationKindAtomicLoad: + ret = "operationKindAtomicLoad" + case operationKindAtomicLoad8: + ret = "operationKindAtomicLoad8" + case operationKindAtomicLoad16: + ret = "operationKindAtomicLoad16" + case operationKindAtomicStore: + ret = "operationKindAtomicStore" + case operationKindAtomicStore8: + ret = "operationKindAtomicStore8" + case operationKindAtomicStore16: + ret = "operationKindAtomicStore16" + case operationKindAtomicRMW: + ret = "operationKindAtomicRMW" + case operationKindAtomicRMW8: + ret = "operationKindAtomicRMW8" + case operationKindAtomicRMW16: + ret = "operationKindAtomicRMW16" + case operationKindAtomicRMWCmpxchg: + ret = "operationKindAtomicRMWCmpxchg" + case operationKindAtomicRMW8Cmpxchg: + ret = "operationKindAtomicRMW8Cmpxchg" + case operationKindAtomicRMW16Cmpxchg: + ret = "operationKindAtomicRMW16Cmpxchg" + case operationKindTailCallReturnCall: + ret = "operationKindTailCallReturnCall" + case operationKindTailCallReturnCallIndirect: + ret = "operationKindTailCallReturnCallIndirect" + default: + panic(fmt.Errorf("unknown operation %d", o)) + } + return +} + +const ( + // operationKindUnreachable is the Kind for NewOperationUnreachable. + operationKindUnreachable operationKind = iota + // operationKindLabel is the Kind for NewOperationLabel. + operationKindLabel + // operationKindBr is the Kind for NewOperationBr. + operationKindBr + // operationKindBrIf is the Kind for NewOperationBrIf. + operationKindBrIf + // operationKindBrTable is the Kind for NewOperationBrTable. + operationKindBrTable + // operationKindCall is the Kind for NewOperationCall. + operationKindCall + // operationKindCallIndirect is the Kind for NewOperationCallIndirect. + operationKindCallIndirect + // operationKindDrop is the Kind for NewOperationDrop. + operationKindDrop + // operationKindSelect is the Kind for NewOperationSelect. + operationKindSelect + // operationKindPick is the Kind for NewOperationPick. + operationKindPick + // operationKindSet is the Kind for NewOperationSet. + operationKindSet + // operationKindGlobalGet is the Kind for NewOperationGlobalGet. + operationKindGlobalGet + // operationKindGlobalSet is the Kind for NewOperationGlobalSet. + operationKindGlobalSet + // operationKindLoad is the Kind for NewOperationLoad. + operationKindLoad + // operationKindLoad8 is the Kind for NewOperationLoad8. + operationKindLoad8 + // operationKindLoad16 is the Kind for NewOperationLoad16. + operationKindLoad16 + // operationKindLoad32 is the Kind for NewOperationLoad32. + operationKindLoad32 + // operationKindStore is the Kind for NewOperationStore. + operationKindStore + // operationKindStore8 is the Kind for NewOperationStore8. + operationKindStore8 + // operationKindStore16 is the Kind for NewOperationStore16. + operationKindStore16 + // operationKindStore32 is the Kind for NewOperationStore32. + operationKindStore32 + // operationKindMemorySize is the Kind for NewOperationMemorySize. + operationKindMemorySize + // operationKindMemoryGrow is the Kind for NewOperationMemoryGrow. + operationKindMemoryGrow + // operationKindConstI32 is the Kind for NewOperationConstI32. + operationKindConstI32 + // operationKindConstI64 is the Kind for NewOperationConstI64. + operationKindConstI64 + // operationKindConstF32 is the Kind for NewOperationConstF32. + operationKindConstF32 + // operationKindConstF64 is the Kind for NewOperationConstF64. + operationKindConstF64 + // operationKindEq is the Kind for NewOperationEq. + operationKindEq + // operationKindNe is the Kind for NewOperationNe. + operationKindNe + // operationKindEqz is the Kind for NewOperationEqz. + operationKindEqz + // operationKindLt is the Kind for NewOperationLt. + operationKindLt + // operationKindGt is the Kind for NewOperationGt. + operationKindGt + // operationKindLe is the Kind for NewOperationLe. + operationKindLe + // operationKindGe is the Kind for NewOperationGe. + operationKindGe + // operationKindAdd is the Kind for NewOperationAdd. + operationKindAdd + // operationKindSub is the Kind for NewOperationSub. + operationKindSub + // operationKindMul is the Kind for NewOperationMul. + operationKindMul + // operationKindClz is the Kind for NewOperationClz. + operationKindClz + // operationKindCtz is the Kind for NewOperationCtz. + operationKindCtz + // operationKindPopcnt is the Kind for NewOperationPopcnt. + operationKindPopcnt + // operationKindDiv is the Kind for NewOperationDiv. + operationKindDiv + // operationKindRem is the Kind for NewOperationRem. + operationKindRem + // operationKindAnd is the Kind for NewOperationAnd. + operationKindAnd + // operationKindOr is the Kind for NewOperationOr. + operationKindOr + // operationKindXor is the Kind for NewOperationXor. + operationKindXor + // operationKindShl is the Kind for NewOperationShl. + operationKindShl + // operationKindShr is the Kind for NewOperationShr. + operationKindShr + // operationKindRotl is the Kind for NewOperationRotl. + operationKindRotl + // operationKindRotr is the Kind for NewOperationRotr. + operationKindRotr + // operationKindAbs is the Kind for NewOperationAbs. + operationKindAbs + // operationKindNeg is the Kind for NewOperationNeg. + operationKindNeg + // operationKindCeil is the Kind for NewOperationCeil. + operationKindCeil + // operationKindFloor is the Kind for NewOperationFloor. + operationKindFloor + // operationKindTrunc is the Kind for NewOperationTrunc. + operationKindTrunc + // operationKindNearest is the Kind for NewOperationNearest. + operationKindNearest + // operationKindSqrt is the Kind for NewOperationSqrt. + operationKindSqrt + // operationKindMin is the Kind for NewOperationMin. + operationKindMin + // operationKindMax is the Kind for NewOperationMax. + operationKindMax + // operationKindCopysign is the Kind for NewOperationCopysign. + operationKindCopysign + // operationKindI32WrapFromI64 is the Kind for NewOperationI32WrapFromI64. + operationKindI32WrapFromI64 + // operationKindITruncFromF is the Kind for NewOperationITruncFromF. + operationKindITruncFromF + // operationKindFConvertFromI is the Kind for NewOperationFConvertFromI. + operationKindFConvertFromI + // operationKindF32DemoteFromF64 is the Kind for NewOperationF32DemoteFromF64. + operationKindF32DemoteFromF64 + // operationKindF64PromoteFromF32 is the Kind for NewOperationF64PromoteFromF32. + operationKindF64PromoteFromF32 + // operationKindI32ReinterpretFromF32 is the Kind for NewOperationI32ReinterpretFromF32. + operationKindI32ReinterpretFromF32 + // operationKindI64ReinterpretFromF64 is the Kind for NewOperationI64ReinterpretFromF64. + operationKindI64ReinterpretFromF64 + // operationKindF32ReinterpretFromI32 is the Kind for NewOperationF32ReinterpretFromI32. + operationKindF32ReinterpretFromI32 + // operationKindF64ReinterpretFromI64 is the Kind for NewOperationF64ReinterpretFromI64. + operationKindF64ReinterpretFromI64 + // operationKindExtend is the Kind for NewOperationExtend. + operationKindExtend + // operationKindSignExtend32From8 is the Kind for NewOperationSignExtend32From8. + operationKindSignExtend32From8 + // operationKindSignExtend32From16 is the Kind for NewOperationSignExtend32From16. + operationKindSignExtend32From16 + // operationKindSignExtend64From8 is the Kind for NewOperationSignExtend64From8. + operationKindSignExtend64From8 + // operationKindSignExtend64From16 is the Kind for NewOperationSignExtend64From16. + operationKindSignExtend64From16 + // operationKindSignExtend64From32 is the Kind for NewOperationSignExtend64From32. + operationKindSignExtend64From32 + // operationKindMemoryInit is the Kind for NewOperationMemoryInit. + operationKindMemoryInit + // operationKindDataDrop is the Kind for NewOperationDataDrop. + operationKindDataDrop + // operationKindMemoryCopy is the Kind for NewOperationMemoryCopy. + operationKindMemoryCopy + // operationKindMemoryFill is the Kind for NewOperationMemoryFill. + operationKindMemoryFill + // operationKindTableInit is the Kind for NewOperationTableInit. + operationKindTableInit + // operationKindElemDrop is the Kind for NewOperationElemDrop. + operationKindElemDrop + // operationKindTableCopy is the Kind for NewOperationTableCopy. + operationKindTableCopy + // operationKindRefFunc is the Kind for NewOperationRefFunc. + operationKindRefFunc + // operationKindTableGet is the Kind for NewOperationTableGet. + operationKindTableGet + // operationKindTableSet is the Kind for NewOperationTableSet. + operationKindTableSet + // operationKindTableSize is the Kind for NewOperationTableSize. + operationKindTableSize + // operationKindTableGrow is the Kind for NewOperationTableGrow. + operationKindTableGrow + // operationKindTableFill is the Kind for NewOperationTableFill. + operationKindTableFill + + // Vector value related instructions are prefixed by V128. + + // operationKindV128Const is the Kind for NewOperationV128Const. + operationKindV128Const + // operationKindV128Add is the Kind for NewOperationV128Add. + operationKindV128Add + // operationKindV128Sub is the Kind for NewOperationV128Sub. + operationKindV128Sub + // operationKindV128Load is the Kind for NewOperationV128Load. + operationKindV128Load + // operationKindV128LoadLane is the Kind for NewOperationV128LoadLane. + operationKindV128LoadLane + // operationKindV128Store is the Kind for NewOperationV128Store. + operationKindV128Store + // operationKindV128StoreLane is the Kind for NewOperationV128StoreLane. + operationKindV128StoreLane + // operationKindV128ExtractLane is the Kind for NewOperationV128ExtractLane. + operationKindV128ExtractLane + // operationKindV128ReplaceLane is the Kind for NewOperationV128ReplaceLane. + operationKindV128ReplaceLane + // operationKindV128Splat is the Kind for NewOperationV128Splat. + operationKindV128Splat + // operationKindV128Shuffle is the Kind for NewOperationV128Shuffle. + operationKindV128Shuffle + // operationKindV128Swizzle is the Kind for NewOperationV128Swizzle. + operationKindV128Swizzle + // operationKindV128AnyTrue is the Kind for NewOperationV128AnyTrue. + operationKindV128AnyTrue + // operationKindV128AllTrue is the Kind for NewOperationV128AllTrue. + operationKindV128AllTrue + // operationKindV128BitMask is the Kind for NewOperationV128BitMask. + operationKindV128BitMask + // operationKindV128And is the Kind for NewOperationV128And. + operationKindV128And + // operationKindV128Not is the Kind for NewOperationV128Not. + operationKindV128Not + // operationKindV128Or is the Kind for NewOperationV128Or. + operationKindV128Or + // operationKindV128Xor is the Kind for NewOperationV128Xor. + operationKindV128Xor + // operationKindV128Bitselect is the Kind for NewOperationV128Bitselect. + operationKindV128Bitselect + // operationKindV128AndNot is the Kind for NewOperationV128AndNot. + operationKindV128AndNot + // operationKindV128Shl is the Kind for NewOperationV128Shl. + operationKindV128Shl + // operationKindV128Shr is the Kind for NewOperationV128Shr. + operationKindV128Shr + // operationKindV128Cmp is the Kind for NewOperationV128Cmp. + operationKindV128Cmp + // operationKindV128AddSat is the Kind for NewOperationV128AddSat. + operationKindV128AddSat + // operationKindV128SubSat is the Kind for NewOperationV128SubSat. + operationKindV128SubSat + // operationKindV128Mul is the Kind for NewOperationV128Mul. + operationKindV128Mul + // operationKindV128Div is the Kind for NewOperationV128Div. + operationKindV128Div + // operationKindV128Neg is the Kind for NewOperationV128Neg. + operationKindV128Neg + // operationKindV128Sqrt is the Kind for NewOperationV128Sqrt. + operationKindV128Sqrt + // operationKindV128Abs is the Kind for NewOperationV128Abs. + operationKindV128Abs + // operationKindV128Popcnt is the Kind for NewOperationV128Popcnt. + operationKindV128Popcnt + // operationKindV128Min is the Kind for NewOperationV128Min. + operationKindV128Min + // operationKindV128Max is the Kind for NewOperationV128Max. + operationKindV128Max + // operationKindV128AvgrU is the Kind for NewOperationV128AvgrU. + operationKindV128AvgrU + // operationKindV128Pmin is the Kind for NewOperationV128Pmin. + operationKindV128Pmin + // operationKindV128Pmax is the Kind for NewOperationV128Pmax. + operationKindV128Pmax + // operationKindV128Ceil is the Kind for NewOperationV128Ceil. + operationKindV128Ceil + // operationKindV128Floor is the Kind for NewOperationV128Floor. + operationKindV128Floor + // operationKindV128Trunc is the Kind for NewOperationV128Trunc. + operationKindV128Trunc + // operationKindV128Nearest is the Kind for NewOperationV128Nearest. + operationKindV128Nearest + // operationKindV128Extend is the Kind for NewOperationV128Extend. + operationKindV128Extend + // operationKindV128ExtMul is the Kind for NewOperationV128ExtMul. + operationKindV128ExtMul + // operationKindV128Q15mulrSatS is the Kind for NewOperationV128Q15mulrSatS. + operationKindV128Q15mulrSatS + // operationKindV128ExtAddPairwise is the Kind for NewOperationV128ExtAddPairwise. + operationKindV128ExtAddPairwise + // operationKindV128FloatPromote is the Kind for NewOperationV128FloatPromote. + operationKindV128FloatPromote + // operationKindV128FloatDemote is the Kind for NewOperationV128FloatDemote. + operationKindV128FloatDemote + // operationKindV128FConvertFromI is the Kind for NewOperationV128FConvertFromI. + operationKindV128FConvertFromI + // operationKindV128Dot is the Kind for NewOperationV128Dot. + operationKindV128Dot + // operationKindV128Narrow is the Kind for NewOperationV128Narrow. + operationKindV128Narrow + // operationKindV128ITruncSatFromF is the Kind for NewOperationV128ITruncSatFromF. + operationKindV128ITruncSatFromF + + // operationKindBuiltinFunctionCheckExitCode is the Kind for NewOperationBuiltinFunctionCheckExitCode. + operationKindBuiltinFunctionCheckExitCode + + // operationKindAtomicMemoryWait is the kind for NewOperationAtomicMemoryWait. + operationKindAtomicMemoryWait + // operationKindAtomicMemoryNotify is the kind for NewOperationAtomicMemoryNotify. + operationKindAtomicMemoryNotify + // operationKindAtomicFence is the kind for NewOperationAtomicFence. + operationKindAtomicFence + // operationKindAtomicLoad is the kind for NewOperationAtomicLoad. + operationKindAtomicLoad + // operationKindAtomicLoad8 is the kind for NewOperationAtomicLoad8. + operationKindAtomicLoad8 + // operationKindAtomicLoad16 is the kind for NewOperationAtomicLoad16. + operationKindAtomicLoad16 + // operationKindAtomicStore is the kind for NewOperationAtomicStore. + operationKindAtomicStore + // operationKindAtomicStore8 is the kind for NewOperationAtomicStore8. + operationKindAtomicStore8 + // operationKindAtomicStore16 is the kind for NewOperationAtomicStore16. + operationKindAtomicStore16 + + // operationKindAtomicRMW is the kind for NewOperationAtomicRMW. + operationKindAtomicRMW + // operationKindAtomicRMW8 is the kind for NewOperationAtomicRMW8. + operationKindAtomicRMW8 + // operationKindAtomicRMW16 is the kind for NewOperationAtomicRMW16. + operationKindAtomicRMW16 + + // operationKindAtomicRMWCmpxchg is the kind for NewOperationAtomicRMWCmpxchg. + operationKindAtomicRMWCmpxchg + // operationKindAtomicRMW8Cmpxchg is the kind for NewOperationAtomicRMW8Cmpxchg. + operationKindAtomicRMW8Cmpxchg + // operationKindAtomicRMW16Cmpxchg is the kind for NewOperationAtomicRMW16Cmpxchg. + operationKindAtomicRMW16Cmpxchg + + // operationKindTailCallReturnCall is the Kind for newOperationTailCallReturnCall. + operationKindTailCallReturnCall + // operationKindTailCallReturnCallIndirect is the Kind for newOperationKindTailCallReturnCallIndirect. + operationKindTailCallReturnCallIndirect + + // operationKindEnd is always placed at the bottom of this iota definition to be used in the test. + operationKindEnd +) + +// NewOperationBuiltinFunctionCheckExitCode is a constructor for unionOperation with Kind operationKindBuiltinFunctionCheckExitCode. +// +// OperationBuiltinFunctionCheckExitCode corresponds to the instruction to check the api.Module is already closed due to +// context.DeadlineExceeded, context.Canceled, or the explicit call of CloseWithExitCode on api.Module. +func newOperationBuiltinFunctionCheckExitCode() unionOperation { + return unionOperation{Kind: operationKindBuiltinFunctionCheckExitCode} +} + +// label is the unique identifier for each block in a single function in interpreterir +// where "block" consists of multiple operations, and must End with branching operations +// (e.g. operationKindBr or operationKindBrIf). +type label uint64 + +// Kind returns the labelKind encoded in this label. +func (l label) Kind() labelKind { + return labelKind(uint32(l)) +} + +// FrameID returns the frame id encoded in this label. +func (l label) FrameID() int { + return int(uint32(l >> 32)) +} + +// NewLabel is a constructor for a label. +func newLabel(kind labelKind, frameID uint32) label { + return label(kind) | label(frameID)<<32 +} + +// String implements fmt.Stringer. +func (l label) String() (ret string) { + frameID := l.FrameID() + switch l.Kind() { + case labelKindHeader: + ret = fmt.Sprintf(".L%d", frameID) + case labelKindElse: + ret = fmt.Sprintf(".L%d_else", frameID) + case labelKindContinuation: + ret = fmt.Sprintf(".L%d_cont", frameID) + case labelKindReturn: + return ".return" + } + return +} + +func (l label) IsReturnTarget() bool { + return l.Kind() == labelKindReturn +} + +// labelKind is the Kind of the label. +type labelKind = byte + +const ( + // labelKindHeader is the header for various blocks. For example, the "then" block of + // wasm.OpcodeIfName in Wasm has the label of this Kind. + labelKindHeader labelKind = iota + // labelKindElse is the Kind of label for "else" block of wasm.OpcodeIfName in Wasm. + labelKindElse + // labelKindContinuation is the Kind of label which is the continuation of blocks. + // For example, for wasm text like + // (func + // .... + // (if (local.get 0) (then (nop)) (else (nop))) + // return + // ) + // we have the continuation block (of if-block) corresponding to "return" opcode. + labelKindContinuation + labelKindReturn + labelKindNum +) + +// unionOperation implements Operation and is the compilation (engine.lowerIR) result of a interpreterir.Operation. +// +// Not all operations result in a unionOperation, e.g. interpreterir.OperationI32ReinterpretFromF32, and some operations are +// more complex than others, e.g. interpreterir.NewOperationBrTable. +// +// Note: This is a form of union type as it can store fields needed for any operation. Hence, most fields are opaque and +// only relevant when in context of its kind. +type unionOperation struct { + // Kind determines how to interpret the other fields in this struct. + Kind operationKind + B1, B2 byte + B3 bool + U1, U2 uint64 + U3 uint64 + Us []uint64 +} + +// String implements fmt.Stringer. +func (o unionOperation) String() string { + switch o.Kind { + case operationKindUnreachable, + operationKindSelect, + operationKindMemorySize, + operationKindMemoryGrow, + operationKindI32WrapFromI64, + operationKindF32DemoteFromF64, + operationKindF64PromoteFromF32, + operationKindI32ReinterpretFromF32, + operationKindI64ReinterpretFromF64, + operationKindF32ReinterpretFromI32, + operationKindF64ReinterpretFromI64, + operationKindSignExtend32From8, + operationKindSignExtend32From16, + operationKindSignExtend64From8, + operationKindSignExtend64From16, + operationKindSignExtend64From32, + operationKindMemoryInit, + operationKindDataDrop, + operationKindMemoryCopy, + operationKindMemoryFill, + operationKindTableInit, + operationKindElemDrop, + operationKindTableCopy, + operationKindRefFunc, + operationKindTableGet, + operationKindTableSet, + operationKindTableSize, + operationKindTableGrow, + operationKindTableFill, + operationKindBuiltinFunctionCheckExitCode: + return o.Kind.String() + + case operationKindCall, + operationKindGlobalGet, + operationKindGlobalSet: + return fmt.Sprintf("%s %d", o.Kind, o.B1) + + case operationKindLabel: + return label(o.U1).String() + + case operationKindBr: + return fmt.Sprintf("%s %s", o.Kind, label(o.U1).String()) + + case operationKindBrIf: + thenTarget := label(o.U1) + elseTarget := label(o.U2) + return fmt.Sprintf("%s %s, %s", o.Kind, thenTarget, elseTarget) + + case operationKindBrTable: + var targets []string + var defaultLabel label + if len(o.Us) > 0 { + targets = make([]string, len(o.Us)-1) + for i, t := range o.Us[1:] { + targets[i] = label(t).String() + } + defaultLabel = label(o.Us[0]) + } + return fmt.Sprintf("%s [%s] %s", o.Kind, strings.Join(targets, ","), defaultLabel) + + case operationKindCallIndirect: + return fmt.Sprintf("%s: type=%d, table=%d", o.Kind, o.U1, o.U2) + + case operationKindDrop: + start := int64(o.U1) + end := int64(o.U2) + return fmt.Sprintf("%s %d..%d", o.Kind, start, end) + + case operationKindPick, operationKindSet: + return fmt.Sprintf("%s %d (is_vector=%v)", o.Kind, o.U1, o.B3) + + case operationKindLoad, operationKindStore: + return fmt.Sprintf("%s.%s (align=%d, offset=%d)", unsignedType(o.B1), o.Kind, o.U1, o.U2) + + case operationKindLoad8, + operationKindLoad16: + return fmt.Sprintf("%s.%s (align=%d, offset=%d)", signedType(o.B1), o.Kind, o.U1, o.U2) + + case operationKindStore8, + operationKindStore16, + operationKindStore32: + return fmt.Sprintf("%s (align=%d, offset=%d)", o.Kind, o.U1, o.U2) + + case operationKindLoad32: + var t string + if o.B1 == 1 { + t = "i64" + } else { + t = "u64" + } + return fmt.Sprintf("%s.%s (align=%d, offset=%d)", t, o.Kind, o.U1, o.U2) + + case operationKindEq, + operationKindNe, + operationKindAdd, + operationKindSub, + operationKindMul: + return fmt.Sprintf("%s.%s", unsignedType(o.B1), o.Kind) + + case operationKindEqz, + operationKindClz, + operationKindCtz, + operationKindPopcnt, + operationKindAnd, + operationKindOr, + operationKindXor, + operationKindShl, + operationKindRotl, + operationKindRotr: + return fmt.Sprintf("%s.%s", unsignedInt(o.B1), o.Kind) + + case operationKindRem, operationKindShr: + return fmt.Sprintf("%s.%s", signedInt(o.B1), o.Kind) + + case operationKindLt, + operationKindGt, + operationKindLe, + operationKindGe, + operationKindDiv: + return fmt.Sprintf("%s.%s", signedType(o.B1), o.Kind) + + case operationKindAbs, + operationKindNeg, + operationKindCeil, + operationKindFloor, + operationKindTrunc, + operationKindNearest, + operationKindSqrt, + operationKindMin, + operationKindMax, + operationKindCopysign: + return fmt.Sprintf("%s.%s", float(o.B1), o.Kind) + + case operationKindConstI32, + operationKindConstI64: + return fmt.Sprintf("%s %#x", o.Kind, o.U1) + + case operationKindConstF32: + return fmt.Sprintf("%s %f", o.Kind, math.Float32frombits(uint32(o.U1))) + case operationKindConstF64: + return fmt.Sprintf("%s %f", o.Kind, math.Float64frombits(o.U1)) + + case operationKindITruncFromF: + return fmt.Sprintf("%s.%s.%s (non_trapping=%v)", signedInt(o.B2), o.Kind, float(o.B1), o.B3) + case operationKindFConvertFromI: + return fmt.Sprintf("%s.%s.%s", float(o.B2), o.Kind, signedInt(o.B1)) + case operationKindExtend: + var in, out string + if o.B3 { + in = "i32" + out = "i64" + } else { + in = "u32" + out = "u64" + } + return fmt.Sprintf("%s.%s.%s", out, o.Kind, in) + + case operationKindV128Const: + return fmt.Sprintf("%s [%#x, %#x]", o.Kind, o.U1, o.U2) + case operationKindV128Add, + operationKindV128Sub: + return fmt.Sprintf("%s (shape=%s)", o.Kind, shapeName(o.B1)) + case operationKindV128Load, + operationKindV128LoadLane, + operationKindV128Store, + operationKindV128StoreLane, + operationKindV128ExtractLane, + operationKindV128ReplaceLane, + operationKindV128Splat, + operationKindV128Shuffle, + operationKindV128Swizzle, + operationKindV128AnyTrue, + operationKindV128AllTrue, + operationKindV128BitMask, + operationKindV128And, + operationKindV128Not, + operationKindV128Or, + operationKindV128Xor, + operationKindV128Bitselect, + operationKindV128AndNot, + operationKindV128Shl, + operationKindV128Shr, + operationKindV128Cmp, + operationKindV128AddSat, + operationKindV128SubSat, + operationKindV128Mul, + operationKindV128Div, + operationKindV128Neg, + operationKindV128Sqrt, + operationKindV128Abs, + operationKindV128Popcnt, + operationKindV128Min, + operationKindV128Max, + operationKindV128AvgrU, + operationKindV128Pmin, + operationKindV128Pmax, + operationKindV128Ceil, + operationKindV128Floor, + operationKindV128Trunc, + operationKindV128Nearest, + operationKindV128Extend, + operationKindV128ExtMul, + operationKindV128Q15mulrSatS, + operationKindV128ExtAddPairwise, + operationKindV128FloatPromote, + operationKindV128FloatDemote, + operationKindV128FConvertFromI, + operationKindV128Dot, + operationKindV128Narrow: + return o.Kind.String() + + case operationKindV128ITruncSatFromF: + if o.B3 { + return fmt.Sprintf("%s.%sS", o.Kind, shapeName(o.B1)) + } else { + return fmt.Sprintf("%s.%sU", o.Kind, shapeName(o.B1)) + } + + case operationKindAtomicMemoryWait, + operationKindAtomicMemoryNotify, + operationKindAtomicFence, + operationKindAtomicLoad, + operationKindAtomicLoad8, + operationKindAtomicLoad16, + operationKindAtomicStore, + operationKindAtomicStore8, + operationKindAtomicStore16, + operationKindAtomicRMW, + operationKindAtomicRMW8, + operationKindAtomicRMW16, + operationKindAtomicRMWCmpxchg, + operationKindAtomicRMW8Cmpxchg, + operationKindAtomicRMW16Cmpxchg: + return o.Kind.String() + + case operationKindTailCallReturnCall: + return fmt.Sprintf("%s %d %s", o.Kind, o.U1, label(o.U2).String()) + + case operationKindTailCallReturnCallIndirect: + return fmt.Sprintf("%s %d %d", o.Kind, o.U1, o.U2) + + default: + panic(fmt.Sprintf("TODO: %v", o.Kind)) + } +} + +// NewOperationUnreachable is a constructor for unionOperation with operationKindUnreachable +// +// This corresponds to wasm.OpcodeUnreachable. +// +// The engines are expected to exit the execution with wasmruntime.ErrRuntimeUnreachable error. +func newOperationUnreachable() unionOperation { + return unionOperation{Kind: operationKindUnreachable} +} + +// NewOperationLabel is a constructor for unionOperation with operationKindLabel. +// +// This is used to inform the engines of the beginning of a label. +func newOperationLabel(label label) unionOperation { + return unionOperation{Kind: operationKindLabel, U1: uint64(label)} +} + +// NewOperationBr is a constructor for unionOperation with operationKindBr. +// +// The engines are expected to branch into U1 label. +func newOperationBr(target label) unionOperation { + return unionOperation{Kind: operationKindBr, U1: uint64(target)} +} + +// NewOperationBrIf is a constructor for unionOperation with operationKindBrIf. +// +// The engines are expected to pop a value and branch into U1 label if the value equals 1. +// Otherwise, the code branches into U2 label. +func newOperationBrIf(thenTarget, elseTarget label, thenDrop inclusiveRange) unionOperation { + return unionOperation{ + Kind: operationKindBrIf, + U1: uint64(thenTarget), + U2: uint64(elseTarget), + U3: thenDrop.AsU64(), + } +} + +// NewOperationBrTable is a constructor for unionOperation with operationKindBrTable. +// +// This corresponds to wasm.OpcodeBrTableName except that the label +// here means the interpreterir level, not the ones of Wasm. +// +// The engines are expected to do the br_table operation based on the default (Us[len(Us)-1], Us[len(Us)-2]) and +// targets (Us[:len(Us)-1], Rs[:len(Us)-1]). More precisely, this pops a value from the stack (called "index") +// and decides which branch we go into next based on the value. +// +// For example, assume we have operations like {default: L_DEFAULT, targets: [L0, L1, L2]}. +// If "index" >= len(defaults), then branch into the L_DEFAULT label. +// Otherwise, we enter label of targets[index]. +func newOperationBrTable(targetLabelsAndRanges []uint64) unionOperation { + return unionOperation{ + Kind: operationKindBrTable, + Us: targetLabelsAndRanges, + } +} + +// NewOperationCall is a constructor for unionOperation with operationKindCall. +// +// This corresponds to wasm.OpcodeCallName, and engines are expected to +// enter into a function whose index equals OperationCall.FunctionIndex. +func newOperationCall(functionIndex uint32) unionOperation { + return unionOperation{Kind: operationKindCall, U1: uint64(functionIndex)} +} + +// NewOperationCallIndirect implements Operation. +// +// This corresponds to wasm.OpcodeCallIndirectName, and engines are expected to +// consume the one value from the top of stack (called "offset"), +// and make a function call against the function whose function address equals +// Tables[OperationCallIndirect.TableIndex][offset]. +// +// Note: This is called indirect function call in the sense that the target function is indirectly +// determined by the current state (top value) of the stack. +// Therefore, two checks are performed at runtime before entering the target function: +// 1) whether "offset" exceeds the length of table Tables[OperationCallIndirect.TableIndex]. +// 2) whether the type of the function table[offset] matches the function type specified by OperationCallIndirect.TypeIndex. +func newOperationCallIndirect(typeIndex, tableIndex uint32) unionOperation { + return unionOperation{Kind: operationKindCallIndirect, U1: uint64(typeIndex), U2: uint64(tableIndex)} +} + +// inclusiveRange is the range which spans across the value stack starting from the top to the bottom, and +// both boundary are included in the range. +type inclusiveRange struct { + Start, End int32 +} + +// AsU64 is be used to convert inclusiveRange to uint64 so that it can be stored in unionOperation. +func (i inclusiveRange) AsU64() uint64 { + return uint64(uint32(i.Start))<<32 | uint64(uint32(i.End)) +} + +// inclusiveRangeFromU64 retrieves inclusiveRange from the given uint64 which is stored in unionOperation. +func inclusiveRangeFromU64(v uint64) inclusiveRange { + return inclusiveRange{ + Start: int32(uint32(v >> 32)), + End: int32(uint32(v)), + } +} + +// nopinclusiveRange is inclusiveRange which corresponds to no-operation. +var nopinclusiveRange = inclusiveRange{Start: -1, End: -1} + +// NewOperationDrop is a constructor for unionOperation with operationKindDrop. +// +// The engines are expected to discard the values selected by NewOperationDrop.Depth which +// starts from the top of the stack to the bottom. +// +// depth spans across the uint64 value stack at runtime to be dropped by this operation. +func newOperationDrop(depth inclusiveRange) unionOperation { + return unionOperation{Kind: operationKindDrop, U1: depth.AsU64()} +} + +// NewOperationSelect is a constructor for unionOperation with operationKindSelect. +// +// This corresponds to wasm.OpcodeSelect. +// +// The engines are expected to pop three values, say [..., x2, x1, c], then if the value "c" equals zero, +// "x1" is pushed back onto the stack and, otherwise "x2" is pushed back. +// +// isTargetVector true if the selection target value's type is wasm.ValueTypeV128. +func newOperationSelect(isTargetVector bool) unionOperation { + return unionOperation{Kind: operationKindSelect, B3: isTargetVector} +} + +// NewOperationPick is a constructor for unionOperation with operationKindPick. +// +// The engines are expected to copy a value pointed by depth, and push the +// copied value onto the top of the stack. +// +// depth is the location of the pick target in the uint64 value stack at runtime. +// If isTargetVector=true, this points to the location of the lower 64-bits of the vector. +func newOperationPick(depth int, isTargetVector bool) unionOperation { + return unionOperation{Kind: operationKindPick, U1: uint64(depth), B3: isTargetVector} +} + +// NewOperationSet is a constructor for unionOperation with operationKindSet. +// +// The engines are expected to set the top value of the stack to the location specified by +// depth. +// +// depth is the location of the set target in the uint64 value stack at runtime. +// If isTargetVector=true, this points the location of the lower 64-bits of the vector. +func newOperationSet(depth int, isTargetVector bool) unionOperation { + return unionOperation{Kind: operationKindSet, U1: uint64(depth), B3: isTargetVector} +} + +// NewOperationGlobalGet is a constructor for unionOperation with operationKindGlobalGet. +// +// The engines are expected to read the global value specified by OperationGlobalGet.Index, +// and push the copy of the value onto the stack. +// +// See wasm.OpcodeGlobalGet. +func newOperationGlobalGet(index uint32) unionOperation { + return unionOperation{Kind: operationKindGlobalGet, U1: uint64(index)} +} + +// NewOperationGlobalSet is a constructor for unionOperation with operationKindGlobalSet. +// +// The engines are expected to consume the value from the top of the stack, +// and write the value into the global specified by OperationGlobalSet.Index. +// +// See wasm.OpcodeGlobalSet. +func newOperationGlobalSet(index uint32) unionOperation { + return unionOperation{Kind: operationKindGlobalSet, U1: uint64(index)} +} + +// memoryArg is the "memarg" to all memory instructions. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instructions%E2%91%A0 +type memoryArg struct { + // Alignment the expected alignment (expressed as the exponent of a power of 2). Default to the natural alignment. + // + // "Natural alignment" is defined here as the smallest power of two that can hold the size of the value type. Ex + // wasm.ValueTypeI64 is encoded in 8 little-endian bytes. 2^3 = 8, so the natural alignment is three. + Alignment uint32 + + // Offset is the address offset added to the instruction's dynamic address operand, yielding a 33-bit effective + // address that is the zero-based index at which the memory is accessed. Default to zero. + Offset uint32 +} + +// NewOperationLoad is a constructor for unionOperation with operationKindLoad. +// +// This corresponds to wasm.OpcodeI32LoadName wasm.OpcodeI64LoadName wasm.OpcodeF32LoadName and wasm.OpcodeF64LoadName. +// +// The engines are expected to check the boundary of memory length, and exit the execution if this exceeds the boundary, +// otherwise load the corresponding value following the semantics of the corresponding WebAssembly instruction. +func newOperationLoad(unsignedType unsignedType, arg memoryArg) unionOperation { + return unionOperation{Kind: operationKindLoad, B1: byte(unsignedType), U1: uint64(arg.Alignment), U2: uint64(arg.Offset)} +} + +// NewOperationLoad8 is a constructor for unionOperation with operationKindLoad8. +// +// This corresponds to wasm.OpcodeI32Load8SName wasm.OpcodeI32Load8UName wasm.OpcodeI64Load8SName wasm.OpcodeI64Load8UName. +// +// The engines are expected to check the boundary of memory length, and exit the execution if this exceeds the boundary, +// otherwise load the corresponding value following the semantics of the corresponding WebAssembly instruction. +func newOperationLoad8(signedInt signedInt, arg memoryArg) unionOperation { + return unionOperation{Kind: operationKindLoad8, B1: byte(signedInt), U1: uint64(arg.Alignment), U2: uint64(arg.Offset)} +} + +// NewOperationLoad16 is a constructor for unionOperation with operationKindLoad16. +// +// This corresponds to wasm.OpcodeI32Load16SName wasm.OpcodeI32Load16UName wasm.OpcodeI64Load16SName wasm.OpcodeI64Load16UName. +// +// The engines are expected to check the boundary of memory length, and exit the execution if this exceeds the boundary, +// otherwise load the corresponding value following the semantics of the corresponding WebAssembly instruction. +func newOperationLoad16(signedInt signedInt, arg memoryArg) unionOperation { + return unionOperation{Kind: operationKindLoad16, B1: byte(signedInt), U1: uint64(arg.Alignment), U2: uint64(arg.Offset)} +} + +// NewOperationLoad32 is a constructor for unionOperation with operationKindLoad32. +// +// This corresponds to wasm.OpcodeI64Load32SName wasm.OpcodeI64Load32UName. +// +// The engines are expected to check the boundary of memory length, and exit the execution if this exceeds the boundary, +// otherwise load the corresponding value following the semantics of the corresponding WebAssembly instruction. +func newOperationLoad32(signed bool, arg memoryArg) unionOperation { + sigB := byte(0) + if signed { + sigB = 1 + } + return unionOperation{Kind: operationKindLoad32, B1: sigB, U1: uint64(arg.Alignment), U2: uint64(arg.Offset)} +} + +// NewOperationStore is a constructor for unionOperation with operationKindStore. +// +// # This corresponds to wasm.OpcodeI32StoreName wasm.OpcodeI64StoreName wasm.OpcodeF32StoreName wasm.OpcodeF64StoreName +// +// The engines are expected to check the boundary of memory length, and exit the execution if this exceeds the boundary, +// otherwise store the corresponding value following the semantics of the corresponding WebAssembly instruction. +func newOperationStore(unsignedType unsignedType, arg memoryArg) unionOperation { + return unionOperation{Kind: operationKindStore, B1: byte(unsignedType), U1: uint64(arg.Alignment), U2: uint64(arg.Offset)} +} + +// NewOperationStore8 is a constructor for unionOperation with operationKindStore8. +// +// # This corresponds to wasm.OpcodeI32Store8Name wasm.OpcodeI64Store8Name +// +// The engines are expected to check the boundary of memory length, and exit the execution if this exceeds the boundary, +// otherwise store the corresponding value following the semantics of the corresponding WebAssembly instruction. +func newOperationStore8(arg memoryArg) unionOperation { + return unionOperation{Kind: operationKindStore8, U1: uint64(arg.Alignment), U2: uint64(arg.Offset)} +} + +// NewOperationStore16 is a constructor for unionOperation with operationKindStore16. +// +// # This corresponds to wasm.OpcodeI32Store16Name wasm.OpcodeI64Store16Name +// +// The engines are expected to check the boundary of memory length, and exit the execution if this exceeds the boundary, +// otherwise store the corresponding value following the semantics of the corresponding WebAssembly instruction. +func newOperationStore16(arg memoryArg) unionOperation { + return unionOperation{Kind: operationKindStore16, U1: uint64(arg.Alignment), U2: uint64(arg.Offset)} +} + +// NewOperationStore32 is a constructor for unionOperation with operationKindStore32. +// +// # This corresponds to wasm.OpcodeI64Store32Name +// +// The engines are expected to check the boundary of memory length, and exit the execution if this exceeds the boundary, +// otherwise store the corresponding value following the semantics of the corresponding WebAssembly instruction. +func newOperationStore32(arg memoryArg) unionOperation { + return unionOperation{Kind: operationKindStore32, U1: uint64(arg.Alignment), U2: uint64(arg.Offset)} +} + +// NewOperationMemorySize is a constructor for unionOperation with operationKindMemorySize. +// +// This corresponds to wasm.OpcodeMemorySize. +// +// The engines are expected to push the current page size of the memory onto the stack. +func newOperationMemorySize() unionOperation { + return unionOperation{Kind: operationKindMemorySize} +} + +// NewOperationMemoryGrow is a constructor for unionOperation with operationKindMemoryGrow. +// +// This corresponds to wasm.OpcodeMemoryGrow. +// +// The engines are expected to pop one value from the top of the stack, then +// execute wasm.MemoryInstance Grow with the value, and push the previous +// page size of the memory onto the stack. +func newOperationMemoryGrow() unionOperation { + return unionOperation{Kind: operationKindMemoryGrow} +} + +// NewOperationConstI32 is a constructor for unionOperation with OperationConstI32. +// +// This corresponds to wasm.OpcodeI32Const. +func newOperationConstI32(value uint32) unionOperation { + return unionOperation{Kind: operationKindConstI32, U1: uint64(value)} +} + +// NewOperationConstI64 is a constructor for unionOperation with OperationConstI64. +// +// This corresponds to wasm.OpcodeI64Const. +func newOperationConstI64(value uint64) unionOperation { + return unionOperation{Kind: operationKindConstI64, U1: value} +} + +// NewOperationConstF32 is a constructor for unionOperation with OperationConstF32. +// +// This corresponds to wasm.OpcodeF32Const. +func newOperationConstF32(value float32) unionOperation { + return unionOperation{Kind: operationKindConstF32, U1: uint64(math.Float32bits(value))} +} + +// NewOperationConstF64 is a constructor for unionOperation with OperationConstF64. +// +// This corresponds to wasm.OpcodeF64Const. +func newOperationConstF64(value float64) unionOperation { + return unionOperation{Kind: operationKindConstF64, U1: math.Float64bits(value)} +} + +// NewOperationEq is a constructor for unionOperation with operationKindEq. +// +// This corresponds to wasm.OpcodeI32EqName wasm.OpcodeI64EqName wasm.OpcodeF32EqName wasm.OpcodeF64EqName +func newOperationEq(b unsignedType) unionOperation { + return unionOperation{Kind: operationKindEq, B1: byte(b)} +} + +// NewOperationNe is a constructor for unionOperation with operationKindNe. +// +// This corresponds to wasm.OpcodeI32NeName wasm.OpcodeI64NeName wasm.OpcodeF32NeName wasm.OpcodeF64NeName +func newOperationNe(b unsignedType) unionOperation { + return unionOperation{Kind: operationKindNe, B1: byte(b)} +} + +// NewOperationEqz is a constructor for unionOperation with operationKindEqz. +// +// This corresponds to wasm.OpcodeI32EqzName wasm.OpcodeI64EqzName +func newOperationEqz(b unsignedInt) unionOperation { + return unionOperation{Kind: operationKindEqz, B1: byte(b)} +} + +// NewOperationLt is a constructor for unionOperation with operationKindLt. +// +// This corresponds to wasm.OpcodeI32LtS wasm.OpcodeI32LtU wasm.OpcodeI64LtS wasm.OpcodeI64LtU wasm.OpcodeF32Lt wasm.OpcodeF64Lt +func newOperationLt(b signedType) unionOperation { + return unionOperation{Kind: operationKindLt, B1: byte(b)} +} + +// NewOperationGt is a constructor for unionOperation with operationKindGt. +// +// This corresponds to wasm.OpcodeI32GtS wasm.OpcodeI32GtU wasm.OpcodeI64GtS wasm.OpcodeI64GtU wasm.OpcodeF32Gt wasm.OpcodeF64Gt +func newOperationGt(b signedType) unionOperation { + return unionOperation{Kind: operationKindGt, B1: byte(b)} +} + +// NewOperationLe is a constructor for unionOperation with operationKindLe. +// +// This corresponds to wasm.OpcodeI32LeS wasm.OpcodeI32LeU wasm.OpcodeI64LeS wasm.OpcodeI64LeU wasm.OpcodeF32Le wasm.OpcodeF64Le +func newOperationLe(b signedType) unionOperation { + return unionOperation{Kind: operationKindLe, B1: byte(b)} +} + +// NewOperationGe is a constructor for unionOperation with operationKindGe. +// +// This corresponds to wasm.OpcodeI32GeS wasm.OpcodeI32GeU wasm.OpcodeI64GeS wasm.OpcodeI64GeU wasm.OpcodeF32Ge wasm.OpcodeF64Ge +// NewOperationGe is the constructor for OperationGe +func newOperationGe(b signedType) unionOperation { + return unionOperation{Kind: operationKindGe, B1: byte(b)} +} + +// NewOperationAdd is a constructor for unionOperation with operationKindAdd. +// +// This corresponds to wasm.OpcodeI32AddName wasm.OpcodeI64AddName wasm.OpcodeF32AddName wasm.OpcodeF64AddName. +func newOperationAdd(b unsignedType) unionOperation { + return unionOperation{Kind: operationKindAdd, B1: byte(b)} +} + +// NewOperationSub is a constructor for unionOperation with operationKindSub. +// +// This corresponds to wasm.OpcodeI32SubName wasm.OpcodeI64SubName wasm.OpcodeF32SubName wasm.OpcodeF64SubName. +func newOperationSub(b unsignedType) unionOperation { + return unionOperation{Kind: operationKindSub, B1: byte(b)} +} + +// NewOperationMul is a constructor for unionOperation with wperationKindMul. +// +// This corresponds to wasm.OpcodeI32MulName wasm.OpcodeI64MulName wasm.OpcodeF32MulName wasm.OpcodeF64MulName. +// NewOperationMul is the constructor for OperationMul +func newOperationMul(b unsignedType) unionOperation { + return unionOperation{Kind: operationKindMul, B1: byte(b)} +} + +// NewOperationClz is a constructor for unionOperation with operationKindClz. +// +// This corresponds to wasm.OpcodeI32ClzName wasm.OpcodeI64ClzName. +// +// The engines are expected to count up the leading zeros in the +// current top of the stack, and push the count result. +// For example, stack of [..., 0x00_ff_ff_ff] results in [..., 8]. +// See wasm.OpcodeI32Clz wasm.OpcodeI64Clz +func newOperationClz(b unsignedInt) unionOperation { + return unionOperation{Kind: operationKindClz, B1: byte(b)} +} + +// NewOperationCtz is a constructor for unionOperation with operationKindCtz. +// +// This corresponds to wasm.OpcodeI32CtzName wasm.OpcodeI64CtzName. +// +// The engines are expected to count up the trailing zeros in the +// current top of the stack, and push the count result. +// For example, stack of [..., 0xff_ff_ff_00] results in [..., 8]. +func newOperationCtz(b unsignedInt) unionOperation { + return unionOperation{Kind: operationKindCtz, B1: byte(b)} +} + +// NewOperationPopcnt is a constructor for unionOperation with operationKindPopcnt. +// +// This corresponds to wasm.OpcodeI32PopcntName wasm.OpcodeI64PopcntName. +// +// The engines are expected to count up the number of set bits in the +// current top of the stack, and push the count result. +// For example, stack of [..., 0b00_00_00_11] results in [..., 2]. +func newOperationPopcnt(b unsignedInt) unionOperation { + return unionOperation{Kind: operationKindPopcnt, B1: byte(b)} +} + +// NewOperationDiv is a constructor for unionOperation with operationKindDiv. +// +// This corresponds to wasm.OpcodeI32DivS wasm.OpcodeI32DivU wasm.OpcodeI64DivS +// +// wasm.OpcodeI64DivU wasm.OpcodeF32Div wasm.OpcodeF64Div. +func newOperationDiv(b signedType) unionOperation { + return unionOperation{Kind: operationKindDiv, B1: byte(b)} +} + +// NewOperationRem is a constructor for unionOperation with operationKindRem. +// +// This corresponds to wasm.OpcodeI32RemS wasm.OpcodeI32RemU wasm.OpcodeI64RemS wasm.OpcodeI64RemU. +// +// The engines are expected to perform division on the top +// two values of integer type on the stack and puts the remainder of the result +// onto the stack. For example, stack [..., 10, 3] results in [..., 1] where +// the quotient is discarded. +// NewOperationRem is the constructor for OperationRem +func newOperationRem(b signedInt) unionOperation { + return unionOperation{Kind: operationKindRem, B1: byte(b)} +} + +// NewOperationAnd is a constructor for unionOperation with operationKindAnd. +// +// # This corresponds to wasm.OpcodeI32AndName wasm.OpcodeI64AndName +// +// The engines are expected to perform "And" operation on +// top two values on the stack, and pushes the result. +func newOperationAnd(b unsignedInt) unionOperation { + return unionOperation{Kind: operationKindAnd, B1: byte(b)} +} + +// NewOperationOr is a constructor for unionOperation with operationKindOr. +// +// # This corresponds to wasm.OpcodeI32OrName wasm.OpcodeI64OrName +// +// The engines are expected to perform "Or" operation on +// top two values on the stack, and pushes the result. +func newOperationOr(b unsignedInt) unionOperation { + return unionOperation{Kind: operationKindOr, B1: byte(b)} +} + +// NewOperationXor is a constructor for unionOperation with operationKindXor. +// +// # This corresponds to wasm.OpcodeI32XorName wasm.OpcodeI64XorName +// +// The engines are expected to perform "Xor" operation on +// top two values on the stack, and pushes the result. +func newOperationXor(b unsignedInt) unionOperation { + return unionOperation{Kind: operationKindXor, B1: byte(b)} +} + +// NewOperationShl is a constructor for unionOperation with operationKindShl. +// +// # This corresponds to wasm.OpcodeI32ShlName wasm.OpcodeI64ShlName +// +// The engines are expected to perform "Shl" operation on +// top two values on the stack, and pushes the result. +func newOperationShl(b unsignedInt) unionOperation { + return unionOperation{Kind: operationKindShl, B1: byte(b)} +} + +// NewOperationShr is a constructor for unionOperation with operationKindShr. +// +// # This corresponds to wasm.OpcodeI32ShrSName wasm.OpcodeI32ShrUName wasm.OpcodeI64ShrSName wasm.OpcodeI64ShrUName +// +// If OperationShr.Type is signed integer, then, the engines are expected to perform arithmetic right shift on the two +// top values on the stack, otherwise do the logical right shift. +func newOperationShr(b signedInt) unionOperation { + return unionOperation{Kind: operationKindShr, B1: byte(b)} +} + +// NewOperationRotl is a constructor for unionOperation with operationKindRotl. +// +// # This corresponds to wasm.OpcodeI32RotlName wasm.OpcodeI64RotlName +// +// The engines are expected to perform "Rotl" operation on +// top two values on the stack, and pushes the result. +func newOperationRotl(b unsignedInt) unionOperation { + return unionOperation{Kind: operationKindRotl, B1: byte(b)} +} + +// NewOperationRotr is a constructor for unionOperation with operationKindRotr. +// +// # This corresponds to wasm.OpcodeI32RotrName wasm.OpcodeI64RotrName +// +// The engines are expected to perform "Rotr" operation on +// top two values on the stack, and pushes the result. +func newOperationRotr(b unsignedInt) unionOperation { + return unionOperation{Kind: operationKindRotr, B1: byte(b)} +} + +// NewOperationAbs is a constructor for unionOperation with operationKindAbs. +// +// This corresponds to wasm.OpcodeF32Abs wasm.OpcodeF64Abs +func newOperationAbs(b float) unionOperation { + return unionOperation{Kind: operationKindAbs, B1: byte(b)} +} + +// NewOperationNeg is a constructor for unionOperation with operationKindNeg. +// +// This corresponds to wasm.OpcodeF32Neg wasm.OpcodeF64Neg +func newOperationNeg(b float) unionOperation { + return unionOperation{Kind: operationKindNeg, B1: byte(b)} +} + +// NewOperationCeil is a constructor for unionOperation with operationKindCeil. +// +// This corresponds to wasm.OpcodeF32CeilName wasm.OpcodeF64CeilName +func newOperationCeil(b float) unionOperation { + return unionOperation{Kind: operationKindCeil, B1: byte(b)} +} + +// NewOperationFloor is a constructor for unionOperation with operationKindFloor. +// +// This corresponds to wasm.OpcodeF32FloorName wasm.OpcodeF64FloorName +func newOperationFloor(b float) unionOperation { + return unionOperation{Kind: operationKindFloor, B1: byte(b)} +} + +// NewOperationTrunc is a constructor for unionOperation with operationKindTrunc. +// +// This corresponds to wasm.OpcodeF32TruncName wasm.OpcodeF64TruncName +func newOperationTrunc(b float) unionOperation { + return unionOperation{Kind: operationKindTrunc, B1: byte(b)} +} + +// NewOperationNearest is a constructor for unionOperation with operationKindNearest. +// +// # This corresponds to wasm.OpcodeF32NearestName wasm.OpcodeF64NearestName +// +// Note: this is *not* equivalent to math.Round and instead has the same +// the semantics of LLVM's rint intrinsic. See https://llvm.org/docs/LangRef.html#llvm-rint-intrinsic. +// For example, math.Round(-4.5) produces -5 while we want to produce -4. +func newOperationNearest(b float) unionOperation { + return unionOperation{Kind: operationKindNearest, B1: byte(b)} +} + +// NewOperationSqrt is a constructor for unionOperation with operationKindSqrt. +// +// This corresponds to wasm.OpcodeF32SqrtName wasm.OpcodeF64SqrtName +func newOperationSqrt(b float) unionOperation { + return unionOperation{Kind: operationKindSqrt, B1: byte(b)} +} + +// NewOperationMin is a constructor for unionOperation with operationKindMin. +// +// # This corresponds to wasm.OpcodeF32MinName wasm.OpcodeF64MinName +// +// The engines are expected to pop two values from the stack, and push back the maximum of +// these two values onto the stack. For example, stack [..., 100.1, 1.9] results in [..., 1.9]. +// +// Note: WebAssembly specifies that min/max must always return NaN if one of values is NaN, +// which is a different behavior different from math.Min. +func newOperationMin(b float) unionOperation { + return unionOperation{Kind: operationKindMin, B1: byte(b)} +} + +// NewOperationMax is a constructor for unionOperation with operationKindMax. +// +// # This corresponds to wasm.OpcodeF32MaxName wasm.OpcodeF64MaxName +// +// The engines are expected to pop two values from the stack, and push back the maximum of +// these two values onto the stack. For example, stack [..., 100.1, 1.9] results in [..., 100.1]. +// +// Note: WebAssembly specifies that min/max must always return NaN if one of values is NaN, +// which is a different behavior different from math.Max. +func newOperationMax(b float) unionOperation { + return unionOperation{Kind: operationKindMax, B1: byte(b)} +} + +// NewOperationCopysign is a constructor for unionOperation with operationKindCopysign. +// +// # This corresponds to wasm.OpcodeF32CopysignName wasm.OpcodeF64CopysignName +// +// The engines are expected to pop two float values from the stack, and copy the signbit of +// the first-popped value to the last one. +// For example, stack [..., 1.213, -5.0] results in [..., -1.213]. +func newOperationCopysign(b float) unionOperation { + return unionOperation{Kind: operationKindCopysign, B1: byte(b)} +} + +// NewOperationI32WrapFromI64 is a constructor for unionOperation with operationKindI32WrapFromI64. +// +// This corresponds to wasm.OpcodeI32WrapI64 and equivalent to uint64(uint32(v)) in Go. +// +// The engines are expected to replace the 64-bit int on top of the stack +// with the corresponding 32-bit integer. +func newOperationI32WrapFromI64() unionOperation { + return unionOperation{Kind: operationKindI32WrapFromI64} +} + +// NewOperationITruncFromF is a constructor for unionOperation with operationKindITruncFromF. +// +// This corresponds to +// +// wasm.OpcodeI32TruncF32SName wasm.OpcodeI32TruncF32UName wasm.OpcodeI32TruncF64SName +// wasm.OpcodeI32TruncF64UName wasm.OpcodeI64TruncF32SName wasm.OpcodeI64TruncF32UName wasm.OpcodeI64TruncF64SName +// wasm.OpcodeI64TruncF64UName. wasm.OpcodeI32TruncSatF32SName wasm.OpcodeI32TruncSatF32UName +// wasm.OpcodeI32TruncSatF64SName wasm.OpcodeI32TruncSatF64UName wasm.OpcodeI64TruncSatF32SName +// wasm.OpcodeI64TruncSatF32UName wasm.OpcodeI64TruncSatF64SName wasm.OpcodeI64TruncSatF64UName +// +// See [1] and [2] for when we encounter undefined behavior in the WebAssembly specification if NewOperationITruncFromF.NonTrapping == false. +// To summarize, if the source float value is NaN or doesn't fit in the destination range of integers (incl. +=Inf), +// then the runtime behavior is undefined. In wazero, the engines are expected to exit the execution in these undefined cases with +// wasmruntime.ErrRuntimeInvalidConversionToInteger error. +// +// [1] https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#-hrefop-trunc-umathrmtruncmathsfu_m-n-z for unsigned integers. +// [2] https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#-hrefop-trunc-smathrmtruncmathsfs_m-n-z for signed integers. +// +// nonTrapping true if this conversion is "nontrapping" in the sense of the +// https://github.com/WebAssembly/spec/blob/ce4b6c4d47eb06098cc7ab2e81f24748da822f20/proposals/nontrapping-float-to-int-conversion/Overview.md +func newOperationITruncFromF(inputType float, outputType signedInt, nonTrapping bool) unionOperation { + return unionOperation{ + Kind: operationKindITruncFromF, + B1: byte(inputType), + B2: byte(outputType), + B3: nonTrapping, + } +} + +// NewOperationFConvertFromI is a constructor for unionOperation with operationKindFConvertFromI. +// +// This corresponds to +// +// wasm.OpcodeF32ConvertI32SName wasm.OpcodeF32ConvertI32UName wasm.OpcodeF32ConvertI64SName wasm.OpcodeF32ConvertI64UName +// wasm.OpcodeF64ConvertI32SName wasm.OpcodeF64ConvertI32UName wasm.OpcodeF64ConvertI64SName wasm.OpcodeF64ConvertI64UName +// +// and equivalent to float32(uint32(x)), float32(int32(x)), etc in Go. +func newOperationFConvertFromI(inputType signedInt, outputType float) unionOperation { + return unionOperation{ + Kind: operationKindFConvertFromI, + B1: byte(inputType), + B2: byte(outputType), + } +} + +// NewOperationF32DemoteFromF64 is a constructor for unionOperation with operationKindF32DemoteFromF64. +// +// This corresponds to wasm.OpcodeF32DemoteF64 and is equivalent float32(float64(v)). +func newOperationF32DemoteFromF64() unionOperation { + return unionOperation{Kind: operationKindF32DemoteFromF64} +} + +// NewOperationF64PromoteFromF32 is a constructor for unionOperation with operationKindF64PromoteFromF32. +// +// This corresponds to wasm.OpcodeF64PromoteF32 and is equivalent float64(float32(v)). +func newOperationF64PromoteFromF32() unionOperation { + return unionOperation{Kind: operationKindF64PromoteFromF32} +} + +// NewOperationI32ReinterpretFromF32 is a constructor for unionOperation with operationKindI32ReinterpretFromF32. +// +// This corresponds to wasm.OpcodeI32ReinterpretF32Name. +func newOperationI32ReinterpretFromF32() unionOperation { + return unionOperation{Kind: operationKindI32ReinterpretFromF32} +} + +// NewOperationI64ReinterpretFromF64 is a constructor for unionOperation with operationKindI64ReinterpretFromF64. +// +// This corresponds to wasm.OpcodeI64ReinterpretF64Name. +func newOperationI64ReinterpretFromF64() unionOperation { + return unionOperation{Kind: operationKindI64ReinterpretFromF64} +} + +// NewOperationF32ReinterpretFromI32 is a constructor for unionOperation with operationKindF32ReinterpretFromI32. +// +// This corresponds to wasm.OpcodeF32ReinterpretI32Name. +func newOperationF32ReinterpretFromI32() unionOperation { + return unionOperation{Kind: operationKindF32ReinterpretFromI32} +} + +// NewOperationF64ReinterpretFromI64 is a constructor for unionOperation with operationKindF64ReinterpretFromI64. +// +// This corresponds to wasm.OpcodeF64ReinterpretI64Name. +func newOperationF64ReinterpretFromI64() unionOperation { + return unionOperation{Kind: operationKindF64ReinterpretFromI64} +} + +// NewOperationExtend is a constructor for unionOperation with operationKindExtend. +// +// # This corresponds to wasm.OpcodeI64ExtendI32SName wasm.OpcodeI64ExtendI32UName +// +// The engines are expected to extend the 32-bit signed or unsigned int on top of the stack +// as a 64-bit integer of corresponding signedness. For unsigned case, this is just reinterpreting the +// underlying bit pattern as 64-bit integer. For signed case, this is sign-extension which preserves the +// original integer's sign. +func newOperationExtend(signed bool) unionOperation { + op := unionOperation{Kind: operationKindExtend} + if signed { + op.B1 = 1 + } + return op +} + +// NewOperationSignExtend32From8 is a constructor for unionOperation with operationKindSignExtend32From8. +// +// This corresponds to wasm.OpcodeI32Extend8SName. +// +// The engines are expected to sign-extend the first 8-bits of 32-bit in as signed 32-bit int. +func newOperationSignExtend32From8() unionOperation { + return unionOperation{Kind: operationKindSignExtend32From8} +} + +// NewOperationSignExtend32From16 is a constructor for unionOperation with operationKindSignExtend32From16. +// +// This corresponds to wasm.OpcodeI32Extend16SName. +// +// The engines are expected to sign-extend the first 16-bits of 32-bit in as signed 32-bit int. +func newOperationSignExtend32From16() unionOperation { + return unionOperation{Kind: operationKindSignExtend32From16} +} + +// NewOperationSignExtend64From8 is a constructor for unionOperation with operationKindSignExtend64From8. +// +// This corresponds to wasm.OpcodeI64Extend8SName. +// +// The engines are expected to sign-extend the first 8-bits of 64-bit in as signed 32-bit int. +func newOperationSignExtend64From8() unionOperation { + return unionOperation{Kind: operationKindSignExtend64From8} +} + +// NewOperationSignExtend64From16 is a constructor for unionOperation with operationKindSignExtend64From16. +// +// This corresponds to wasm.OpcodeI64Extend16SName. +// +// The engines are expected to sign-extend the first 16-bits of 64-bit in as signed 32-bit int. +func newOperationSignExtend64From16() unionOperation { + return unionOperation{Kind: operationKindSignExtend64From16} +} + +// NewOperationSignExtend64From32 is a constructor for unionOperation with operationKindSignExtend64From32. +// +// This corresponds to wasm.OpcodeI64Extend32SName. +// +// The engines are expected to sign-extend the first 32-bits of 64-bit in as signed 32-bit int. +func newOperationSignExtend64From32() unionOperation { + return unionOperation{Kind: operationKindSignExtend64From32} +} + +// NewOperationMemoryInit is a constructor for unionOperation with operationKindMemoryInit. +// +// This corresponds to wasm.OpcodeMemoryInitName. +// +// dataIndex is the index of the data instance in ModuleInstance.DataInstances +// by which this operation instantiates a part of the memory. +func newOperationMemoryInit(dataIndex uint32) unionOperation { + return unionOperation{Kind: operationKindMemoryInit, U1: uint64(dataIndex)} +} + +// NewOperationDataDrop implements Operation. +// +// This corresponds to wasm.OpcodeDataDropName. +// +// dataIndex is the index of the data instance in ModuleInstance.DataInstances +// which this operation drops. +func newOperationDataDrop(dataIndex uint32) unionOperation { + return unionOperation{Kind: operationKindDataDrop, U1: uint64(dataIndex)} +} + +// NewOperationMemoryCopy is a consuctor for unionOperation with operationKindMemoryCopy. +// +// This corresponds to wasm.OpcodeMemoryCopyName. +func newOperationMemoryCopy() unionOperation { + return unionOperation{Kind: operationKindMemoryCopy} +} + +// NewOperationMemoryFill is a consuctor for unionOperation with operationKindMemoryFill. +func newOperationMemoryFill() unionOperation { + return unionOperation{Kind: operationKindMemoryFill} +} + +// NewOperationTableInit is a constructor for unionOperation with operationKindTableInit. +// +// This corresponds to wasm.OpcodeTableInitName. +// +// elemIndex is the index of the element by which this operation initializes a part of the table. +// tableIndex is the index of the table on which this operation initialize by the target element. +func newOperationTableInit(elemIndex, tableIndex uint32) unionOperation { + return unionOperation{Kind: operationKindTableInit, U1: uint64(elemIndex), U2: uint64(tableIndex)} +} + +// NewOperationElemDrop is a constructor for unionOperation with operationKindElemDrop. +// +// This corresponds to wasm.OpcodeElemDropName. +// +// elemIndex is the index of the element which this operation drops. +func newOperationElemDrop(elemIndex uint32) unionOperation { + return unionOperation{Kind: operationKindElemDrop, U1: uint64(elemIndex)} +} + +// NewOperationTableCopy implements Operation. +// +// This corresponds to wasm.OpcodeTableCopyName. +func newOperationTableCopy(srcTableIndex, dstTableIndex uint32) unionOperation { + return unionOperation{Kind: operationKindTableCopy, U1: uint64(srcTableIndex), U2: uint64(dstTableIndex)} +} + +// NewOperationRefFunc constructor for unionOperation with operationKindRefFunc. +// +// This corresponds to wasm.OpcodeRefFuncName, and engines are expected to +// push the opaque pointer value of engine specific func for the given FunctionIndex. +// +// Note: in wazero, we express any reference types (funcref or externref) as opaque pointers which is uint64. +// Therefore, the engine implementations emit instructions to push the address of *function onto the stack. +func newOperationRefFunc(functionIndex uint32) unionOperation { + return unionOperation{Kind: operationKindRefFunc, U1: uint64(functionIndex)} +} + +// NewOperationTableGet constructor for unionOperation with operationKindTableGet. +// +// This corresponds to wasm.OpcodeTableGetName. +func newOperationTableGet(tableIndex uint32) unionOperation { + return unionOperation{Kind: operationKindTableGet, U1: uint64(tableIndex)} +} + +// NewOperationTableSet constructor for unionOperation with operationKindTableSet. +// +// This corresponds to wasm.OpcodeTableSetName. +func newOperationTableSet(tableIndex uint32) unionOperation { + return unionOperation{Kind: operationKindTableSet, U1: uint64(tableIndex)} +} + +// NewOperationTableSize constructor for unionOperation with operationKindTableSize. +// +// This corresponds to wasm.OpcodeTableSizeName. +func newOperationTableSize(tableIndex uint32) unionOperation { + return unionOperation{Kind: operationKindTableSize, U1: uint64(tableIndex)} +} + +// NewOperationTableGrow constructor for unionOperation with operationKindTableGrow. +// +// This corresponds to wasm.OpcodeTableGrowName. +func newOperationTableGrow(tableIndex uint32) unionOperation { + return unionOperation{Kind: operationKindTableGrow, U1: uint64(tableIndex)} +} + +// NewOperationTableFill constructor for unionOperation with operationKindTableFill. +// +// This corresponds to wasm.OpcodeTableFillName. +func newOperationTableFill(tableIndex uint32) unionOperation { + return unionOperation{Kind: operationKindTableFill, U1: uint64(tableIndex)} +} + +// NewOperationV128Const constructor for unionOperation with operationKindV128Const +func newOperationV128Const(lo, hi uint64) unionOperation { + return unionOperation{Kind: operationKindV128Const, U1: lo, U2: hi} +} + +// shape corresponds to a shape of v128 values. +// https://webassembly.github.io/spec/core/syntax/instructions.html#syntax-shape +type shape = byte + +const ( + shapeI8x16 shape = iota + shapeI16x8 + shapeI32x4 + shapeI64x2 + shapeF32x4 + shapeF64x2 +) + +func shapeName(s shape) (ret string) { + switch s { + case shapeI8x16: + ret = "I8x16" + case shapeI16x8: + ret = "I16x8" + case shapeI32x4: + ret = "I32x4" + case shapeI64x2: + ret = "I64x2" + case shapeF32x4: + ret = "F32x4" + case shapeF64x2: + ret = "F64x2" + } + return +} + +// NewOperationV128Add constructor for unionOperation with operationKindV128Add. +// +// This corresponds to wasm.OpcodeVecI8x16AddName wasm.OpcodeVecI16x8AddName wasm.OpcodeVecI32x4AddName +// +// wasm.OpcodeVecI64x2AddName wasm.OpcodeVecF32x4AddName wasm.OpcodeVecF64x2AddName +func newOperationV128Add(shape shape) unionOperation { + return unionOperation{Kind: operationKindV128Add, B1: shape} +} + +// NewOperationV128Sub constructor for unionOperation with operationKindV128Sub. +// +// This corresponds to wasm.OpcodeVecI8x16SubName wasm.OpcodeVecI16x8SubName wasm.OpcodeVecI32x4SubName +// +// wasm.OpcodeVecI64x2SubName wasm.OpcodeVecF32x4SubName wasm.OpcodeVecF64x2SubName +func newOperationV128Sub(shape shape) unionOperation { + return unionOperation{Kind: operationKindV128Sub, B1: shape} +} + +// v128LoadType represents a type of wasm.OpcodeVecV128Load* instructions. +type v128LoadType = byte + +const ( + // v128LoadType128 corresponds to wasm.OpcodeVecV128LoadName. + v128LoadType128 v128LoadType = iota + // v128LoadType8x8s corresponds to wasm.OpcodeVecV128Load8x8SName. + v128LoadType8x8s + // v128LoadType8x8u corresponds to wasm.OpcodeVecV128Load8x8UName. + v128LoadType8x8u + // v128LoadType16x4s corresponds to wasm.OpcodeVecV128Load16x4SName + v128LoadType16x4s + // v128LoadType16x4u corresponds to wasm.OpcodeVecV128Load16x4UName + v128LoadType16x4u + // v128LoadType32x2s corresponds to wasm.OpcodeVecV128Load32x2SName + v128LoadType32x2s + // v128LoadType32x2u corresponds to wasm.OpcodeVecV128Load32x2UName + v128LoadType32x2u + // v128LoadType8Splat corresponds to wasm.OpcodeVecV128Load8SplatName + v128LoadType8Splat + // v128LoadType16Splat corresponds to wasm.OpcodeVecV128Load16SplatName + v128LoadType16Splat + // v128LoadType32Splat corresponds to wasm.OpcodeVecV128Load32SplatName + v128LoadType32Splat + // v128LoadType64Splat corresponds to wasm.OpcodeVecV128Load64SplatName + v128LoadType64Splat + // v128LoadType32zero corresponds to wasm.OpcodeVecV128Load32zeroName + v128LoadType32zero + // v128LoadType64zero corresponds to wasm.OpcodeVecV128Load64zeroName + v128LoadType64zero +) + +// NewOperationV128Load is a constructor for unionOperation with operationKindV128Load. +// +// This corresponds to +// +// wasm.OpcodeVecV128LoadName wasm.OpcodeVecV128Load8x8SName wasm.OpcodeVecV128Load8x8UName +// wasm.OpcodeVecV128Load16x4SName wasm.OpcodeVecV128Load16x4UName wasm.OpcodeVecV128Load32x2SName +// wasm.OpcodeVecV128Load32x2UName wasm.OpcodeVecV128Load8SplatName wasm.OpcodeVecV128Load16SplatName +// wasm.OpcodeVecV128Load32SplatName wasm.OpcodeVecV128Load64SplatName wasm.OpcodeVecV128Load32zeroName +// wasm.OpcodeVecV128Load64zeroName +func newOperationV128Load(loadType v128LoadType, arg memoryArg) unionOperation { + return unionOperation{Kind: operationKindV128Load, B1: loadType, U1: uint64(arg.Alignment), U2: uint64(arg.Offset)} +} + +// NewOperationV128LoadLane is a constructor for unionOperation with operationKindV128LoadLane. +// +// This corresponds to wasm.OpcodeVecV128Load8LaneName wasm.OpcodeVecV128Load16LaneName +// +// wasm.OpcodeVecV128Load32LaneName wasm.OpcodeVecV128Load64LaneName. +// +// laneIndex is >=0 && <(128/LaneSize). +// laneSize is either 8, 16, 32, or 64. +func newOperationV128LoadLane(laneIndex, laneSize byte, arg memoryArg) unionOperation { + return unionOperation{Kind: operationKindV128LoadLane, B1: laneSize, B2: laneIndex, U1: uint64(arg.Alignment), U2: uint64(arg.Offset)} +} + +// NewOperationV128Store is a constructor for unionOperation with operationKindV128Store. +// +// This corresponds to wasm.OpcodeVecV128Load8LaneName wasm.OpcodeVecV128Load16LaneName +// +// wasm.OpcodeVecV128Load32LaneName wasm.OpcodeVecV128Load64LaneName. +func newOperationV128Store(arg memoryArg) unionOperation { + return unionOperation{ + Kind: operationKindV128Store, + U1: uint64(arg.Alignment), + U2: uint64(arg.Offset), + } +} + +// NewOperationV128StoreLane implements Operation. +// +// This corresponds to wasm.OpcodeVecV128Load8LaneName wasm.OpcodeVecV128Load16LaneName +// +// wasm.OpcodeVecV128Load32LaneName wasm.OpcodeVecV128Load64LaneName. +// +// laneIndex is >=0 && <(128/LaneSize). +// laneSize is either 8, 16, 32, or 64. +func newOperationV128StoreLane(laneIndex byte, laneSize byte, arg memoryArg) unionOperation { + return unionOperation{ + Kind: operationKindV128StoreLane, + B1: laneSize, + B2: laneIndex, + U1: uint64(arg.Alignment), + U2: uint64(arg.Offset), + } +} + +// NewOperationV128ExtractLane is a constructor for unionOperation with operationKindV128ExtractLane. +// +// This corresponds to +// +// wasm.OpcodeVecI8x16ExtractLaneSName wasm.OpcodeVecI8x16ExtractLaneUName +// wasm.OpcodeVecI16x8ExtractLaneSName wasm.OpcodeVecI16x8ExtractLaneUName +// wasm.OpcodeVecI32x4ExtractLaneName wasm.OpcodeVecI64x2ExtractLaneName +// wasm.OpcodeVecF32x4ExtractLaneName wasm.OpcodeVecF64x2ExtractLaneName. +// +// laneIndex is >=0 && =0 && = l { + return nil, fmt.Errorf("invalid local index for local.get %d >= %d", index, l) + } + var t wasm.ValueType + if index < inputLen { + t = c.sig.Params[index] + } else { + t = c.localTypes[index-inputLen] + } + return wasmValueTypeToUnsignedOutSignature(t), nil + case wasm.OpcodeLocalSet: + inputLen := uint32(len(c.sig.Params)) + if l := uint32(len(c.localTypes)) + inputLen; index >= l { + return nil, fmt.Errorf("invalid local index for local.get %d >= %d", index, l) + } + var t wasm.ValueType + if index < inputLen { + t = c.sig.Params[index] + } else { + t = c.localTypes[index-inputLen] + } + return wasmValueTypeToUnsignedInSignature(t), nil + case wasm.OpcodeLocalTee: + inputLen := uint32(len(c.sig.Params)) + if l := uint32(len(c.localTypes)) + inputLen; index >= l { + return nil, fmt.Errorf("invalid local index for local.get %d >= %d", index, l) + } + var t wasm.ValueType + if index < inputLen { + t = c.sig.Params[index] + } else { + t = c.localTypes[index-inputLen] + } + return wasmValueTypeToUnsignedInOutSignature(t), nil + case wasm.OpcodeGlobalGet: + if len(c.globals) <= int(index) { + return nil, fmt.Errorf("invalid global index for global.get %d >= %d", index, len(c.globals)) + } + return wasmValueTypeToUnsignedOutSignature(c.globals[index].ValType), nil + case wasm.OpcodeGlobalSet: + if len(c.globals) <= int(index) { + return nil, fmt.Errorf("invalid global index for global.get %d >= %d", index, len(c.globals)) + } + return wasmValueTypeToUnsignedInSignature(c.globals[index].ValType), nil + case wasm.OpcodeI32Load: + return signature_I32_I32, nil + case wasm.OpcodeI64Load: + return signature_I32_I64, nil + case wasm.OpcodeF32Load: + return signature_I32_F32, nil + case wasm.OpcodeF64Load: + return signature_I32_F64, nil + case wasm.OpcodeI32Load8S, wasm.OpcodeI32Load8U, wasm.OpcodeI32Load16S, wasm.OpcodeI32Load16U: + return signature_I32_I32, nil + case wasm.OpcodeI64Load8S, wasm.OpcodeI64Load8U, wasm.OpcodeI64Load16S, wasm.OpcodeI64Load16U, + wasm.OpcodeI64Load32S, wasm.OpcodeI64Load32U: + return signature_I32_I64, nil + case wasm.OpcodeI32Store: + return signature_I32I32_None, nil + case wasm.OpcodeI64Store: + return signature_I32I64_None, nil + case wasm.OpcodeF32Store: + return signature_I32F32_None, nil + case wasm.OpcodeF64Store: + return signature_I32F64_None, nil + case wasm.OpcodeI32Store8: + return signature_I32I32_None, nil + case wasm.OpcodeI32Store16: + return signature_I32I32_None, nil + case wasm.OpcodeI64Store8: + return signature_I32I64_None, nil + case wasm.OpcodeI64Store16: + return signature_I32I64_None, nil + case wasm.OpcodeI64Store32: + return signature_I32I64_None, nil + case wasm.OpcodeMemorySize: + return signature_None_I32, nil + case wasm.OpcodeMemoryGrow: + return signature_I32_I32, nil + case wasm.OpcodeI32Const: + return signature_None_I32, nil + case wasm.OpcodeI64Const: + return signature_None_I64, nil + case wasm.OpcodeF32Const: + return signature_None_F32, nil + case wasm.OpcodeF64Const: + return signature_None_F64, nil + case wasm.OpcodeI32Eqz: + return signature_I32_I32, nil + case wasm.OpcodeI32Eq, wasm.OpcodeI32Ne, wasm.OpcodeI32LtS, + wasm.OpcodeI32LtU, wasm.OpcodeI32GtS, wasm.OpcodeI32GtU, + wasm.OpcodeI32LeS, wasm.OpcodeI32LeU, wasm.OpcodeI32GeS, + wasm.OpcodeI32GeU: + return signature_I32I32_I32, nil + case wasm.OpcodeI64Eqz: + return signature_I64_I32, nil + case wasm.OpcodeI64Eq, wasm.OpcodeI64Ne, wasm.OpcodeI64LtS, + wasm.OpcodeI64LtU, wasm.OpcodeI64GtS, wasm.OpcodeI64GtU, + wasm.OpcodeI64LeS, wasm.OpcodeI64LeU, wasm.OpcodeI64GeS, + wasm.OpcodeI64GeU: + return signature_I64I64_I32, nil + case wasm.OpcodeF32Eq, wasm.OpcodeF32Ne, wasm.OpcodeF32Lt, + wasm.OpcodeF32Gt, wasm.OpcodeF32Le, wasm.OpcodeF32Ge: + return signature_F32F32_I32, nil + case wasm.OpcodeF64Eq, wasm.OpcodeF64Ne, wasm.OpcodeF64Lt, + wasm.OpcodeF64Gt, wasm.OpcodeF64Le, wasm.OpcodeF64Ge: + return signature_F64F64_I32, nil + case wasm.OpcodeI32Clz, wasm.OpcodeI32Ctz, wasm.OpcodeI32Popcnt: + return signature_I32_I32, nil + case wasm.OpcodeI32Add, wasm.OpcodeI32Sub, wasm.OpcodeI32Mul, + wasm.OpcodeI32DivS, wasm.OpcodeI32DivU, wasm.OpcodeI32RemS, + wasm.OpcodeI32RemU, wasm.OpcodeI32And, wasm.OpcodeI32Or, + wasm.OpcodeI32Xor, wasm.OpcodeI32Shl, wasm.OpcodeI32ShrS, + wasm.OpcodeI32ShrU, wasm.OpcodeI32Rotl, wasm.OpcodeI32Rotr: + return signature_I32I32_I32, nil + case wasm.OpcodeI64Clz, wasm.OpcodeI64Ctz, wasm.OpcodeI64Popcnt: + return signature_I64_I64, nil + case wasm.OpcodeI64Add, wasm.OpcodeI64Sub, wasm.OpcodeI64Mul, + wasm.OpcodeI64DivS, wasm.OpcodeI64DivU, wasm.OpcodeI64RemS, + wasm.OpcodeI64RemU, wasm.OpcodeI64And, wasm.OpcodeI64Or, + wasm.OpcodeI64Xor, wasm.OpcodeI64Shl, wasm.OpcodeI64ShrS, + wasm.OpcodeI64ShrU, wasm.OpcodeI64Rotl, wasm.OpcodeI64Rotr: + return signature_I64I64_I64, nil + case wasm.OpcodeF32Abs, wasm.OpcodeF32Neg, wasm.OpcodeF32Ceil, + wasm.OpcodeF32Floor, wasm.OpcodeF32Trunc, wasm.OpcodeF32Nearest, + wasm.OpcodeF32Sqrt: + return signature_F32_F32, nil + case wasm.OpcodeF32Add, wasm.OpcodeF32Sub, wasm.OpcodeF32Mul, + wasm.OpcodeF32Div, wasm.OpcodeF32Min, wasm.OpcodeF32Max, + wasm.OpcodeF32Copysign: + return signature_F32F32_F32, nil + case wasm.OpcodeF64Abs, wasm.OpcodeF64Neg, wasm.OpcodeF64Ceil, + wasm.OpcodeF64Floor, wasm.OpcodeF64Trunc, wasm.OpcodeF64Nearest, + wasm.OpcodeF64Sqrt: + return signature_F64_F64, nil + case wasm.OpcodeF64Add, wasm.OpcodeF64Sub, wasm.OpcodeF64Mul, + wasm.OpcodeF64Div, wasm.OpcodeF64Min, wasm.OpcodeF64Max, + wasm.OpcodeF64Copysign: + return signature_F64F64_F64, nil + case wasm.OpcodeI32WrapI64: + return signature_I64_I32, nil + case wasm.OpcodeI32TruncF32S, wasm.OpcodeI32TruncF32U: + return signature_F32_I32, nil + case wasm.OpcodeI32TruncF64S, wasm.OpcodeI32TruncF64U: + return signature_F64_I32, nil + case wasm.OpcodeI64ExtendI32S, wasm.OpcodeI64ExtendI32U: + return signature_I32_I64, nil + case wasm.OpcodeI64TruncF32S, wasm.OpcodeI64TruncF32U: + return signature_F32_I64, nil + case wasm.OpcodeI64TruncF64S, wasm.OpcodeI64TruncF64U: + return signature_F64_I64, nil + case wasm.OpcodeF32ConvertI32S, wasm.OpcodeF32ConvertI32U: + return signature_I32_F32, nil + case wasm.OpcodeF32ConvertI64S, wasm.OpcodeF32ConvertI64U: + return signature_I64_F32, nil + case wasm.OpcodeF32DemoteF64: + return signature_F64_F32, nil + case wasm.OpcodeF64ConvertI32S, wasm.OpcodeF64ConvertI32U: + return signature_I32_F64, nil + case wasm.OpcodeF64ConvertI64S, wasm.OpcodeF64ConvertI64U: + return signature_I64_F64, nil + case wasm.OpcodeF64PromoteF32: + return signature_F32_F64, nil + case wasm.OpcodeI32ReinterpretF32: + return signature_F32_I32, nil + case wasm.OpcodeI64ReinterpretF64: + return signature_F64_I64, nil + case wasm.OpcodeF32ReinterpretI32: + return signature_I32_F32, nil + case wasm.OpcodeF64ReinterpretI64: + return signature_I64_F64, nil + case wasm.OpcodeI32Extend8S, wasm.OpcodeI32Extend16S: + return signature_I32_I32, nil + case wasm.OpcodeI64Extend8S, wasm.OpcodeI64Extend16S, wasm.OpcodeI64Extend32S: + return signature_I64_I64, nil + case wasm.OpcodeTableGet: + // table.get takes table's offset and pushes the ref type value of opaque pointer as i64 value onto the stack. + return signature_I32_I64, nil + case wasm.OpcodeTableSet: + // table.set takes table's offset and the ref type value of opaque pointer as i64 value. + return signature_I32I64_None, nil + case wasm.OpcodeRefFunc: + // ref.func is translated as pushing the compiled function's opaque pointer (uint64) at interpreterir layer. + return signature_None_I64, nil + case wasm.OpcodeRefIsNull: + // ref.is_null is translated as checking if the uint64 on the top of the stack (opaque pointer) is zero or not. + return signature_I64_I32, nil + case wasm.OpcodeRefNull: + // ref.null is translated as i64.const 0. + return signature_None_I64, nil + case wasm.OpcodeMiscPrefix: + switch miscOp := c.body[c.pc+1]; miscOp { + case wasm.OpcodeMiscI32TruncSatF32S, wasm.OpcodeMiscI32TruncSatF32U: + return signature_F32_I32, nil + case wasm.OpcodeMiscI32TruncSatF64S, wasm.OpcodeMiscI32TruncSatF64U: + return signature_F64_I32, nil + case wasm.OpcodeMiscI64TruncSatF32S, wasm.OpcodeMiscI64TruncSatF32U: + return signature_F32_I64, nil + case wasm.OpcodeMiscI64TruncSatF64S, wasm.OpcodeMiscI64TruncSatF64U: + return signature_F64_I64, nil + case wasm.OpcodeMiscMemoryInit, wasm.OpcodeMiscMemoryCopy, wasm.OpcodeMiscMemoryFill, + wasm.OpcodeMiscTableInit, wasm.OpcodeMiscTableCopy: + return signature_I32I32I32_None, nil + case wasm.OpcodeMiscDataDrop, wasm.OpcodeMiscElemDrop: + return signature_None_None, nil + case wasm.OpcodeMiscTableGrow: + return signature_I64I32_I32, nil + case wasm.OpcodeMiscTableSize: + return signature_None_I32, nil + case wasm.OpcodeMiscTableFill: + return signature_I32I64I32_None, nil + default: + return nil, fmt.Errorf("unsupported misc instruction in interpreterir: 0x%x", op) + } + case wasm.OpcodeVecPrefix: + switch vecOp := c.body[c.pc+1]; vecOp { + case wasm.OpcodeVecV128Const: + return signature_None_V128, nil + case wasm.OpcodeVecV128Load, wasm.OpcodeVecV128Load8x8s, wasm.OpcodeVecV128Load8x8u, + wasm.OpcodeVecV128Load16x4s, wasm.OpcodeVecV128Load16x4u, wasm.OpcodeVecV128Load32x2s, + wasm.OpcodeVecV128Load32x2u, wasm.OpcodeVecV128Load8Splat, wasm.OpcodeVecV128Load16Splat, + wasm.OpcodeVecV128Load32Splat, wasm.OpcodeVecV128Load64Splat, wasm.OpcodeVecV128Load32zero, + wasm.OpcodeVecV128Load64zero: + return signature_I32_V128, nil + case wasm.OpcodeVecV128Load8Lane, wasm.OpcodeVecV128Load16Lane, + wasm.OpcodeVecV128Load32Lane, wasm.OpcodeVecV128Load64Lane: + return signature_I32V128_V128, nil + case wasm.OpcodeVecV128Store, + wasm.OpcodeVecV128Store8Lane, + wasm.OpcodeVecV128Store16Lane, + wasm.OpcodeVecV128Store32Lane, + wasm.OpcodeVecV128Store64Lane: + return signature_I32V128_None, nil + case wasm.OpcodeVecI8x16ExtractLaneS, + wasm.OpcodeVecI8x16ExtractLaneU, + wasm.OpcodeVecI16x8ExtractLaneS, + wasm.OpcodeVecI16x8ExtractLaneU, + wasm.OpcodeVecI32x4ExtractLane: + return signature_V128_I32, nil + case wasm.OpcodeVecI64x2ExtractLane: + return signature_V128_I64, nil + case wasm.OpcodeVecF32x4ExtractLane: + return signature_V128_F32, nil + case wasm.OpcodeVecF64x2ExtractLane: + return signature_V128_F64, nil + case wasm.OpcodeVecI8x16ReplaceLane, wasm.OpcodeVecI16x8ReplaceLane, wasm.OpcodeVecI32x4ReplaceLane, + wasm.OpcodeVecI8x16Shl, wasm.OpcodeVecI8x16ShrS, wasm.OpcodeVecI8x16ShrU, + wasm.OpcodeVecI16x8Shl, wasm.OpcodeVecI16x8ShrS, wasm.OpcodeVecI16x8ShrU, + wasm.OpcodeVecI32x4Shl, wasm.OpcodeVecI32x4ShrS, wasm.OpcodeVecI32x4ShrU, + wasm.OpcodeVecI64x2Shl, wasm.OpcodeVecI64x2ShrS, wasm.OpcodeVecI64x2ShrU: + return signature_V128I32_V128, nil + case wasm.OpcodeVecI64x2ReplaceLane: + return signature_V128I64_V128, nil + case wasm.OpcodeVecF32x4ReplaceLane: + return signature_V128F32_V128, nil + case wasm.OpcodeVecF64x2ReplaceLane: + return signature_V128F64_V128, nil + case wasm.OpcodeVecI8x16Splat, + wasm.OpcodeVecI16x8Splat, + wasm.OpcodeVecI32x4Splat: + return signature_I32_V128, nil + case wasm.OpcodeVecI64x2Splat: + return signature_I64_V128, nil + case wasm.OpcodeVecF32x4Splat: + return signature_F32_V128, nil + case wasm.OpcodeVecF64x2Splat: + return signature_F64_V128, nil + case wasm.OpcodeVecV128i8x16Shuffle, wasm.OpcodeVecI8x16Swizzle, wasm.OpcodeVecV128And, wasm.OpcodeVecV128Or, wasm.OpcodeVecV128Xor, wasm.OpcodeVecV128AndNot: + return signature_V128V128_V128, nil + case wasm.OpcodeVecI8x16AllTrue, wasm.OpcodeVecI16x8AllTrue, wasm.OpcodeVecI32x4AllTrue, wasm.OpcodeVecI64x2AllTrue, + wasm.OpcodeVecV128AnyTrue, + wasm.OpcodeVecI8x16BitMask, wasm.OpcodeVecI16x8BitMask, wasm.OpcodeVecI32x4BitMask, wasm.OpcodeVecI64x2BitMask: + return signature_V128_I32, nil + case wasm.OpcodeVecV128Not, wasm.OpcodeVecI8x16Neg, wasm.OpcodeVecI16x8Neg, wasm.OpcodeVecI32x4Neg, wasm.OpcodeVecI64x2Neg, + wasm.OpcodeVecF32x4Neg, wasm.OpcodeVecF64x2Neg, wasm.OpcodeVecF32x4Sqrt, wasm.OpcodeVecF64x2Sqrt, + wasm.OpcodeVecI8x16Abs, wasm.OpcodeVecI8x16Popcnt, wasm.OpcodeVecI16x8Abs, wasm.OpcodeVecI32x4Abs, wasm.OpcodeVecI64x2Abs, + wasm.OpcodeVecF32x4Abs, wasm.OpcodeVecF64x2Abs, + wasm.OpcodeVecF32x4Ceil, wasm.OpcodeVecF32x4Floor, wasm.OpcodeVecF32x4Trunc, wasm.OpcodeVecF32x4Nearest, + wasm.OpcodeVecF64x2Ceil, wasm.OpcodeVecF64x2Floor, wasm.OpcodeVecF64x2Trunc, wasm.OpcodeVecF64x2Nearest, + wasm.OpcodeVecI16x8ExtendLowI8x16S, wasm.OpcodeVecI16x8ExtendHighI8x16S, wasm.OpcodeVecI16x8ExtendLowI8x16U, wasm.OpcodeVecI16x8ExtendHighI8x16U, + wasm.OpcodeVecI32x4ExtendLowI16x8S, wasm.OpcodeVecI32x4ExtendHighI16x8S, wasm.OpcodeVecI32x4ExtendLowI16x8U, wasm.OpcodeVecI32x4ExtendHighI16x8U, + wasm.OpcodeVecI64x2ExtendLowI32x4S, wasm.OpcodeVecI64x2ExtendHighI32x4S, wasm.OpcodeVecI64x2ExtendLowI32x4U, wasm.OpcodeVecI64x2ExtendHighI32x4U, + wasm.OpcodeVecI16x8ExtaddPairwiseI8x16S, wasm.OpcodeVecI16x8ExtaddPairwiseI8x16U, wasm.OpcodeVecI32x4ExtaddPairwiseI16x8S, wasm.OpcodeVecI32x4ExtaddPairwiseI16x8U, + wasm.OpcodeVecF64x2PromoteLowF32x4Zero, wasm.OpcodeVecF32x4DemoteF64x2Zero, + wasm.OpcodeVecF32x4ConvertI32x4S, wasm.OpcodeVecF32x4ConvertI32x4U, + wasm.OpcodeVecF64x2ConvertLowI32x4S, wasm.OpcodeVecF64x2ConvertLowI32x4U, + wasm.OpcodeVecI32x4TruncSatF32x4S, wasm.OpcodeVecI32x4TruncSatF32x4U, + wasm.OpcodeVecI32x4TruncSatF64x2SZero, wasm.OpcodeVecI32x4TruncSatF64x2UZero: + return signature_V128_V128, nil + case wasm.OpcodeVecV128Bitselect: + return signature_V128V128V128_V32, nil + case wasm.OpcodeVecI8x16Eq, wasm.OpcodeVecI8x16Ne, wasm.OpcodeVecI8x16LtS, wasm.OpcodeVecI8x16LtU, wasm.OpcodeVecI8x16GtS, + wasm.OpcodeVecI8x16GtU, wasm.OpcodeVecI8x16LeS, wasm.OpcodeVecI8x16LeU, wasm.OpcodeVecI8x16GeS, wasm.OpcodeVecI8x16GeU, + wasm.OpcodeVecI16x8Eq, wasm.OpcodeVecI16x8Ne, wasm.OpcodeVecI16x8LtS, wasm.OpcodeVecI16x8LtU, wasm.OpcodeVecI16x8GtS, + wasm.OpcodeVecI16x8GtU, wasm.OpcodeVecI16x8LeS, wasm.OpcodeVecI16x8LeU, wasm.OpcodeVecI16x8GeS, wasm.OpcodeVecI16x8GeU, + wasm.OpcodeVecI32x4Eq, wasm.OpcodeVecI32x4Ne, wasm.OpcodeVecI32x4LtS, wasm.OpcodeVecI32x4LtU, wasm.OpcodeVecI32x4GtS, + wasm.OpcodeVecI32x4GtU, wasm.OpcodeVecI32x4LeS, wasm.OpcodeVecI32x4LeU, wasm.OpcodeVecI32x4GeS, wasm.OpcodeVecI32x4GeU, + wasm.OpcodeVecI64x2Eq, wasm.OpcodeVecI64x2Ne, wasm.OpcodeVecI64x2LtS, wasm.OpcodeVecI64x2GtS, wasm.OpcodeVecI64x2LeS, + wasm.OpcodeVecI64x2GeS, wasm.OpcodeVecF32x4Eq, wasm.OpcodeVecF32x4Ne, wasm.OpcodeVecF32x4Lt, wasm.OpcodeVecF32x4Gt, + wasm.OpcodeVecF32x4Le, wasm.OpcodeVecF32x4Ge, wasm.OpcodeVecF64x2Eq, wasm.OpcodeVecF64x2Ne, wasm.OpcodeVecF64x2Lt, + wasm.OpcodeVecF64x2Gt, wasm.OpcodeVecF64x2Le, wasm.OpcodeVecF64x2Ge, + wasm.OpcodeVecI8x16Add, wasm.OpcodeVecI8x16AddSatS, wasm.OpcodeVecI8x16AddSatU, wasm.OpcodeVecI8x16Sub, + wasm.OpcodeVecI8x16SubSatS, wasm.OpcodeVecI8x16SubSatU, + wasm.OpcodeVecI16x8Add, wasm.OpcodeVecI16x8AddSatS, wasm.OpcodeVecI16x8AddSatU, wasm.OpcodeVecI16x8Sub, + wasm.OpcodeVecI16x8SubSatS, wasm.OpcodeVecI16x8SubSatU, wasm.OpcodeVecI16x8Mul, + wasm.OpcodeVecI32x4Add, wasm.OpcodeVecI32x4Sub, wasm.OpcodeVecI32x4Mul, + wasm.OpcodeVecI64x2Add, wasm.OpcodeVecI64x2Sub, wasm.OpcodeVecI64x2Mul, + wasm.OpcodeVecF32x4Add, wasm.OpcodeVecF32x4Sub, wasm.OpcodeVecF32x4Mul, wasm.OpcodeVecF32x4Div, + wasm.OpcodeVecF64x2Add, wasm.OpcodeVecF64x2Sub, wasm.OpcodeVecF64x2Mul, wasm.OpcodeVecF64x2Div, + wasm.OpcodeVecI8x16MinS, wasm.OpcodeVecI8x16MinU, wasm.OpcodeVecI8x16MaxS, wasm.OpcodeVecI8x16MaxU, wasm.OpcodeVecI8x16AvgrU, + wasm.OpcodeVecI16x8MinS, wasm.OpcodeVecI16x8MinU, wasm.OpcodeVecI16x8MaxS, wasm.OpcodeVecI16x8MaxU, wasm.OpcodeVecI16x8AvgrU, + wasm.OpcodeVecI32x4MinS, wasm.OpcodeVecI32x4MinU, wasm.OpcodeVecI32x4MaxS, wasm.OpcodeVecI32x4MaxU, + wasm.OpcodeVecF32x4Min, wasm.OpcodeVecF32x4Max, wasm.OpcodeVecF64x2Min, wasm.OpcodeVecF64x2Max, + wasm.OpcodeVecF32x4Pmin, wasm.OpcodeVecF32x4Pmax, wasm.OpcodeVecF64x2Pmin, wasm.OpcodeVecF64x2Pmax, + wasm.OpcodeVecI16x8Q15mulrSatS, + wasm.OpcodeVecI16x8ExtMulLowI8x16S, wasm.OpcodeVecI16x8ExtMulHighI8x16S, wasm.OpcodeVecI16x8ExtMulLowI8x16U, wasm.OpcodeVecI16x8ExtMulHighI8x16U, + wasm.OpcodeVecI32x4ExtMulLowI16x8S, wasm.OpcodeVecI32x4ExtMulHighI16x8S, wasm.OpcodeVecI32x4ExtMulLowI16x8U, wasm.OpcodeVecI32x4ExtMulHighI16x8U, + wasm.OpcodeVecI64x2ExtMulLowI32x4S, wasm.OpcodeVecI64x2ExtMulHighI32x4S, wasm.OpcodeVecI64x2ExtMulLowI32x4U, wasm.OpcodeVecI64x2ExtMulHighI32x4U, + wasm.OpcodeVecI32x4DotI16x8S, + wasm.OpcodeVecI8x16NarrowI16x8S, wasm.OpcodeVecI8x16NarrowI16x8U, wasm.OpcodeVecI16x8NarrowI32x4S, wasm.OpcodeVecI16x8NarrowI32x4U: + return signature_V128V128_V128, nil + default: + return nil, fmt.Errorf("unsupported vector instruction in interpreterir: %s", wasm.VectorInstructionName(vecOp)) + } + case wasm.OpcodeAtomicPrefix: + switch atomicOp := c.body[c.pc+1]; atomicOp { + case wasm.OpcodeAtomicMemoryNotify: + return signature_I32I32_I32, nil + case wasm.OpcodeAtomicMemoryWait32: + return signature_I32I32I64_I32, nil + case wasm.OpcodeAtomicMemoryWait64: + return signature_I32I64I64_I32, nil + case wasm.OpcodeAtomicFence: + return signature_None_None, nil + case wasm.OpcodeAtomicI32Load, wasm.OpcodeAtomicI32Load8U, wasm.OpcodeAtomicI32Load16U: + return signature_I32_I32, nil + case wasm.OpcodeAtomicI64Load, wasm.OpcodeAtomicI64Load8U, wasm.OpcodeAtomicI64Load16U, wasm.OpcodeAtomicI64Load32U: + return signature_I32_I64, nil + case wasm.OpcodeAtomicI32Store, wasm.OpcodeAtomicI32Store8, wasm.OpcodeAtomicI32Store16: + return signature_I32I32_None, nil + case wasm.OpcodeAtomicI64Store, wasm.OpcodeAtomicI64Store8, wasm.OpcodeAtomicI64Store16, wasm.OpcodeAtomicI64Store32: + return signature_I32I64_None, nil + case wasm.OpcodeAtomicI32RmwAdd, wasm.OpcodeAtomicI32RmwSub, wasm.OpcodeAtomicI32RmwAnd, wasm.OpcodeAtomicI32RmwOr, wasm.OpcodeAtomicI32RmwXor, wasm.OpcodeAtomicI32RmwXchg, + wasm.OpcodeAtomicI32Rmw8AddU, wasm.OpcodeAtomicI32Rmw8SubU, wasm.OpcodeAtomicI32Rmw8AndU, wasm.OpcodeAtomicI32Rmw8OrU, wasm.OpcodeAtomicI32Rmw8XorU, wasm.OpcodeAtomicI32Rmw8XchgU, + wasm.OpcodeAtomicI32Rmw16AddU, wasm.OpcodeAtomicI32Rmw16SubU, wasm.OpcodeAtomicI32Rmw16AndU, wasm.OpcodeAtomicI32Rmw16OrU, wasm.OpcodeAtomicI32Rmw16XorU, wasm.OpcodeAtomicI32Rmw16XchgU: + return signature_I32I32_I32, nil + case wasm.OpcodeAtomicI64RmwAdd, wasm.OpcodeAtomicI64RmwSub, wasm.OpcodeAtomicI64RmwAnd, wasm.OpcodeAtomicI64RmwOr, wasm.OpcodeAtomicI64RmwXor, wasm.OpcodeAtomicI64RmwXchg, + wasm.OpcodeAtomicI64Rmw8AddU, wasm.OpcodeAtomicI64Rmw8SubU, wasm.OpcodeAtomicI64Rmw8AndU, wasm.OpcodeAtomicI64Rmw8OrU, wasm.OpcodeAtomicI64Rmw8XorU, wasm.OpcodeAtomicI64Rmw8XchgU, + wasm.OpcodeAtomicI64Rmw16AddU, wasm.OpcodeAtomicI64Rmw16SubU, wasm.OpcodeAtomicI64Rmw16AndU, wasm.OpcodeAtomicI64Rmw16OrU, wasm.OpcodeAtomicI64Rmw16XorU, wasm.OpcodeAtomicI64Rmw16XchgU, + wasm.OpcodeAtomicI64Rmw32AddU, wasm.OpcodeAtomicI64Rmw32SubU, wasm.OpcodeAtomicI64Rmw32AndU, wasm.OpcodeAtomicI64Rmw32OrU, wasm.OpcodeAtomicI64Rmw32XorU, wasm.OpcodeAtomicI64Rmw32XchgU: + return signature_I32I64_I64, nil + case wasm.OpcodeAtomicI32RmwCmpxchg, wasm.OpcodeAtomicI32Rmw8CmpxchgU, wasm.OpcodeAtomicI32Rmw16CmpxchgU: + return signature_I32I32I32_I32, nil + case wasm.OpcodeAtomicI64RmwCmpxchg, wasm.OpcodeAtomicI64Rmw8CmpxchgU, wasm.OpcodeAtomicI64Rmw16CmpxchgU, wasm.OpcodeAtomicI64Rmw32CmpxchgU: + return signature_I32I64I64_I64, nil + default: + return nil, fmt.Errorf("unsupported atomic instruction in interpreterir: %s", wasm.AtomicInstructionName(atomicOp)) + } + default: + return nil, fmt.Errorf("unsupported instruction in interpreterir: 0x%x", op) + } +} + +// funcTypeToIRSignatures is the central cache for a module to get the *signature +// for function calls. +type funcTypeToIRSignatures struct { + directCalls []*signature + indirectCalls []*signature + wasmTypes []wasm.FunctionType +} + +// get returns the *signature for the direct or indirect function call against functions whose type is at `typeIndex`. +func (f *funcTypeToIRSignatures) get(typeIndex wasm.Index, indirect bool) *signature { + var sig *signature + if indirect { + sig = f.indirectCalls[typeIndex] + } else { + sig = f.directCalls[typeIndex] + } + if sig != nil { + return sig + } + + tp := &f.wasmTypes[typeIndex] + if indirect { + sig = &signature{ + in: make([]unsignedType, 0, len(tp.Params)+1), // +1 to reserve space for call indirect index. + out: make([]unsignedType, 0, len(tp.Results)), + } + } else { + sig = &signature{ + in: make([]unsignedType, 0, len(tp.Params)), + out: make([]unsignedType, 0, len(tp.Results)), + } + } + + for _, vt := range tp.Params { + sig.in = append(sig.in, wasmValueTypeTounsignedType(vt)) + } + for _, vt := range tp.Results { + sig.out = append(sig.out, wasmValueTypeTounsignedType(vt)) + } + + if indirect { + sig.in = append(sig.in, unsignedTypeI32) + f.indirectCalls[typeIndex] = sig + } else { + f.directCalls[typeIndex] = sig + } + return sig +} + +func wasmValueTypeTounsignedType(vt wasm.ValueType) unsignedType { + switch vt { + case wasm.ValueTypeI32: + return unsignedTypeI32 + case wasm.ValueTypeI64, + // From interpreterir layer, ref type values are opaque 64-bit pointers. + wasm.ValueTypeExternref, wasm.ValueTypeFuncref: + return unsignedTypeI64 + case wasm.ValueTypeF32: + return unsignedTypeF32 + case wasm.ValueTypeF64: + return unsignedTypeF64 + case wasm.ValueTypeV128: + return unsignedTypeV128 + } + panic("unreachable") +} + +func wasmValueTypeToUnsignedOutSignature(vt wasm.ValueType) *signature { + switch vt { + case wasm.ValueTypeI32: + return signature_None_I32 + case wasm.ValueTypeI64, + // From interpreterir layer, ref type values are opaque 64-bit pointers. + wasm.ValueTypeExternref, wasm.ValueTypeFuncref: + return signature_None_I64 + case wasm.ValueTypeF32: + return signature_None_F32 + case wasm.ValueTypeF64: + return signature_None_F64 + case wasm.ValueTypeV128: + return signature_None_V128 + } + panic("unreachable") +} + +func wasmValueTypeToUnsignedInSignature(vt wasm.ValueType) *signature { + switch vt { + case wasm.ValueTypeI32: + return signature_I32_None + case wasm.ValueTypeI64, + // From interpreterir layer, ref type values are opaque 64-bit pointers. + wasm.ValueTypeExternref, wasm.ValueTypeFuncref: + return signature_I64_None + case wasm.ValueTypeF32: + return signature_F32_None + case wasm.ValueTypeF64: + return signature_F64_None + case wasm.ValueTypeV128: + return signature_V128_None + } + panic("unreachable") +} + +func wasmValueTypeToUnsignedInOutSignature(vt wasm.ValueType) *signature { + switch vt { + case wasm.ValueTypeI32: + return signature_I32_I32 + case wasm.ValueTypeI64, + // At interpreterir layer, ref type values are opaque 64-bit pointers. + wasm.ValueTypeExternref, wasm.ValueTypeFuncref: + return signature_I64_I64 + case wasm.ValueTypeF32: + return signature_F32_F32 + case wasm.ValueTypeF64: + return signature_F64_F64 + case wasm.ValueTypeV128: + return signature_V128_V128 + } + panic("unreachable") +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/abi.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/abi.go new file mode 100644 index 00000000000..cf91c6b7ad3 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/abi.go @@ -0,0 +1,170 @@ +package backend + +import ( + "fmt" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" +) + +type ( + // FunctionABI represents the ABI information for a function which corresponds to a ssa.Signature. + FunctionABI struct { + Initialized bool + + Args, Rets []ABIArg + ArgStackSize, RetStackSize int64 + + ArgIntRealRegs byte + ArgFloatRealRegs byte + RetIntRealRegs byte + RetFloatRealRegs byte + } + + // ABIArg represents either argument or return value's location. + ABIArg struct { + // Index is the index of the argument. + Index int + // Kind is the kind of the argument. + Kind ABIArgKind + // Reg is valid if Kind == ABIArgKindReg. + // This VReg must be based on RealReg. + Reg regalloc.VReg + // Offset is valid if Kind == ABIArgKindStack. + // This is the offset from the beginning of either arg or ret stack slot. + Offset int64 + // Type is the type of the argument. + Type ssa.Type + } + + // ABIArgKind is the kind of ABI argument. + ABIArgKind byte +) + +const ( + // ABIArgKindReg represents an argument passed in a register. + ABIArgKindReg = iota + // ABIArgKindStack represents an argument passed in the stack. + ABIArgKindStack +) + +// String implements fmt.Stringer. +func (a *ABIArg) String() string { + return fmt.Sprintf("args[%d]: %s", a.Index, a.Kind) +} + +// String implements fmt.Stringer. +func (a ABIArgKind) String() string { + switch a { + case ABIArgKindReg: + return "reg" + case ABIArgKindStack: + return "stack" + default: + panic("BUG") + } +} + +// Init initializes the abiImpl for the given signature. +func (a *FunctionABI) Init(sig *ssa.Signature, argResultInts, argResultFloats []regalloc.RealReg) { + if len(a.Rets) < len(sig.Results) { + a.Rets = make([]ABIArg, len(sig.Results)) + } + a.Rets = a.Rets[:len(sig.Results)] + a.RetStackSize = a.setABIArgs(a.Rets, sig.Results, argResultInts, argResultFloats) + if argsNum := len(sig.Params); len(a.Args) < argsNum { + a.Args = make([]ABIArg, argsNum) + } + a.Args = a.Args[:len(sig.Params)] + a.ArgStackSize = a.setABIArgs(a.Args, sig.Params, argResultInts, argResultFloats) + + // Gather the real registers usages in arg/return. + a.ArgIntRealRegs, a.ArgFloatRealRegs = 0, 0 + a.RetIntRealRegs, a.RetFloatRealRegs = 0, 0 + for i := range a.Rets { + r := &a.Rets[i] + if r.Kind == ABIArgKindReg { + if r.Type.IsInt() { + a.RetIntRealRegs++ + } else { + a.RetFloatRealRegs++ + } + } + } + for i := range a.Args { + arg := &a.Args[i] + if arg.Kind == ABIArgKindReg { + if arg.Type.IsInt() { + a.ArgIntRealRegs++ + } else { + a.ArgFloatRealRegs++ + } + } + } + + a.Initialized = true +} + +// setABIArgs sets the ABI arguments in the given slice. This assumes that len(s) >= len(types) +// where if len(s) > len(types), the last elements of s is for the multi-return slot. +func (a *FunctionABI) setABIArgs(s []ABIArg, types []ssa.Type, ints, floats []regalloc.RealReg) (stackSize int64) { + il, fl := len(ints), len(floats) + + var stackOffset int64 + intParamIndex, floatParamIndex := 0, 0 + for i, typ := range types { + arg := &s[i] + arg.Index = i + arg.Type = typ + if typ.IsInt() { + if intParamIndex >= il { + arg.Kind = ABIArgKindStack + const slotSize = 8 // Align 8 bytes. + arg.Offset = stackOffset + stackOffset += slotSize + } else { + arg.Kind = ABIArgKindReg + arg.Reg = regalloc.FromRealReg(ints[intParamIndex], regalloc.RegTypeInt) + intParamIndex++ + } + } else { + if floatParamIndex >= fl { + arg.Kind = ABIArgKindStack + slotSize := int64(8) // Align at least 8 bytes. + if typ.Bits() == 128 { // Vector. + slotSize = 16 + } + arg.Offset = stackOffset + stackOffset += slotSize + } else { + arg.Kind = ABIArgKindReg + arg.Reg = regalloc.FromRealReg(floats[floatParamIndex], regalloc.RegTypeFloat) + floatParamIndex++ + } + } + } + return stackOffset +} + +func (a *FunctionABI) AlignedArgResultStackSlotSize() uint32 { + stackSlotSize := a.RetStackSize + a.ArgStackSize + // Align stackSlotSize to 16 bytes. + stackSlotSize = (stackSlotSize + 15) &^ 15 + // Check overflow 32-bit. + if stackSlotSize > 0xFFFFFFFF { + panic("ABI stack slot size overflow") + } + return uint32(stackSlotSize) +} + +func (a *FunctionABI) ABIInfoAsUint64() uint64 { + return uint64(a.ArgIntRealRegs)<<56 | + uint64(a.ArgFloatRealRegs)<<48 | + uint64(a.RetIntRealRegs)<<40 | + uint64(a.RetFloatRealRegs)<<32 | + uint64(a.AlignedArgResultStackSlotSize()) +} + +func ABIInfoFromUint64(info uint64) (argIntRealRegs, argFloatRealRegs, retIntRealRegs, retFloatRealRegs byte, stackSlotSize uint32) { + return byte(info >> 56), byte(info >> 48), byte(info >> 40), byte(info >> 32), uint32(info) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/backend.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/backend.go new file mode 100644 index 00000000000..dd67da43e83 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/backend.go @@ -0,0 +1,3 @@ +// Package backend must be free of Wasm-specific concept. In other words, +// this package must not import internal/wasm package. +package backend diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/compiler.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/compiler.go new file mode 100644 index 00000000000..8e3f08efcd4 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/compiler.go @@ -0,0 +1,402 @@ +package backend + +import ( + "context" + "fmt" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +// NewCompiler returns a new Compiler that can generate a machine code. +func NewCompiler(ctx context.Context, mach Machine, builder ssa.Builder) Compiler { + return newCompiler(ctx, mach, builder) +} + +func newCompiler(_ context.Context, mach Machine, builder ssa.Builder) *compiler { + argResultInts, argResultFloats := mach.ArgsResultsRegs() + c := &compiler{ + mach: mach, ssaBuilder: builder, + nextVRegID: regalloc.VRegIDNonReservedBegin, + argResultInts: argResultInts, + argResultFloats: argResultFloats, + } + mach.SetCompiler(c) + return c +} + +// Compiler is the backend of wazevo which takes ssa.Builder and Machine, +// use the information there to emit the final machine code. +type Compiler interface { + // SSABuilder returns the ssa.Builder used by this compiler. + SSABuilder() ssa.Builder + + // Compile executes the following steps: + // 1. Lower() + // 2. RegAlloc() + // 3. Finalize() + // 4. Encode() + // + // Each step can be called individually for testing purpose, therefore they are exposed in this interface too. + // + // The returned byte slices are the machine code and the relocation information for the machine code. + // The caller is responsible for copying them immediately since the compiler may reuse the buffer. + Compile(ctx context.Context) (_ []byte, _ []RelocationInfo, _ error) + + // Lower lowers the given ssa.Instruction to the machine-specific instructions. + Lower() + + // RegAlloc performs the register allocation after Lower is called. + RegAlloc() + + // Finalize performs the finalization of the compilation, including machine code emission. + // This must be called after RegAlloc. + Finalize(ctx context.Context) error + + // Buf returns the buffer of the encoded machine code. This is only used for testing purpose. + Buf() []byte + + BufPtr() *[]byte + + // Format returns the debug string of the current state of the compiler. + Format() string + + // Init initializes the internal state of the compiler for the next compilation. + Init() + + // AllocateVReg allocates a new virtual register of the given type. + AllocateVReg(typ ssa.Type) regalloc.VReg + + // ValueDefinition returns the definition of the given value. + ValueDefinition(ssa.Value) SSAValueDefinition + + // VRegOf returns the virtual register of the given ssa.Value. + VRegOf(value ssa.Value) regalloc.VReg + + // TypeOf returns the ssa.Type of the given virtual register. + TypeOf(regalloc.VReg) ssa.Type + + // MatchInstr returns true if the given definition is from an instruction with the given opcode, the current group ID, + // and a refcount of 1. That means, the instruction can be merged/swapped within the current instruction group. + MatchInstr(def SSAValueDefinition, opcode ssa.Opcode) bool + + // MatchInstrOneOf is the same as MatchInstr but for multiple opcodes. If it matches one of ssa.Opcode, + // this returns the opcode. Otherwise, this returns ssa.OpcodeInvalid. + // + // Note: caller should be careful to avoid excessive allocation on opcodes slice. + MatchInstrOneOf(def SSAValueDefinition, opcodes []ssa.Opcode) ssa.Opcode + + // AddRelocationInfo appends the relocation information for the function reference at the current buffer offset. + AddRelocationInfo(funcRef ssa.FuncRef, isTailCall bool) + + // AddSourceOffsetInfo appends the source offset information for the given offset. + AddSourceOffsetInfo(executableOffset int64, sourceOffset ssa.SourceOffset) + + // SourceOffsetInfo returns the source offset information for the current buffer offset. + SourceOffsetInfo() []SourceOffsetInfo + + // EmitByte appends a byte to the buffer. Used during the code emission. + EmitByte(b byte) + + // Emit4Bytes appends 4 bytes to the buffer. Used during the code emission. + Emit4Bytes(b uint32) + + // Emit8Bytes appends 8 bytes to the buffer. Used during the code emission. + Emit8Bytes(b uint64) + + // GetFunctionABI returns the ABI information for the given signature. + GetFunctionABI(sig *ssa.Signature) *FunctionABI +} + +// RelocationInfo represents the relocation information for a call instruction. +type RelocationInfo struct { + // Offset represents the offset from the beginning of the machine code of either a function or the entire module. + Offset int64 + // Target is the target function of the call instruction. + FuncRef ssa.FuncRef + // IsTailCall indicates whether the call instruction is a tail call. + IsTailCall bool +} + +// compiler implements Compiler. +type compiler struct { + mach Machine + currentGID ssa.InstructionGroupID + ssaBuilder ssa.Builder + // nextVRegID is the next virtual register ID to be allocated. + nextVRegID regalloc.VRegID + // ssaValueToVRegs maps ssa.ValueID to regalloc.VReg. + ssaValueToVRegs [] /* VRegID to */ regalloc.VReg + ssaValuesInfo []ssa.ValueInfo + // returnVRegs is the list of virtual registers that store the return values. + returnVRegs []regalloc.VReg + varEdges [][2]regalloc.VReg + varEdgeTypes []ssa.Type + constEdges []struct { + cInst *ssa.Instruction + dst regalloc.VReg + } + vRegSet []bool + vRegIDs []regalloc.VRegID + tempRegs []regalloc.VReg + tmpVals []ssa.Value + ssaTypeOfVRegID [] /* VRegID to */ ssa.Type + buf []byte + relocations []RelocationInfo + sourceOffsets []SourceOffsetInfo + // abis maps ssa.SignatureID to the ABI implementation. + abis []FunctionABI + argResultInts, argResultFloats []regalloc.RealReg +} + +// SourceOffsetInfo is a data to associate the source offset with the executable offset. +type SourceOffsetInfo struct { + // SourceOffset is the source offset in the original source code. + SourceOffset ssa.SourceOffset + // ExecutableOffset is the offset in the compiled executable. + ExecutableOffset int64 +} + +// Compile implements Compiler.Compile. +func (c *compiler) Compile(ctx context.Context) ([]byte, []RelocationInfo, error) { + c.Lower() + if wazevoapi.PrintSSAToBackendIRLowering && wazevoapi.PrintEnabledIndex(ctx) { + fmt.Printf("[[[after lowering for %s ]]]%s\n", wazevoapi.GetCurrentFunctionName(ctx), c.Format()) + } + if wazevoapi.DeterministicCompilationVerifierEnabled { + wazevoapi.VerifyOrSetDeterministicCompilationContextValue(ctx, "After lowering to ISA specific IR", c.Format()) + } + c.RegAlloc() + if wazevoapi.PrintRegisterAllocated && wazevoapi.PrintEnabledIndex(ctx) { + fmt.Printf("[[[after regalloc for %s]]]%s\n", wazevoapi.GetCurrentFunctionName(ctx), c.Format()) + } + if wazevoapi.DeterministicCompilationVerifierEnabled { + wazevoapi.VerifyOrSetDeterministicCompilationContextValue(ctx, "After Register Allocation", c.Format()) + } + if err := c.Finalize(ctx); err != nil { + return nil, nil, err + } + if wazevoapi.PrintFinalizedMachineCode && wazevoapi.PrintEnabledIndex(ctx) { + fmt.Printf("[[[after finalize for %s]]]%s\n", wazevoapi.GetCurrentFunctionName(ctx), c.Format()) + } + if wazevoapi.DeterministicCompilationVerifierEnabled { + wazevoapi.VerifyOrSetDeterministicCompilationContextValue(ctx, "After Finalization", c.Format()) + } + return c.buf, c.relocations, nil +} + +// RegAlloc implements Compiler.RegAlloc. +func (c *compiler) RegAlloc() { + c.mach.RegAlloc() +} + +// Finalize implements Compiler.Finalize. +func (c *compiler) Finalize(ctx context.Context) error { + c.mach.PostRegAlloc() + return c.mach.Encode(ctx) +} + +// setCurrentGroupID sets the current instruction group ID. +func (c *compiler) setCurrentGroupID(gid ssa.InstructionGroupID) { + c.currentGID = gid +} + +// assignVirtualRegisters assigns a virtual register to each ssa.ValueID Valid in the ssa.Builder. +func (c *compiler) assignVirtualRegisters() { + builder := c.ssaBuilder + c.ssaValuesInfo = builder.ValuesInfo() + + if diff := len(c.ssaValuesInfo) - len(c.ssaValueToVRegs); diff > 0 { + c.ssaValueToVRegs = append(c.ssaValueToVRegs, make([]regalloc.VReg, diff+1)...) + } + + for blk := builder.BlockIteratorReversePostOrderBegin(); blk != nil; blk = builder.BlockIteratorReversePostOrderNext() { + // First we assign a virtual register to each parameter. + for i := 0; i < blk.Params(); i++ { + p := blk.Param(i) + pid := p.ID() + typ := p.Type() + vreg := c.AllocateVReg(typ) + c.ssaValueToVRegs[pid] = vreg + c.ssaTypeOfVRegID[vreg.ID()] = p.Type() + } + + // Assigns each value to a virtual register produced by instructions. + for cur := blk.Root(); cur != nil; cur = cur.Next() { + r, rs := cur.Returns() + if r.Valid() { + id := r.ID() + ssaTyp := r.Type() + typ := r.Type() + vReg := c.AllocateVReg(typ) + c.ssaValueToVRegs[id] = vReg + c.ssaTypeOfVRegID[vReg.ID()] = ssaTyp + } + for _, r := range rs { + id := r.ID() + ssaTyp := r.Type() + vReg := c.AllocateVReg(ssaTyp) + c.ssaValueToVRegs[id] = vReg + c.ssaTypeOfVRegID[vReg.ID()] = ssaTyp + } + } + } + + for i, retBlk := 0, builder.ReturnBlock(); i < retBlk.Params(); i++ { + typ := retBlk.Param(i).Type() + vReg := c.AllocateVReg(typ) + c.returnVRegs = append(c.returnVRegs, vReg) + c.ssaTypeOfVRegID[vReg.ID()] = typ + } +} + +// AllocateVReg implements Compiler.AllocateVReg. +func (c *compiler) AllocateVReg(typ ssa.Type) regalloc.VReg { + regType := regalloc.RegTypeOf(typ) + r := regalloc.VReg(c.nextVRegID).SetRegType(regType) + + id := r.ID() + if int(id) >= len(c.ssaTypeOfVRegID) { + c.ssaTypeOfVRegID = append(c.ssaTypeOfVRegID, make([]ssa.Type, id+1)...) + } + c.ssaTypeOfVRegID[id] = typ + c.nextVRegID++ + return r +} + +// Init implements Compiler.Init. +func (c *compiler) Init() { + c.currentGID = 0 + c.nextVRegID = regalloc.VRegIDNonReservedBegin + c.returnVRegs = c.returnVRegs[:0] + c.mach.Reset() + c.varEdges = c.varEdges[:0] + c.constEdges = c.constEdges[:0] + c.buf = c.buf[:0] + c.sourceOffsets = c.sourceOffsets[:0] + c.relocations = c.relocations[:0] +} + +// ValueDefinition implements Compiler.ValueDefinition. +func (c *compiler) ValueDefinition(value ssa.Value) SSAValueDefinition { + return SSAValueDefinition{ + V: value, + Instr: c.ssaBuilder.InstructionOfValue(value), + RefCount: c.ssaValuesInfo[value.ID()].RefCount, + } +} + +// VRegOf implements Compiler.VRegOf. +func (c *compiler) VRegOf(value ssa.Value) regalloc.VReg { + return c.ssaValueToVRegs[value.ID()] +} + +// Format implements Compiler.Format. +func (c *compiler) Format() string { + return c.mach.Format() +} + +// TypeOf implements Compiler.Format. +func (c *compiler) TypeOf(v regalloc.VReg) ssa.Type { + return c.ssaTypeOfVRegID[v.ID()] +} + +// MatchInstr implements Compiler.MatchInstr. +func (c *compiler) MatchInstr(def SSAValueDefinition, opcode ssa.Opcode) bool { + instr := def.Instr + return def.IsFromInstr() && + instr.Opcode() == opcode && + instr.GroupID() == c.currentGID && + def.RefCount < 2 +} + +// MatchInstrOneOf implements Compiler.MatchInstrOneOf. +func (c *compiler) MatchInstrOneOf(def SSAValueDefinition, opcodes []ssa.Opcode) ssa.Opcode { + instr := def.Instr + if !def.IsFromInstr() { + return ssa.OpcodeInvalid + } + + if instr.GroupID() != c.currentGID { + return ssa.OpcodeInvalid + } + + if def.RefCount >= 2 { + return ssa.OpcodeInvalid + } + + opcode := instr.Opcode() + for _, op := range opcodes { + if opcode == op { + return opcode + } + } + return ssa.OpcodeInvalid +} + +// SSABuilder implements Compiler .SSABuilder. +func (c *compiler) SSABuilder() ssa.Builder { + return c.ssaBuilder +} + +// AddSourceOffsetInfo implements Compiler.AddSourceOffsetInfo. +func (c *compiler) AddSourceOffsetInfo(executableOffset int64, sourceOffset ssa.SourceOffset) { + c.sourceOffsets = append(c.sourceOffsets, SourceOffsetInfo{ + SourceOffset: sourceOffset, + ExecutableOffset: executableOffset, + }) +} + +// SourceOffsetInfo implements Compiler.SourceOffsetInfo. +func (c *compiler) SourceOffsetInfo() []SourceOffsetInfo { + return c.sourceOffsets +} + +// AddRelocationInfo implements Compiler.AddRelocationInfo. +func (c *compiler) AddRelocationInfo(funcRef ssa.FuncRef, isTailCall bool) { + c.relocations = append(c.relocations, RelocationInfo{ + Offset: int64(len(c.buf)), + FuncRef: funcRef, + IsTailCall: isTailCall, + }) +} + +// Emit8Bytes implements Compiler.Emit8Bytes. +func (c *compiler) Emit8Bytes(b uint64) { + c.buf = append(c.buf, byte(b), byte(b>>8), byte(b>>16), byte(b>>24), byte(b>>32), byte(b>>40), byte(b>>48), byte(b>>56)) +} + +// Emit4Bytes implements Compiler.Emit4Bytes. +func (c *compiler) Emit4Bytes(b uint32) { + c.buf = append(c.buf, byte(b), byte(b>>8), byte(b>>16), byte(b>>24)) +} + +// EmitByte implements Compiler.EmitByte. +func (c *compiler) EmitByte(b byte) { + c.buf = append(c.buf, b) +} + +// Buf implements Compiler.Buf. +func (c *compiler) Buf() []byte { + return c.buf +} + +// BufPtr implements Compiler.BufPtr. +func (c *compiler) BufPtr() *[]byte { + return &c.buf +} + +func (c *compiler) GetFunctionABI(sig *ssa.Signature) *FunctionABI { + if int(sig.ID) >= len(c.abis) { + c.abis = append(c.abis, make([]FunctionABI, int(sig.ID)+1)...) + } + + abi := &c.abis[sig.ID] + if abi.Initialized { + return abi + } + + abi.Init(sig, c.argResultInts, c.argResultFloats) + return abi +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/compiler_lower.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/compiler_lower.go new file mode 100644 index 00000000000..735cfa3d35d --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/compiler_lower.go @@ -0,0 +1,226 @@ +package backend + +import ( + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" +) + +// Lower implements Compiler.Lower. +func (c *compiler) Lower() { + c.assignVirtualRegisters() + c.mach.SetCurrentABI(c.GetFunctionABI(c.ssaBuilder.Signature())) + c.mach.StartLoweringFunction(c.ssaBuilder.BlockIDMax()) + c.lowerBlocks() +} + +// lowerBlocks lowers each block in the ssa.Builder. +func (c *compiler) lowerBlocks() { + builder := c.ssaBuilder + for blk := builder.BlockIteratorReversePostOrderBegin(); blk != nil; blk = builder.BlockIteratorReversePostOrderNext() { + c.lowerBlock(blk) + } + + // After lowering all blocks, we need to link adjacent blocks to layout one single instruction list. + var prev ssa.BasicBlock + for next := builder.BlockIteratorReversePostOrderBegin(); next != nil; next = builder.BlockIteratorReversePostOrderNext() { + if prev != nil { + c.mach.LinkAdjacentBlocks(prev, next) + } + prev = next + } +} + +func (c *compiler) lowerBlock(blk ssa.BasicBlock) { + mach := c.mach + mach.StartBlock(blk) + + // We traverse the instructions in reverse order because we might want to lower multiple + // instructions together. + cur := blk.Tail() + + // First gather the branching instructions at the end of the blocks. + var br0, br1 *ssa.Instruction + if cur.IsBranching() { + br0 = cur + cur = cur.Prev() + if cur != nil && cur.IsBranching() { + br1 = cur + cur = cur.Prev() + } + } + + if br0 != nil { + c.lowerBranches(br0, br1) + } + + if br1 != nil && br0 == nil { + panic("BUG? when a block has conditional branch but doesn't end with an unconditional branch?") + } + + // Now start lowering the non-branching instructions. + for ; cur != nil; cur = cur.Prev() { + c.setCurrentGroupID(cur.GroupID()) + if cur.Lowered() { + continue + } + + switch cur.Opcode() { + case ssa.OpcodeReturn: + rets := cur.ReturnVals() + if len(rets) > 0 { + c.mach.LowerReturns(rets) + } + c.mach.InsertReturn() + default: + mach.LowerInstr(cur) + } + mach.FlushPendingInstructions() + } + + // Finally, if this is the entry block, we have to insert copies of arguments from the real location to the VReg. + if blk.EntryBlock() { + c.lowerFunctionArguments(blk) + } + + mach.EndBlock() +} + +// lowerBranches is called right after StartBlock and before any LowerInstr call if +// there are branches to the given block. br0 is the very end of the block and b1 is the before the br0 if it exists. +// At least br0 is not nil, but br1 can be nil if there's no branching before br0. +// +// See ssa.Instruction IsBranching, and the comment on ssa.BasicBlock. +func (c *compiler) lowerBranches(br0, br1 *ssa.Instruction) { + mach := c.mach + + c.setCurrentGroupID(br0.GroupID()) + c.mach.LowerSingleBranch(br0) + mach.FlushPendingInstructions() + if br1 != nil { + c.setCurrentGroupID(br1.GroupID()) + c.mach.LowerConditionalBranch(br1) + mach.FlushPendingInstructions() + } + + if br0.Opcode() == ssa.OpcodeJump { + _, args, targetBlockID := br0.BranchData() + argExists := len(args) != 0 + if argExists && br1 != nil { + panic("BUG: critical edge split failed") + } + target := c.ssaBuilder.BasicBlock(targetBlockID) + if argExists && target.ReturnBlock() { + if len(args) > 0 { + c.mach.LowerReturns(args) + } + } else if argExists { + c.lowerBlockArguments(args, target) + } + } + mach.FlushPendingInstructions() +} + +func (c *compiler) lowerFunctionArguments(entry ssa.BasicBlock) { + mach := c.mach + + c.tmpVals = c.tmpVals[:0] + data := c.ssaBuilder.ValuesInfo() + for i := 0; i < entry.Params(); i++ { + p := entry.Param(i) + if data[p.ID()].RefCount > 0 { + c.tmpVals = append(c.tmpVals, p) + } else { + // If the argument is not used, we can just pass an invalid value. + c.tmpVals = append(c.tmpVals, ssa.ValueInvalid) + } + } + mach.LowerParams(c.tmpVals) + mach.FlushPendingInstructions() +} + +// lowerBlockArguments lowers how to pass arguments to the given successor block. +func (c *compiler) lowerBlockArguments(args []ssa.Value, succ ssa.BasicBlock) { + if len(args) != succ.Params() { + panic("BUG: mismatched number of arguments") + } + + c.varEdges = c.varEdges[:0] + c.varEdgeTypes = c.varEdgeTypes[:0] + c.constEdges = c.constEdges[:0] + for i := 0; i < len(args); i++ { + dst := succ.Param(i) + src := args[i] + + dstReg := c.VRegOf(dst) + srcInstr := c.ssaBuilder.InstructionOfValue(src) + if srcInstr != nil && srcInstr.Constant() { + c.constEdges = append(c.constEdges, struct { + cInst *ssa.Instruction + dst regalloc.VReg + }{cInst: srcInstr, dst: dstReg}) + } else { + srcReg := c.VRegOf(src) + // Even when the src=dst, insert the move so that we can keep such registers keep-alive. + c.varEdges = append(c.varEdges, [2]regalloc.VReg{srcReg, dstReg}) + c.varEdgeTypes = append(c.varEdgeTypes, src.Type()) + } + } + + // Check if there's an overlap among the dsts and srcs in varEdges. + c.vRegIDs = c.vRegIDs[:0] + for _, edge := range c.varEdges { + src := edge[0].ID() + if int(src) >= len(c.vRegSet) { + c.vRegSet = append(c.vRegSet, make([]bool, src+1)...) + } + c.vRegSet[src] = true + c.vRegIDs = append(c.vRegIDs, src) + } + separated := true + for _, edge := range c.varEdges { + dst := edge[1].ID() + if int(dst) >= len(c.vRegSet) { + c.vRegSet = append(c.vRegSet, make([]bool, dst+1)...) + } else { + if c.vRegSet[dst] { + separated = false + break + } + } + } + for _, id := range c.vRegIDs { + c.vRegSet[id] = false // reset for the next use. + } + + if separated { + // If there's no overlap, we can simply move the source to destination. + for i, edge := range c.varEdges { + src, dst := edge[0], edge[1] + c.mach.InsertMove(dst, src, c.varEdgeTypes[i]) + } + } else { + // Otherwise, we allocate a temporary registers and move the source to the temporary register, + // + // First move all of them to temporary registers. + c.tempRegs = c.tempRegs[:0] + for i, edge := range c.varEdges { + src := edge[0] + typ := c.varEdgeTypes[i] + temp := c.AllocateVReg(typ) + c.tempRegs = append(c.tempRegs, temp) + c.mach.InsertMove(temp, src, typ) + } + // Then move the temporary registers to the destination. + for i, edge := range c.varEdges { + temp := c.tempRegs[i] + dst := edge[1] + c.mach.InsertMove(dst, temp, c.varEdgeTypes[i]) + } + } + + // Finally, move the constants. + for _, edge := range c.constEdges { + cInst, dst := edge.cInst, edge.dst + c.mach.InsertLoadConstantBlockArg(cInst, dst) + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/go_call.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/go_call.go new file mode 100644 index 00000000000..6fe6d7b3cbc --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/go_call.go @@ -0,0 +1,33 @@ +package backend + +import "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" + +// GoFunctionCallRequiredStackSize returns the size of the stack required for the Go function call. +// argBegin is the index of the first argument in the signature which is not either execution context or module context. +func GoFunctionCallRequiredStackSize(sig *ssa.Signature, argBegin int) (ret, retUnaligned int64) { + var paramNeededInBytes, resultNeededInBytes int64 + for _, p := range sig.Params[argBegin:] { + s := int64(p.Size()) + if s < 8 { + s = 8 // We use uint64 for all basic types, except SIMD v128. + } + paramNeededInBytes += s + } + for _, r := range sig.Results { + s := int64(r.Size()) + if s < 8 { + s = 8 // We use uint64 for all basic types, except SIMD v128. + } + resultNeededInBytes += s + } + + if paramNeededInBytes > resultNeededInBytes { + ret = paramNeededInBytes + } else { + ret = resultNeededInBytes + } + retUnaligned = ret + // Align to 16 bytes. + ret = (ret + 15) &^ 15 + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi.go new file mode 100644 index 00000000000..dc6df3109fa --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi.go @@ -0,0 +1,186 @@ +package amd64 + +import ( + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" +) + +// For the details of the ABI, see: +// https://github.com/golang/go/blob/go1.24.0/src/cmd/compile/abi-internal.md#amd64-architecture + +var ( + intArgResultRegs = []regalloc.RealReg{rax, rbx, rcx, rdi, rsi, r8, r9, r10, r11} + floatArgResultRegs = []regalloc.RealReg{xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7} +) + +var regInfo = ®alloc.RegisterInfo{ + AllocatableRegisters: [regalloc.NumRegType][]regalloc.RealReg{ + regalloc.RegTypeInt: { + rax, rcx, rdx, rbx, rsi, rdi, r8, r9, r10, r11, r12, r13, r14, r15, + }, + regalloc.RegTypeFloat: { + xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15, + }, + }, + CalleeSavedRegisters: regalloc.NewRegSet( + rdx, r12, r13, r14, r15, + xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15, + ), + CallerSavedRegisters: regalloc.NewRegSet( + rax, rcx, rbx, rsi, rdi, r8, r9, r10, r11, + xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, + ), + RealRegToVReg: []regalloc.VReg{ + rax: raxVReg, rcx: rcxVReg, rdx: rdxVReg, rbx: rbxVReg, rsp: rspVReg, rbp: rbpVReg, rsi: rsiVReg, rdi: rdiVReg, + r8: r8VReg, r9: r9VReg, r10: r10VReg, r11: r11VReg, r12: r12VReg, r13: r13VReg, r14: r14VReg, r15: r15VReg, + xmm0: xmm0VReg, xmm1: xmm1VReg, xmm2: xmm2VReg, xmm3: xmm3VReg, xmm4: xmm4VReg, xmm5: xmm5VReg, xmm6: xmm6VReg, + xmm7: xmm7VReg, xmm8: xmm8VReg, xmm9: xmm9VReg, xmm10: xmm10VReg, xmm11: xmm11VReg, xmm12: xmm12VReg, + xmm13: xmm13VReg, xmm14: xmm14VReg, xmm15: xmm15VReg, + }, + RealRegName: func(r regalloc.RealReg) string { return regNames[r] }, + RealRegType: func(r regalloc.RealReg) regalloc.RegType { + if r < xmm0 { + return regalloc.RegTypeInt + } + return regalloc.RegTypeFloat + }, +} + +// ArgsResultsRegs implements backend.Machine. +func (m *machine) ArgsResultsRegs() (argResultInts, argResultFloats []regalloc.RealReg) { + return intArgResultRegs, floatArgResultRegs +} + +// LowerParams implements backend.Machine. +func (m *machine) LowerParams(args []ssa.Value) { + a := m.currentABI + + for i, ssaArg := range args { + if !ssaArg.Valid() { + continue + } + reg := m.c.VRegOf(ssaArg) + arg := &a.Args[i] + if arg.Kind == backend.ABIArgKindReg { + m.InsertMove(reg, arg.Reg, arg.Type) + } else { + // + // (high address) + // +-----------------+ + // | ....... | + // | ret Y | + // | ....... | + // | ret 0 | + // | arg X | + // | ....... | + // | arg 1 | + // | arg 0 | + // | ReturnAddress | + // | Caller_RBP | + // +-----------------+ <-- RBP + // | ........... | + // | clobbered M | + // | ............ | + // | clobbered 0 | + // | spill slot N | + // | ........... | + // | spill slot 0 | + // RSP--> +-----------------+ + // (low address) + + // Load the value from the arg stack slot above the current RBP. + load := m.allocateInstr() + mem := newOperandMem(m.newAmodeImmRBPReg(uint32(arg.Offset + 16))) + switch arg.Type { + case ssa.TypeI32: + load.asMovzxRmR(extModeLQ, mem, reg) + case ssa.TypeI64: + load.asMov64MR(mem, reg) + case ssa.TypeF32: + load.asXmmUnaryRmR(sseOpcodeMovss, mem, reg) + case ssa.TypeF64: + load.asXmmUnaryRmR(sseOpcodeMovsd, mem, reg) + case ssa.TypeV128: + load.asXmmUnaryRmR(sseOpcodeMovdqu, mem, reg) + default: + panic("BUG") + } + m.insert(load) + } + } +} + +// LowerReturns implements backend.Machine. +func (m *machine) LowerReturns(rets []ssa.Value) { + // Load the XMM registers first as it might need a temporary register to inline + // constant return. + a := m.currentABI + for i, ret := range rets { + r := &a.Rets[i] + if !r.Type.IsInt() { + m.LowerReturn(ret, r) + } + } + // Then load the GPR registers. + for i, ret := range rets { + r := &a.Rets[i] + if r.Type.IsInt() { + m.LowerReturn(ret, r) + } + } +} + +func (m *machine) LowerReturn(ret ssa.Value, r *backend.ABIArg) { + reg := m.c.VRegOf(ret) + if def := m.c.ValueDefinition(ret); def.IsFromInstr() { + // Constant instructions are inlined. + if inst := def.Instr; inst.Constant() { + m.insertLoadConstant(inst, reg) + } + } + if r.Kind == backend.ABIArgKindReg { + m.InsertMove(r.Reg, reg, ret.Type()) + } else { + // + // (high address) + // +-----------------+ + // | ....... | + // | ret Y | + // | ....... | + // | ret 0 | + // | arg X | + // | ....... | + // | arg 1 | + // | arg 0 | + // | ReturnAddress | + // | Caller_RBP | + // +-----------------+ <-- RBP + // | ........... | + // | clobbered M | + // | ............ | + // | clobbered 0 | + // | spill slot N | + // | ........... | + // | spill slot 0 | + // RSP--> +-----------------+ + // (low address) + + // Store the value to the return stack slot above the current RBP. + store := m.allocateInstr() + mem := newOperandMem(m.newAmodeImmRBPReg(uint32(m.currentABI.ArgStackSize + 16 + r.Offset))) + switch r.Type { + case ssa.TypeI32: + store.asMovRM(reg, mem, 4) + case ssa.TypeI64: + store.asMovRM(reg, mem, 8) + case ssa.TypeF32: + store.asXmmMovRM(sseOpcodeMovss, reg, mem) + case ssa.TypeF64: + store.asXmmMovRM(sseOpcodeMovsd, reg, mem) + case ssa.TypeV128: + store.asXmmMovRM(sseOpcodeMovdqu, reg, mem) + } + m.insert(store) + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.go new file mode 100644 index 00000000000..cbf1cfdc594 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.go @@ -0,0 +1,9 @@ +package amd64 + +// entrypoint enters the machine code generated by this backend which begins with the preamble generated by functionABI.EmitGoEntryPreamble below. +// This implements wazevo.entrypoint, and see the comments there for detail. +func entrypoint(preambleExecutable, functionExecutable *byte, executionContextPtr uintptr, moduleContextPtr *byte, paramResultPtr *uint64, goAllocatedStackSlicePtr uintptr) + +// afterGoFunctionCallEntrypoint enters the machine code after growing the stack. +// This implements wazevo.afterGoFunctionCallEntrypoint, and see the comments there for detail. +func afterGoFunctionCallEntrypoint(executable *byte, executionContextPtr uintptr, stackPointer, framePointer uintptr) diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.s b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.s new file mode 100644 index 00000000000..e9cb131d1ca --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.s @@ -0,0 +1,29 @@ +#include "funcdata.h" +#include "textflag.h" + +// entrypoint(preambleExecutable, functionExecutable *byte, executionContextPtr uintptr, moduleContextPtr *byte, paramResultPtr *uint64, goAllocatedStackSlicePtr uintptr +TEXT ·entrypoint(SB), NOSPLIT|NOFRAME, $0-48 + MOVQ preambleExecutable+0(FP), R11 + MOVQ functionExectuable+8(FP), R14 + MOVQ executionContextPtr+16(FP), AX // First argument is passed in AX. + MOVQ moduleContextPtr+24(FP), BX // Second argument is passed in BX. + MOVQ paramResultSlicePtr+32(FP), R12 + MOVQ goAllocatedStackSlicePtr+40(FP), R13 + JMP R11 + +// afterGoFunctionCallEntrypoint(executable *byte, executionContextPtr uintptr, stackPointer, framePointer uintptr) +TEXT ·afterGoFunctionCallEntrypoint(SB), NOSPLIT|NOFRAME, $0-32 + MOVQ executable+0(FP), CX + MOVQ executionContextPtr+8(FP), AX // First argument is passed in AX. + + // Save the stack pointer and frame pointer. + MOVQ BP, 16(AX) // 16 == ExecutionContextOffsetOriginalFramePointer + MOVQ SP, 24(AX) // 24 == ExecutionContextOffsetOriginalStackPointer + + // Then set the stack pointer and frame pointer to the values we got from the Go runtime. + MOVQ framePointer+24(FP), BP + + // WARNING: do not update SP before BP, because the Go translates (FP) as (SP) + 8. + MOVQ stackPointer+16(FP), SP + + JMP CX diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble.go new file mode 100644 index 00000000000..882d06c0620 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble.go @@ -0,0 +1,248 @@ +package amd64 + +import ( + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +var ( + executionContextPtrReg = raxVReg + + // Followings are callee saved registers. They can be used freely in the entry preamble + // since the preamble is called via Go assembly function which has stack-based ABI. + + // savedExecutionContextPtr also must be a callee-saved reg so that they can be used in the prologue and epilogue. + savedExecutionContextPtr = rdxVReg + // paramResultSlicePtr must match with entrypoint function in abi_entry_amd64.s. + paramResultSlicePtr = r12VReg + // goAllocatedStackPtr must match with entrypoint function in abi_entry_amd64.s. + goAllocatedStackPtr = r13VReg + // functionExecutable must match with entrypoint function in abi_entry_amd64.s. + functionExecutable = r14VReg + tmpIntReg = r15VReg + tmpXmmReg = xmm15VReg +) + +// CompileEntryPreamble implements backend.Machine. +func (m *machine) CompileEntryPreamble(sig *ssa.Signature) []byte { + root := m.compileEntryPreamble(sig) + m.encodeWithoutSSA(root) + buf := m.c.Buf() + return buf +} + +func (m *machine) compileEntryPreamble(sig *ssa.Signature) *instruction { + abi := backend.FunctionABI{} + abi.Init(sig, intArgResultRegs, floatArgResultRegs) + + root := m.allocateNop() + + //// ----------------------------------- prologue ----------------------------------- //// + + // First, we save executionContextPtrReg into a callee-saved register so that it can be used in epilogue as well. + // mov %executionContextPtrReg, %savedExecutionContextPtr + cur := m.move64(executionContextPtrReg, savedExecutionContextPtr, root) + + // Next is to save the original RBP and RSP into the execution context. + cur = m.saveOriginalRSPRBP(cur) + + // Now set the RSP to the Go-allocated stack pointer. + // mov %goAllocatedStackPtr, %rsp + cur = m.move64(goAllocatedStackPtr, rspVReg, cur) + + if stackSlotSize := abi.AlignedArgResultStackSlotSize(); stackSlotSize > 0 { + // Allocate stack slots for the arguments and return values. + // sub $stackSlotSize, %rsp + spDec := m.allocateInstr().asAluRmiR(aluRmiROpcodeSub, newOperandImm32(uint32(stackSlotSize)), rspVReg, true) + cur = linkInstr(cur, spDec) + } + + var offset uint32 + for i := range abi.Args { + if i < 2 { + // module context ptr and execution context ptr are passed in rax and rbx by the Go assembly function. + continue + } + arg := &abi.Args[i] + cur = m.goEntryPreamblePassArg(cur, paramResultSlicePtr, offset, arg) + if arg.Type == ssa.TypeV128 { + offset += 16 + } else { + offset += 8 + } + } + + // Zero out RBP so that the unwind/stack growth code can correctly detect the end of the stack. + zerosRbp := m.allocateInstr().asAluRmiR(aluRmiROpcodeXor, newOperandReg(rbpVReg), rbpVReg, true) + cur = linkInstr(cur, zerosRbp) + + // Now ready to call the real function. Note that at this point stack pointer is already set to the Go-allocated, + // which is aligned to 16 bytes. + call := m.allocateInstr().asCallIndirect(newOperandReg(functionExecutable), &abi) + cur = linkInstr(cur, call) + + //// ----------------------------------- epilogue ----------------------------------- //// + + // Read the results from regs and the stack, and set them correctly into the paramResultSlicePtr. + offset = 0 + for i := range abi.Rets { + r := &abi.Rets[i] + cur = m.goEntryPreamblePassResult(cur, paramResultSlicePtr, offset, r, uint32(abi.ArgStackSize)) + if r.Type == ssa.TypeV128 { + offset += 16 + } else { + offset += 8 + } + } + + // Finally, restore the original RBP and RSP. + cur = m.restoreOriginalRSPRBP(cur) + + ret := m.allocateInstr().asRet() + linkInstr(cur, ret) + return root +} + +// saveOriginalRSPRBP saves the original RSP and RBP into the execution context. +func (m *machine) saveOriginalRSPRBP(cur *instruction) *instruction { + // mov %rbp, wazevoapi.ExecutionContextOffsetOriginalFramePointer(%executionContextPtrReg) + // mov %rsp, wazevoapi.ExecutionContextOffsetOriginalStackPointer(%executionContextPtrReg) + cur = m.loadOrStore64AtExecutionCtx(executionContextPtrReg, wazevoapi.ExecutionContextOffsetOriginalFramePointer, rbpVReg, true, cur) + cur = m.loadOrStore64AtExecutionCtx(executionContextPtrReg, wazevoapi.ExecutionContextOffsetOriginalStackPointer, rspVReg, true, cur) + return cur +} + +// restoreOriginalRSPRBP restores the original RSP and RBP from the execution context. +func (m *machine) restoreOriginalRSPRBP(cur *instruction) *instruction { + // mov wazevoapi.ExecutionContextOffsetOriginalFramePointer(%executionContextPtrReg), %rbp + // mov wazevoapi.ExecutionContextOffsetOriginalStackPointer(%executionContextPtrReg), %rsp + cur = m.loadOrStore64AtExecutionCtx(savedExecutionContextPtr, wazevoapi.ExecutionContextOffsetOriginalFramePointer, rbpVReg, false, cur) + cur = m.loadOrStore64AtExecutionCtx(savedExecutionContextPtr, wazevoapi.ExecutionContextOffsetOriginalStackPointer, rspVReg, false, cur) + return cur +} + +func (m *machine) move64(src, dst regalloc.VReg, prev *instruction) *instruction { + mov := m.allocateInstr().asMovRR(src, dst, true) + return linkInstr(prev, mov) +} + +func (m *machine) loadOrStore64AtExecutionCtx(execCtx regalloc.VReg, offset wazevoapi.Offset, r regalloc.VReg, store bool, prev *instruction) *instruction { + mem := newOperandMem(m.newAmodeImmReg(offset.U32(), execCtx)) + instr := m.allocateInstr() + if store { + instr.asMovRM(r, mem, 8) + } else { + instr.asMov64MR(mem, r) + } + return linkInstr(prev, instr) +} + +// This is for debugging. +func (m *machine) linkUD2(cur *instruction) *instruction { //nolint + return linkInstr(cur, m.allocateInstr().asUD2()) +} + +func (m *machine) goEntryPreamblePassArg(cur *instruction, paramSlicePtr regalloc.VReg, offsetInParamSlice uint32, arg *backend.ABIArg) *instruction { + var dst regalloc.VReg + argTyp := arg.Type + if arg.Kind == backend.ABIArgKindStack { + // Caller saved registers ca + switch argTyp { + case ssa.TypeI32, ssa.TypeI64: + dst = tmpIntReg + case ssa.TypeF32, ssa.TypeF64, ssa.TypeV128: + dst = tmpXmmReg + default: + panic("BUG") + } + } else { + dst = arg.Reg + } + + load := m.allocateInstr() + a := newOperandMem(m.newAmodeImmReg(offsetInParamSlice, paramSlicePtr)) + switch arg.Type { + case ssa.TypeI32: + load.asMovzxRmR(extModeLQ, a, dst) + case ssa.TypeI64: + load.asMov64MR(a, dst) + case ssa.TypeF32: + load.asXmmUnaryRmR(sseOpcodeMovss, a, dst) + case ssa.TypeF64: + load.asXmmUnaryRmR(sseOpcodeMovsd, a, dst) + case ssa.TypeV128: + load.asXmmUnaryRmR(sseOpcodeMovdqu, a, dst) + } + + cur = linkInstr(cur, load) + if arg.Kind == backend.ABIArgKindStack { + // Store back to the stack. + store := m.allocateInstr() + a := newOperandMem(m.newAmodeImmReg(uint32(arg.Offset), rspVReg)) + switch arg.Type { + case ssa.TypeI32: + store.asMovRM(dst, a, 4) + case ssa.TypeI64: + store.asMovRM(dst, a, 8) + case ssa.TypeF32: + store.asXmmMovRM(sseOpcodeMovss, dst, a) + case ssa.TypeF64: + store.asXmmMovRM(sseOpcodeMovsd, dst, a) + case ssa.TypeV128: + store.asXmmMovRM(sseOpcodeMovdqu, dst, a) + } + cur = linkInstr(cur, store) + } + return cur +} + +func (m *machine) goEntryPreamblePassResult(cur *instruction, resultSlicePtr regalloc.VReg, offsetInResultSlice uint32, result *backend.ABIArg, resultStackSlotBeginOffset uint32) *instruction { + var r regalloc.VReg + if result.Kind == backend.ABIArgKindStack { + // Load the value to the temporary. + load := m.allocateInstr() + offset := resultStackSlotBeginOffset + uint32(result.Offset) + a := newOperandMem(m.newAmodeImmReg(offset, rspVReg)) + switch result.Type { + case ssa.TypeI32: + r = tmpIntReg + load.asMovzxRmR(extModeLQ, a, r) + case ssa.TypeI64: + r = tmpIntReg + load.asMov64MR(a, r) + case ssa.TypeF32: + r = tmpXmmReg + load.asXmmUnaryRmR(sseOpcodeMovss, a, r) + case ssa.TypeF64: + r = tmpXmmReg + load.asXmmUnaryRmR(sseOpcodeMovsd, a, r) + case ssa.TypeV128: + r = tmpXmmReg + load.asXmmUnaryRmR(sseOpcodeMovdqu, a, r) + default: + panic("BUG") + } + cur = linkInstr(cur, load) + } else { + r = result.Reg + } + + store := m.allocateInstr() + a := newOperandMem(m.newAmodeImmReg(offsetInResultSlice, resultSlicePtr)) + switch result.Type { + case ssa.TypeI32: + store.asMovRM(r, a, 4) + case ssa.TypeI64: + store.asMovRM(r, a, 8) + case ssa.TypeF32: + store.asXmmMovRM(sseOpcodeMovss, r, a) + case ssa.TypeF64: + store.asXmmMovRM(sseOpcodeMovsd, r, a) + case ssa.TypeV128: + store.asXmmMovRM(sseOpcodeMovdqu, r, a) + } + + return linkInstr(cur, store) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi_go_call.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi_go_call.go new file mode 100644 index 00000000000..96f035e5827 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi_go_call.go @@ -0,0 +1,440 @@ +package amd64 + +import ( + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +var calleeSavedVRegs = []regalloc.VReg{ + rdxVReg, r12VReg, r13VReg, r14VReg, r15VReg, + xmm8VReg, xmm9VReg, xmm10VReg, xmm11VReg, xmm12VReg, xmm13VReg, xmm14VReg, xmm15VReg, +} + +// CompileGoFunctionTrampoline implements backend.Machine. +func (m *machine) CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *ssa.Signature, needModuleContextPtr bool) []byte { + argBegin := 1 // Skips exec context by default. + if needModuleContextPtr { + argBegin++ + } + + abi := &backend.FunctionABI{} + abi.Init(sig, intArgResultRegs, floatArgResultRegs) + m.currentABI = abi + + cur := m.allocateNop() + m.rootInstr = cur + + // Execution context is always the first argument. + execCtrPtr := raxVReg + + // First we update RBP and RSP just like the normal prologue. + // + // (high address) (high address) + // RBP ----> +-----------------+ +-----------------+ + // | ....... | | ....... | + // | ret Y | | ret Y | + // | ....... | | ....... | + // | ret 0 | | ret 0 | + // | arg X | | arg X | + // | ....... | ====> | ....... | + // | arg 1 | | arg 1 | + // | arg 0 | | arg 0 | + // | Return Addr | | Return Addr | + // RSP ----> +-----------------+ | Caller_RBP | + // (low address) +-----------------+ <----- RSP, RBP + // + cur = m.setupRBPRSP(cur) + + goSliceSizeAligned, goSliceSizeAlignedUnaligned := backend.GoFunctionCallRequiredStackSize(sig, argBegin) + cur = m.insertStackBoundsCheck(goSliceSizeAligned+8 /* size of the Go slice */, cur) + + // Save the callee saved registers. + cur = m.saveRegistersInExecutionContext(cur, execCtrPtr, calleeSavedVRegs) + + if needModuleContextPtr { + moduleCtrPtr := rbxVReg // Module context is always the second argument. + mem := m.newAmodeImmReg( + wazevoapi.ExecutionContextOffsetGoFunctionCallCalleeModuleContextOpaque.U32(), + execCtrPtr) + store := m.allocateInstr().asMovRM(moduleCtrPtr, newOperandMem(mem), 8) + cur = linkInstr(cur, store) + } + + // Now let's advance the RSP to the stack slot for the arguments. + // + // (high address) (high address) + // +-----------------+ +-----------------+ + // | ....... | | ....... | + // | ret Y | | ret Y | + // | ....... | | ....... | + // | ret 0 | | ret 0 | + // | arg X | | arg X | + // | ....... | =======> | ....... | + // | arg 1 | | arg 1 | + // | arg 0 | | arg 0 | + // | Return Addr | | Return Addr | + // | Caller_RBP | | Caller_RBP | + // RBP,RSP --> +-----------------+ +-----------------+ <----- RBP + // (low address) | arg[N]/ret[M] | + // | .......... | + // | arg[1]/ret[1] | + // | arg[0]/ret[0] | + // +-----------------+ <----- RSP + // (low address) + // + // where the region of "arg[0]/ret[0] ... arg[N]/ret[M]" is the stack used by the Go functions, + // therefore will be accessed as the usual []uint64. So that's where we need to pass/receive + // the arguments/return values to/from Go function. + cur = m.addRSP(-int32(goSliceSizeAligned), cur) + + // Next, we need to store all the arguments to the stack in the typical Wasm stack style. + var offsetInGoSlice int32 + for i := range abi.Args[argBegin:] { + arg := &abi.Args[argBegin+i] + var v regalloc.VReg + if arg.Kind == backend.ABIArgKindReg { + v = arg.Reg + } else { + // We have saved callee saved registers, so we can use them. + if arg.Type.IsInt() { + v = r15VReg + } else { + v = xmm15VReg + } + mem := newOperandMem(m.newAmodeImmReg(uint32(arg.Offset+16 /* to skip caller_rbp and ret_addr */), rbpVReg)) + load := m.allocateInstr() + switch arg.Type { + case ssa.TypeI32: + load.asMovzxRmR(extModeLQ, mem, v) + case ssa.TypeI64: + load.asMov64MR(mem, v) + case ssa.TypeF32: + load.asXmmUnaryRmR(sseOpcodeMovss, mem, v) + case ssa.TypeF64: + load.asXmmUnaryRmR(sseOpcodeMovsd, mem, v) + case ssa.TypeV128: + load.asXmmUnaryRmR(sseOpcodeMovdqu, mem, v) + default: + panic("BUG") + } + cur = linkInstr(cur, load) + } + + store := m.allocateInstr() + mem := newOperandMem(m.newAmodeImmReg(uint32(offsetInGoSlice), rspVReg)) + switch arg.Type { + case ssa.TypeI32: + store.asMovRM(v, mem, 4) + offsetInGoSlice += 8 // always uint64 rep. + case ssa.TypeI64: + store.asMovRM(v, mem, 8) + offsetInGoSlice += 8 + case ssa.TypeF32: + store.asXmmMovRM(sseOpcodeMovss, v, mem) + offsetInGoSlice += 8 // always uint64 rep. + case ssa.TypeF64: + store.asXmmMovRM(sseOpcodeMovsd, v, mem) + offsetInGoSlice += 8 + case ssa.TypeV128: + store.asXmmMovRM(sseOpcodeMovdqu, v, mem) + offsetInGoSlice += 16 + default: + panic("BUG") + } + cur = linkInstr(cur, store) + } + + // Finally we push the size of the slice to the stack so the stack looks like: + // + // (high address) + // +-----------------+ + // | ....... | + // | ret Y | + // | ....... | + // | ret 0 | + // | arg X | + // | ....... | + // | arg 1 | + // | arg 0 | + // | Return Addr | + // | Caller_RBP | + // +-----------------+ <----- RBP + // | arg[N]/ret[M] | + // | .......... | + // | arg[1]/ret[1] | + // | arg[0]/ret[0] | + // | slice size | + // +-----------------+ <----- RSP + // (low address) + // + // push $sliceSize + cur = linkInstr(cur, m.allocateInstr().asPush64(newOperandImm32(uint32(goSliceSizeAlignedUnaligned)))) + + // Load the exitCode to the register. + exitCodeReg := r12VReg // Callee saved which is already saved. + cur = linkInstr(cur, m.allocateInstr().asImm(exitCodeReg, uint64(exitCode), false)) + + saveRsp, saveRbp, setExitCode := m.allocateExitInstructions(execCtrPtr, exitCodeReg) + cur = linkInstr(cur, setExitCode) + cur = linkInstr(cur, saveRsp) + cur = linkInstr(cur, saveRbp) + + // Ready to exit the execution. + cur = m.storeReturnAddressAndExit(cur, execCtrPtr) + + // We don't need the slice size anymore, so pop it. + cur = m.addRSP(8, cur) + + // Ready to set up the results. + offsetInGoSlice = 0 + // To avoid overwriting with the execution context pointer by the result, we need to track the offset, + // and defer the restoration of the result to the end of this function. + var argOverlapWithExecCtxOffset int32 = -1 + for i := range abi.Rets { + r := &abi.Rets[i] + var v regalloc.VReg + isRegResult := r.Kind == backend.ABIArgKindReg + if isRegResult { + v = r.Reg + if v.RealReg() == execCtrPtr.RealReg() { + argOverlapWithExecCtxOffset = offsetInGoSlice + offsetInGoSlice += 8 // always uint64 rep. + continue + } + } else { + if r.Type.IsInt() { + v = r15VReg + } else { + v = xmm15VReg + } + } + + load := m.allocateInstr() + mem := newOperandMem(m.newAmodeImmReg(uint32(offsetInGoSlice), rspVReg)) + switch r.Type { + case ssa.TypeI32: + load.asMovzxRmR(extModeLQ, mem, v) + offsetInGoSlice += 8 // always uint64 rep. + case ssa.TypeI64: + load.asMov64MR(mem, v) + offsetInGoSlice += 8 + case ssa.TypeF32: + load.asXmmUnaryRmR(sseOpcodeMovss, mem, v) + offsetInGoSlice += 8 // always uint64 rep. + case ssa.TypeF64: + load.asXmmUnaryRmR(sseOpcodeMovsd, mem, v) + offsetInGoSlice += 8 + case ssa.TypeV128: + load.asXmmUnaryRmR(sseOpcodeMovdqu, mem, v) + offsetInGoSlice += 16 + default: + panic("BUG") + } + cur = linkInstr(cur, load) + + if !isRegResult { + // We need to store it back to the result slot above rbp. + store := m.allocateInstr() + mem := newOperandMem(m.newAmodeImmReg(uint32(abi.ArgStackSize+r.Offset+16 /* to skip caller_rbp and ret_addr */), rbpVReg)) + switch r.Type { + case ssa.TypeI32: + store.asMovRM(v, mem, 4) + case ssa.TypeI64: + store.asMovRM(v, mem, 8) + case ssa.TypeF32: + store.asXmmMovRM(sseOpcodeMovss, v, mem) + case ssa.TypeF64: + store.asXmmMovRM(sseOpcodeMovsd, v, mem) + case ssa.TypeV128: + store.asXmmMovRM(sseOpcodeMovdqu, v, mem) + default: + panic("BUG") + } + cur = linkInstr(cur, store) + } + } + + // Before return, we need to restore the callee saved registers. + cur = m.restoreRegistersInExecutionContext(cur, execCtrPtr, calleeSavedVRegs) + + if argOverlapWithExecCtxOffset >= 0 { + // At this point execCtt is not used anymore, so we can finally store the + // result to the register which overlaps with the execution context pointer. + mem := newOperandMem(m.newAmodeImmReg(uint32(argOverlapWithExecCtxOffset), rspVReg)) + load := m.allocateInstr().asMov64MR(mem, execCtrPtr) + cur = linkInstr(cur, load) + } + + // Finally ready to return. + cur = m.revertRBPRSP(cur) + linkInstr(cur, m.allocateInstr().asRet()) + + m.encodeWithoutSSA(m.rootInstr) + return m.c.Buf() +} + +func (m *machine) saveRegistersInExecutionContext(cur *instruction, execCtx regalloc.VReg, regs []regalloc.VReg) *instruction { + offset := wazevoapi.ExecutionContextOffsetSavedRegistersBegin.I64() + for _, v := range regs { + store := m.allocateInstr() + mem := newOperandMem(m.newAmodeImmReg(uint32(offset), execCtx)) + switch v.RegType() { + case regalloc.RegTypeInt: + store.asMovRM(v, mem, 8) + case regalloc.RegTypeFloat: + store.asXmmMovRM(sseOpcodeMovdqu, v, mem) + default: + panic("BUG") + } + cur = linkInstr(cur, store) + offset += 16 // See execution context struct. Each register is 16 bytes-aligned unconditionally. + } + return cur +} + +func (m *machine) restoreRegistersInExecutionContext(cur *instruction, execCtx regalloc.VReg, regs []regalloc.VReg) *instruction { + offset := wazevoapi.ExecutionContextOffsetSavedRegistersBegin.I64() + for _, v := range regs { + load := m.allocateInstr() + mem := newOperandMem(m.newAmodeImmReg(uint32(offset), execCtx)) + switch v.RegType() { + case regalloc.RegTypeInt: + load.asMov64MR(mem, v) + case regalloc.RegTypeFloat: + load.asXmmUnaryRmR(sseOpcodeMovdqu, mem, v) + default: + panic("BUG") + } + cur = linkInstr(cur, load) + offset += 16 // See execution context struct. Each register is 16 bytes-aligned unconditionally. + } + return cur +} + +func (m *machine) storeReturnAddressAndExit(cur *instruction, execCtx regalloc.VReg) *instruction { + readRip := m.allocateInstr() + cur = linkInstr(cur, readRip) + + ripReg := r12VReg // Callee saved which is already saved. + saveRip := m.allocateInstr().asMovRM( + ripReg, + newOperandMem(m.newAmodeImmReg(wazevoapi.ExecutionContextOffsetGoCallReturnAddress.U32(), execCtx)), + 8, + ) + cur = linkInstr(cur, saveRip) + + exit := m.allocateExitSeq(execCtx) + cur = linkInstr(cur, exit) + + nop, l := m.allocateBrTarget() + cur = linkInstr(cur, nop) + readRip.asLEA(newOperandLabel(l), ripReg) + return cur +} + +// saveRequiredRegs is the set of registers that must be saved/restored during growing stack when there's insufficient +// stack space left. Basically this is the all allocatable registers except for RSP and RBP, and RAX which contains the +// execution context pointer. ExecCtx pointer is always the first argument so we don't need to save it. +var stackGrowSaveVRegs = []regalloc.VReg{ + rdxVReg, r12VReg, r13VReg, r14VReg, r15VReg, + rcxVReg, rbxVReg, rsiVReg, rdiVReg, r8VReg, r9VReg, r10VReg, r11VReg, + xmm8VReg, xmm9VReg, xmm10VReg, xmm11VReg, xmm12VReg, xmm13VReg, xmm14VReg, xmm15VReg, + xmm0VReg, xmm1VReg, xmm2VReg, xmm3VReg, xmm4VReg, xmm5VReg, xmm6VReg, xmm7VReg, +} + +// CompileStackGrowCallSequence implements backend.Machine. +func (m *machine) CompileStackGrowCallSequence() []byte { + cur := m.allocateNop() + m.rootInstr = cur + + cur = m.setupRBPRSP(cur) + + // Execution context is always the first argument. + execCtrPtr := raxVReg + + // Save the callee saved and argument registers. + cur = m.saveRegistersInExecutionContext(cur, execCtrPtr, stackGrowSaveVRegs) + + // Load the exitCode to the register. + exitCodeReg := r12VReg // Already saved. + cur = linkInstr(cur, m.allocateInstr().asImm(exitCodeReg, uint64(wazevoapi.ExitCodeGrowStack), false)) + + saveRsp, saveRbp, setExitCode := m.allocateExitInstructions(execCtrPtr, exitCodeReg) + cur = linkInstr(cur, setExitCode) + cur = linkInstr(cur, saveRsp) + cur = linkInstr(cur, saveRbp) + + // Ready to exit the execution. + cur = m.storeReturnAddressAndExit(cur, execCtrPtr) + + // After the exit, restore the saved registers. + cur = m.restoreRegistersInExecutionContext(cur, execCtrPtr, stackGrowSaveVRegs) + + // Finally ready to return. + cur = m.revertRBPRSP(cur) + linkInstr(cur, m.allocateInstr().asRet()) + + m.encodeWithoutSSA(m.rootInstr) + return m.c.Buf() +} + +// insertStackBoundsCheck will insert the instructions after `cur` to check the +// stack bounds, and if there's no sufficient spaces required for the function, +// exit the execution and try growing it in Go world. +func (m *machine) insertStackBoundsCheck(requiredStackSize int64, cur *instruction) *instruction { + // add $requiredStackSize, %rsp ;; Temporarily update the sp. + // cmp ExecutionContextOffsetStackBottomPtr(%rax), %rsp ;; Compare the stack bottom and the sp. + // ja .ok + // sub $requiredStackSize, %rsp ;; Reverse the temporary update. + // pushq r15 ;; save the temporary. + // mov $requiredStackSize, %r15 + // mov %15, ExecutionContextOffsetStackGrowRequiredSize(%rax) ;; Set the required size in the execution context. + // popq r15 ;; restore the temporary. + // callq *ExecutionContextOffsetStackGrowCallTrampolineAddress(%rax) ;; Call the Go function to grow the stack. + // jmp .cont + // .ok: + // sub $requiredStackSize, %rsp ;; Reverse the temporary update. + // .cont: + cur = m.addRSP(-int32(requiredStackSize), cur) + cur = linkInstr(cur, m.allocateInstr().asCmpRmiR(true, + newOperandMem(m.newAmodeImmReg(wazevoapi.ExecutionContextOffsetStackBottomPtr.U32(), raxVReg)), + rspVReg, true)) + + ja := m.allocateInstr() + cur = linkInstr(cur, ja) + + cur = m.addRSP(int32(requiredStackSize), cur) + + // Save the temporary. + + cur = linkInstr(cur, m.allocateInstr().asPush64(newOperandReg(r15VReg))) + // Load the required size to the temporary. + cur = linkInstr(cur, m.allocateInstr().asImm(r15VReg, uint64(requiredStackSize), true)) + // Set the required size in the execution context. + cur = linkInstr(cur, m.allocateInstr().asMovRM(r15VReg, + newOperandMem(m.newAmodeImmReg(wazevoapi.ExecutionContextOffsetStackGrowRequiredSize.U32(), raxVReg)), 8)) + // Restore the temporary. + cur = linkInstr(cur, m.allocateInstr().asPop64(r15VReg)) + // Call the Go function to grow the stack. + cur = linkInstr(cur, m.allocateInstr().asCallIndirect(newOperandMem(m.newAmodeImmReg( + wazevoapi.ExecutionContextOffsetStackGrowCallTrampolineAddress.U32(), raxVReg)), nil)) + // Jump to the continuation. + jmpToCont := m.allocateInstr() + cur = linkInstr(cur, jmpToCont) + + // .ok: + okInstr, ok := m.allocateBrTarget() + cur = linkInstr(cur, okInstr) + ja.asJmpIf(condNBE, newOperandLabel(ok)) + // On the ok path, we only need to reverse the temporary update. + cur = m.addRSP(int32(requiredStackSize), cur) + + // .cont: + contInstr, cont := m.allocateBrTarget() + cur = linkInstr(cur, contInstr) + jmpToCont.asJmp(newOperandLabel(cont)) + + return cur +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/cond.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/cond.go new file mode 100644 index 00000000000..75cbeab75f2 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/cond.go @@ -0,0 +1,168 @@ +package amd64 + +import ( + "fmt" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" +) + +type cond byte + +const ( + // condO represents (overflow) condition. + condO cond = iota + // condNO represents (no overflow) condition. + condNO + // condB represents (< unsigned) condition. + condB + // condNB represents (>= unsigned) condition. + condNB + // condZ represents (zero) condition. + condZ + // condNZ represents (not-zero) condition. + condNZ + // condBE represents (<= unsigned) condition. + condBE + // condNBE represents (> unsigned) condition. + condNBE + // condS represents (negative) condition. + condS + // condNS represents (not-negative) condition. + condNS + // condP represents (parity) condition. + condP + // condNP represents (not parity) condition. + condNP + // condL represents (< signed) condition. + condL + // condNL represents (>= signed) condition. + condNL + // condLE represents (<= signed) condition. + condLE + // condNLE represents (> signed) condition. + condNLE + + condInvalid +) + +func (c cond) String() string { + switch c { + case condO: + return "o" + case condNO: + return "no" + case condB: + return "b" + case condNB: + return "nb" + case condZ: + return "z" + case condNZ: + return "nz" + case condBE: + return "be" + case condNBE: + return "nbe" + case condS: + return "s" + case condNS: + return "ns" + case condL: + return "l" + case condNL: + return "nl" + case condLE: + return "le" + case condNLE: + return "nle" + case condP: + return "p" + case condNP: + return "np" + default: + panic("unreachable") + } +} + +func condFromSSAIntCmpCond(origin ssa.IntegerCmpCond) cond { + switch origin { + case ssa.IntegerCmpCondEqual: + return condZ + case ssa.IntegerCmpCondNotEqual: + return condNZ + case ssa.IntegerCmpCondSignedLessThan: + return condL + case ssa.IntegerCmpCondSignedGreaterThanOrEqual: + return condNL + case ssa.IntegerCmpCondSignedGreaterThan: + return condNLE + case ssa.IntegerCmpCondSignedLessThanOrEqual: + return condLE + case ssa.IntegerCmpCondUnsignedLessThan: + return condB + case ssa.IntegerCmpCondUnsignedGreaterThanOrEqual: + return condNB + case ssa.IntegerCmpCondUnsignedGreaterThan: + return condNBE + case ssa.IntegerCmpCondUnsignedLessThanOrEqual: + return condBE + default: + panic("unreachable") + } +} + +func condFromSSAFloatCmpCond(origin ssa.FloatCmpCond) cond { + switch origin { + case ssa.FloatCmpCondGreaterThanOrEqual: + return condNB + case ssa.FloatCmpCondGreaterThan: + return condNBE + case ssa.FloatCmpCondEqual, ssa.FloatCmpCondNotEqual, ssa.FloatCmpCondLessThan, ssa.FloatCmpCondLessThanOrEqual: + panic(fmt.Sprintf("cond %s must be treated as a special case", origin)) + default: + panic("unreachable") + } +} + +func (c cond) encoding() byte { + return byte(c) +} + +func (c cond) invert() cond { + switch c { + case condO: + return condNO + case condNO: + return condO + case condB: + return condNB + case condNB: + return condB + case condZ: + return condNZ + case condNZ: + return condZ + case condBE: + return condNBE + case condNBE: + return condBE + case condS: + return condNS + case condNS: + return condS + case condP: + return condNP + case condNP: + return condP + case condL: + return condNL + case condNL: + return condL + case condLE: + return condNLE + case condNLE: + return condLE + default: + panic("unreachable") + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/ext.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/ext.go new file mode 100644 index 00000000000..5e731e8229c --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/ext.go @@ -0,0 +1,35 @@ +package amd64 + +// extMode represents the mode of extension in movzx/movsx. +type extMode byte + +const ( + // extModeBL represents Byte -> Longword. + extModeBL extMode = iota + // extModeBQ represents Byte -> Quadword. + extModeBQ + // extModeWL represents Word -> Longword. + extModeWL + // extModeWQ represents Word -> Quadword. + extModeWQ + // extModeLQ represents Longword -> Quadword. + extModeLQ +) + +// String implements fmt.Stringer. +func (e extMode) String() string { + switch e { + case extModeBL: + return "bl" + case extModeBQ: + return "bq" + case extModeWL: + return "wl" + case extModeWQ: + return "wq" + case extModeLQ: + return "lq" + default: + panic("BUG: invalid ext mode") + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/instr.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/instr.go new file mode 100644 index 00000000000..901c87aafcc --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/instr.go @@ -0,0 +1,2495 @@ +package amd64 + +import ( + "fmt" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" +) + +type instruction struct { + prev, next *instruction + op1, op2 operand + u1, u2 uint64 + b1 bool + addedBeforeRegAlloc bool + kind instructionKind +} + +// IsCall implements regalloc.Instr. +func (i *instruction) IsCall() bool { return i.kind == call } + +// IsIndirectCall implements regalloc.Instr. +func (i *instruction) IsIndirectCall() bool { + return i.kind == callIndirect +} + +// IsReturn implements regalloc.Instr. +func (i *instruction) IsReturn() bool { return i.kind == ret } + +// String implements regalloc.Instr. +func (i *instruction) String() string { + switch i.kind { + case nop0: + return "nop" + case sourceOffsetInfo: + return fmt.Sprintf("source_offset_info %d", i.u1) + case ret: + return "ret" + case imm: + if i.b1 { + return fmt.Sprintf("movabsq $%d, %s", int64(i.u1), i.op2.format(true)) + } else { + return fmt.Sprintf("movl $%d, %s", int32(i.u1), i.op2.format(false)) + } + case aluRmiR: + return fmt.Sprintf("%s %s, %s", aluRmiROpcode(i.u1), i.op1.format(i.b1), i.op2.format(i.b1)) + case movRR: + if i.b1 { + return fmt.Sprintf("movq %s, %s", i.op1.format(true), i.op2.format(true)) + } else { + return fmt.Sprintf("movl %s, %s", i.op1.format(false), i.op2.format(false)) + } + case xmmRmR: + return fmt.Sprintf("%s %s, %s", sseOpcode(i.u1), i.op1.format(false), i.op2.format(false)) + case gprToXmm: + return fmt.Sprintf("%s %s, %s", sseOpcode(i.u1), i.op1.format(i.b1), i.op2.format(i.b1)) + case xmmUnaryRmR: + return fmt.Sprintf("%s %s, %s", sseOpcode(i.u1), i.op1.format(false), i.op2.format(false)) + case xmmUnaryRmRImm: + return fmt.Sprintf("%s $%d, %s, %s", sseOpcode(i.u1), roundingMode(i.u2), i.op1.format(false), i.op2.format(false)) + case unaryRmR: + var suffix string + if i.b1 { + suffix = "q" + } else { + suffix = "l" + } + return fmt.Sprintf("%s%s %s, %s", unaryRmROpcode(i.u1), suffix, i.op1.format(i.b1), i.op2.format(i.b1)) + case not: + var op string + if i.b1 { + op = "notq" + } else { + op = "notl" + } + return fmt.Sprintf("%s %s", op, i.op1.format(i.b1)) + case neg: + var op string + if i.b1 { + op = "negq" + } else { + op = "negl" + } + return fmt.Sprintf("%s %s", op, i.op1.format(i.b1)) + case div: + var prefix string + var op string + if i.b1 { + op = "divq" + } else { + op = "divl" + } + if i.u1 != 0 { + prefix = "i" + } + return fmt.Sprintf("%s%s %s", prefix, op, i.op1.format(i.b1)) + case mulHi: + signed, _64 := i.u1 != 0, i.b1 + var op string + switch { + case signed && _64: + op = "imulq" + case !signed && _64: + op = "mulq" + case signed && !_64: + op = "imull" + case !signed && !_64: + op = "mull" + } + return fmt.Sprintf("%s %s", op, i.op1.format(i.b1)) + case signExtendData: + var op string + if i.b1 { + op = "cqo" + } else { + op = "cdq" + } + return op + case movzxRmR: + return fmt.Sprintf("movzx.%s %s, %s", extMode(i.u1), i.op1.format(true), i.op2.format(true)) + case mov64MR: + return fmt.Sprintf("movq %s, %s", i.op1.format(true), i.op2.format(true)) + case lea: + return fmt.Sprintf("lea %s, %s", i.op1.format(true), i.op2.format(true)) + case movsxRmR: + return fmt.Sprintf("movsx.%s %s, %s", extMode(i.u1), i.op1.format(true), i.op2.format(true)) + case movRM: + var suffix string + switch i.u1 { + case 1: + suffix = "b" + case 2: + suffix = "w" + case 4: + suffix = "l" + case 8: + suffix = "q" + } + return fmt.Sprintf("mov.%s %s, %s", suffix, i.op1.format(true), i.op2.format(true)) + case shiftR: + var suffix string + if i.b1 { + suffix = "q" + } else { + suffix = "l" + } + return fmt.Sprintf("%s%s %s, %s", shiftROp(i.u1), suffix, i.op1.format(false), i.op2.format(i.b1)) + case xmmRmiReg: + return fmt.Sprintf("%s %s, %s", sseOpcode(i.u1), i.op1.format(true), i.op2.format(true)) + case cmpRmiR: + var op, suffix string + if i.u1 != 0 { + op = "cmp" + } else { + op = "test" + } + if i.b1 { + suffix = "q" + } else { + suffix = "l" + } + if op == "test" && i.op1.kind == operandKindMem { + // Print consistently with AT&T syntax. + return fmt.Sprintf("%s%s %s, %s", op, suffix, i.op2.format(i.b1), i.op1.format(i.b1)) + } + return fmt.Sprintf("%s%s %s, %s", op, suffix, i.op1.format(i.b1), i.op2.format(i.b1)) + case setcc: + return fmt.Sprintf("set%s %s", cond(i.u1), i.op2.format(true)) + case cmove: + var suffix string + if i.b1 { + suffix = "q" + } else { + suffix = "l" + } + return fmt.Sprintf("cmov%s%s %s, %s", cond(i.u1), suffix, i.op1.format(i.b1), i.op2.format(i.b1)) + case push64: + return fmt.Sprintf("pushq %s", i.op1.format(true)) + case pop64: + return fmt.Sprintf("popq %s", i.op1.format(true)) + case xmmMovRM: + return fmt.Sprintf("%s %s, %s", sseOpcode(i.u1), i.op1.format(true), i.op2.format(true)) + case xmmLoadConst: + panic("TODO") + case xmmToGpr: + return fmt.Sprintf("%s %s, %s", sseOpcode(i.u1), i.op1.format(i.b1), i.op2.format(i.b1)) + case cvtUint64ToFloatSeq: + panic("TODO") + case cvtFloatToSintSeq: + panic("TODO") + case cvtFloatToUintSeq: + panic("TODO") + case xmmMinMaxSeq: + panic("TODO") + case xmmCmpRmR: + return fmt.Sprintf("%s %s, %s", sseOpcode(i.u1), i.op1.format(false), i.op2.format(false)) + case xmmRmRImm: + op := sseOpcode(i.u1) + r1, r2 := i.op1.format(op == sseOpcodePextrq || op == sseOpcodePinsrq), + i.op2.format(op == sseOpcodePextrq || op == sseOpcodePinsrq) + return fmt.Sprintf("%s $%d, %s, %s", op, i.u2, r1, r2) + case jmp: + return fmt.Sprintf("jmp %s", i.op1.format(true)) + case jmpIf: + return fmt.Sprintf("j%s %s", cond(i.u1), i.op1.format(true)) + case jmpTableIsland: + return fmt.Sprintf("jump_table_island: jmp_table_index=%d", i.u1) + case exitSequence: + return fmt.Sprintf("exit_sequence %s", i.op1.format(true)) + case ud2: + return "ud2" + case call: + return fmt.Sprintf("call %s", ssa.FuncRef(i.u1)) + case callIndirect: + return fmt.Sprintf("callq *%s", i.op1.format(true)) + case xchg: + var suffix string + switch i.u1 { + case 1: + suffix = "b" + case 2: + suffix = "w" + case 4: + suffix = "l" + case 8: + suffix = "q" + } + return fmt.Sprintf("xchg.%s %s, %s", suffix, i.op1.format(true), i.op2.format(true)) + case zeros: + return fmt.Sprintf("xor %s, %s", i.op2.format(true), i.op2.format(true)) + case fcvtToSintSequence: + execCtx, src, tmpGp, tmpGp2, tmpXmm, src64, dst64, sat := i.fcvtToSintSequenceData() + return fmt.Sprintf( + "fcvtToSintSequence execCtx=%s, src=%s, tmpGp=%s, tmpGp2=%s, tmpXmm=%s, src64=%v, dst64=%v, sat=%v", + formatVRegSized(execCtx, true), + formatVRegSized(src, true), + formatVRegSized(tmpGp, true), + formatVRegSized(tmpGp2, true), + formatVRegSized(tmpXmm, true), src64, dst64, sat) + case fcvtToUintSequence: + execCtx, src, tmpGp, tmpGp2, tmpXmm, tmpXmm2, src64, dst64, sat := i.fcvtToUintSequenceData() + return fmt.Sprintf( + "fcvtToUintSequence execCtx=%s, src=%s, tmpGp=%s, tmpGp2=%s, tmpXmm=%s, tmpXmm2=%s, src64=%v, dst64=%v, sat=%v", + formatVRegSized(execCtx, true), + formatVRegSized(src, true), + formatVRegSized(tmpGp, true), + formatVRegSized(tmpGp2, true), + formatVRegSized(tmpXmm, true), + formatVRegSized(tmpXmm2, true), src64, dst64, sat) + case idivRemSequence: + execCtx, divisor, tmpGp, isDiv, signed, _64 := i.idivRemSequenceData() + return fmt.Sprintf("idivRemSequence execCtx=%s, divisor=%s, tmpGp=%s, isDiv=%v, signed=%v, _64=%v", + formatVRegSized(execCtx, true), formatVRegSized(divisor, _64), formatVRegSized(tmpGp, _64), isDiv, signed, _64) + case defineUninitializedReg: + return fmt.Sprintf("defineUninitializedReg %s", i.op2.format(true)) + case xmmCMov: + return fmt.Sprintf("xmmcmov%s %s, %s", cond(i.u1), i.op1.format(true), i.op2.format(true)) + case blendvpd: + return fmt.Sprintf("blendvpd %s, %s, %%xmm0", i.op1.format(false), i.op2.format(false)) + case mfence: + return "mfence" + case lockcmpxchg: + var suffix string + switch i.u1 { + case 1: + suffix = "b" + case 2: + suffix = "w" + case 4: + suffix = "l" + case 8: + suffix = "q" + } + return fmt.Sprintf("lock cmpxchg.%s %s, %s", suffix, i.op1.format(true), i.op2.format(true)) + case lockxadd: + var suffix string + switch i.u1 { + case 1: + suffix = "b" + case 2: + suffix = "w" + case 4: + suffix = "l" + case 8: + suffix = "q" + } + return fmt.Sprintf("lock xadd.%s %s, %s", suffix, i.op1.format(true), i.op2.format(true)) + + case nopUseReg: + return fmt.Sprintf("nop_use_reg %s", i.op1.format(true)) + + case tailCall: + return fmt.Sprintf("tailCall %s", ssa.FuncRef(i.u1)) + case tailCallIndirect: + return fmt.Sprintf("tailCallIndirect %s", i.op1.format(true)) + + default: + panic(fmt.Sprintf("BUG: %d", int(i.kind))) + } +} + +// Defs implements regalloc.Instr. +func (i *instruction) Defs(regs *[]regalloc.VReg) []regalloc.VReg { + *regs = (*regs)[:0] + switch dk := defKinds[i.kind]; dk { + case defKindNone: + case defKindOp2: + *regs = append(*regs, i.op2.reg()) + case defKindCall: + _, _, retIntRealRegs, retFloatRealRegs, _ := backend.ABIInfoFromUint64(i.u2) + for i := byte(0); i < retIntRealRegs; i++ { + *regs = append(*regs, regInfo.RealRegToVReg[intArgResultRegs[i]]) + } + for i := byte(0); i < retFloatRealRegs; i++ { + *regs = append(*regs, regInfo.RealRegToVReg[floatArgResultRegs[i]]) + } + case defKindDivRem: + _, _, _, isDiv, _, _ := i.idivRemSequenceData() + if isDiv { + *regs = append(*regs, raxVReg) + } else { + *regs = append(*regs, rdxVReg) + } + default: + panic(fmt.Sprintf("BUG: invalid defKind \"%s\" for %s", dk, i)) + } + return *regs +} + +// Uses implements regalloc.Instr. +func (i *instruction) Uses(regs *[]regalloc.VReg) []regalloc.VReg { + *regs = (*regs)[:0] + switch uk := useKinds[i.kind]; uk { + case useKindNone: + case useKindOp1Op2Reg, useKindOp1RegOp2: + opAny, opReg := &i.op1, &i.op2 + if uk == useKindOp1RegOp2 { + opAny, opReg = opReg, opAny + } + // The destination operand (op2) can be only reg, + // the source operand (op1) can be imm32, reg or mem. + switch opAny.kind { + case operandKindReg: + *regs = append(*regs, opAny.reg()) + case operandKindMem: + opAny.addressMode().uses(regs) + case operandKindImm32: + default: + panic(fmt.Sprintf("BUG: invalid operand: %s", i)) + } + if opReg.kind != operandKindReg { + panic(fmt.Sprintf("BUG: invalid operand: %s", i)) + } + *regs = append(*regs, opReg.reg()) + case useKindOp1: + op := i.op1 + switch op.kind { + case operandKindReg: + *regs = append(*regs, op.reg()) + case operandKindMem: + op.addressMode().uses(regs) + case operandKindImm32, operandKindLabel: + default: + panic(fmt.Sprintf("BUG: invalid operand: %s", i)) + } + case useKindCallInd, useKindTailCallInd: + op := i.op1 + switch op.kind { + case operandKindReg: + *regs = append(*regs, op.reg()) + case operandKindMem: + op.addressMode().uses(regs) + default: + panic(fmt.Sprintf("BUG: invalid operand: %s", i)) + } + fallthrough + case useKindCall: + argIntRealRegs, argFloatRealRegs, _, _, _ := backend.ABIInfoFromUint64(i.u2) + for i := byte(0); i < argIntRealRegs; i++ { + *regs = append(*regs, regInfo.RealRegToVReg[intArgResultRegs[i]]) + } + for i := byte(0); i < argFloatRealRegs; i++ { + *regs = append(*regs, regInfo.RealRegToVReg[floatArgResultRegs[i]]) + } + case useKindFcvtToSintSequence: + execCtx, src, tmpGp, tmpGp2, tmpXmm, _, _, _ := i.fcvtToSintSequenceData() + *regs = append(*regs, execCtx, src, tmpGp, tmpGp2, tmpXmm) + case useKindFcvtToUintSequence: + execCtx, src, tmpGp, tmpGp2, tmpXmm, tmpXmm2, _, _, _ := i.fcvtToUintSequenceData() + *regs = append(*regs, execCtx, src, tmpGp, tmpGp2, tmpXmm, tmpXmm2) + case useKindDivRem: + execCtx, divisor, tmpGp, _, _, _ := i.idivRemSequenceData() + // idiv uses rax and rdx as implicit operands. + *regs = append(*regs, raxVReg, rdxVReg, execCtx, divisor, tmpGp) + case useKindBlendvpd: + *regs = append(*regs, xmm0VReg) + + opAny, opReg := &i.op1, &i.op2 + switch opAny.kind { + case operandKindReg: + *regs = append(*regs, opAny.reg()) + case operandKindMem: + opAny.addressMode().uses(regs) + default: + panic(fmt.Sprintf("BUG: invalid operand: %s", i)) + } + if opReg.kind != operandKindReg { + panic(fmt.Sprintf("BUG: invalid operand: %s", i)) + } + *regs = append(*regs, opReg.reg()) + + case useKindRaxOp1RegOp2: + opReg, opAny := &i.op1, &i.op2 + *regs = append(*regs, raxVReg, opReg.reg()) + switch opAny.kind { + case operandKindReg: + *regs = append(*regs, opAny.reg()) + case operandKindMem: + opAny.addressMode().uses(regs) + default: + panic(fmt.Sprintf("BUG: invalid operand: %s", i)) + } + if opReg.kind != operandKindReg { + panic(fmt.Sprintf("BUG: invalid operand: %s", i)) + } + + default: + panic(fmt.Sprintf("BUG: invalid useKind %s for %s", uk, i)) + } + return *regs +} + +// AssignUse implements regalloc.Instr. +func (i *instruction) AssignUse(index int, v regalloc.VReg) { + switch uk := useKinds[i.kind]; uk { + case useKindNone: + case useKindCallInd, useKindTailCallInd: + if index != 0 { + panic("BUG") + } + op := &i.op1 + switch op.kind { + case operandKindReg: + if uk == useKindTailCallInd && v != r11VReg { + panic("BUG") + } + op.setReg(v) + case operandKindMem: + op.addressMode().assignUses(index, v) + default: + panic("BUG") + } + case useKindOp1Op2Reg, useKindOp1RegOp2: + op, opMustBeReg := &i.op1, &i.op2 + if uk == useKindOp1RegOp2 { + op, opMustBeReg = opMustBeReg, op + } + switch op.kind { + case operandKindReg: + if index == 0 { + op.setReg(v) + } else if index == 1 { + opMustBeReg.setReg(v) + } else { + panic("BUG") + } + case operandKindMem: + nregs := op.addressMode().nregs() + if index < nregs { + op.addressMode().assignUses(index, v) + } else if index == nregs { + opMustBeReg.setReg(v) + } else { + panic("BUG") + } + case operandKindImm32: + if index == 0 { + opMustBeReg.setReg(v) + } else { + panic("BUG") + } + default: + panic(fmt.Sprintf("BUG: invalid operand pair: %s", i)) + } + case useKindOp1: + op := &i.op1 + switch op.kind { + case operandKindReg: + if index != 0 { + panic("BUG") + } + op.setReg(v) + case operandKindMem: + op.addressMode().assignUses(index, v) + default: + panic(fmt.Sprintf("BUG: invalid operand: %s", i)) + } + case useKindFcvtToSintSequence: + switch index { + case 0: + i.op1.addressMode().base = v + case 1: + i.op1.addressMode().index = v + case 2: + i.op2.addressMode().base = v + case 3: + i.op2.addressMode().index = v + case 4: + i.u1 = uint64(v) + default: + panic("BUG") + } + case useKindFcvtToUintSequence: + switch index { + case 0: + i.op1.addressMode().base = v + case 1: + i.op1.addressMode().index = v + case 2: + i.op2.addressMode().base = v + case 3: + i.op2.addressMode().index = v + case 4: + i.u1 = uint64(v) + case 5: + i.u2 = uint64(v) + default: + panic("BUG") + } + case useKindDivRem: + switch index { + case 0: + if v != raxVReg { + panic("BUG") + } + case 1: + if v != rdxVReg { + panic("BUG") + } + case 2: + i.op1.setReg(v) + case 3: + i.op2.setReg(v) + case 4: + i.u1 = uint64(v) + default: + panic("BUG") + } + case useKindBlendvpd: + op, opMustBeReg := &i.op1, &i.op2 + if index == 0 { + if v.RealReg() != xmm0 { + panic("BUG") + } + } else { + switch op.kind { + case operandKindReg: + switch index { + case 1: + op.setReg(v) + case 2: + opMustBeReg.setReg(v) + default: + panic("BUG") + } + case operandKindMem: + nregs := op.addressMode().nregs() + index-- + if index < nregs { + op.addressMode().assignUses(index, v) + } else if index == nregs { + opMustBeReg.setReg(v) + } else { + panic("BUG") + } + default: + panic(fmt.Sprintf("BUG: invalid operand pair: %s", i)) + } + } + + case useKindRaxOp1RegOp2: + switch index { + case 0: + if v.RealReg() != rax { + panic("BUG") + } + case 1: + i.op1.setReg(v) + default: + op := &i.op2 + switch op.kind { + case operandKindReg: + switch index { + case 1: + op.setReg(v) + case 2: + op.setReg(v) + default: + panic("BUG") + } + case operandKindMem: + nregs := op.addressMode().nregs() + index -= 2 + if index < nregs { + op.addressMode().assignUses(index, v) + } else if index == nregs { + op.setReg(v) + } else { + panic("BUG") + } + default: + panic(fmt.Sprintf("BUG: invalid operand pair: %s", i)) + } + } + default: + panic(fmt.Sprintf("BUG: invalid useKind %s for %s", uk, i)) + } +} + +// AssignDef implements regalloc.Instr. +func (i *instruction) AssignDef(reg regalloc.VReg) { + switch dk := defKinds[i.kind]; dk { + case defKindNone: + case defKindOp2: + i.op2.setReg(reg) + default: + panic(fmt.Sprintf("BUG: invalid defKind \"%s\" for %s", dk, i)) + } +} + +// IsCopy implements regalloc.Instr. +func (i *instruction) IsCopy() bool { + k := i.kind + if k == movRR { + return true + } + if k == xmmUnaryRmR { + if i.op1.kind == operandKindReg { + sse := sseOpcode(i.u1) + return sse == sseOpcodeMovss || sse == sseOpcodeMovsd || sse == sseOpcodeMovdqu + } + } + return false +} + +func resetInstruction(i *instruction) { + *i = instruction{} +} + +func (i *instruction) asNop0WithLabel(label label) *instruction { //nolint + i.kind = nop0 + i.u1 = uint64(label) + return i +} + +func (i *instruction) nop0Label() label { + return label(i.u1) +} + +type instructionKind byte + +const ( + nop0 instructionKind = iota + 1 + + // Integer arithmetic/bit-twiddling: (add sub and or xor mul, etc.) (32 64) (reg addr imm) reg + aluRmiR + + // Instructions on GPR that only read src and defines dst (dst is not modified): bsr, etc. + unaryRmR + + // Bitwise not + not + + // Integer negation + neg + + // Integer quotient and remainder: (div idiv) $rax $rdx (reg addr) + div + + // The high bits (RDX) of a (un)signed multiply: RDX:RAX := RAX * rhs. + mulHi + + // Do a sign-extend based on the sign of the value in rax into rdx: (cwd cdq cqo) + // or al into ah: (cbw) + signExtendData + + // Constant materialization: (imm32 imm64) reg. + // Either: movl $imm32, %reg32 or movabsq $imm64, %reg64. + imm + + // GPR to GPR move: mov (64 32) reg reg. + movRR + + // movzxRmR is zero-extended loads or move (R to R), except for 64 bits: movz (bl bq wl wq lq) addr reg. + // Note that the lq variant doesn't really exist since the default zero-extend rule makes it + // unnecessary. For that case we emit the equivalent "movl AM, reg32". + movzxRmR + + // mov64MR is a plain 64-bit integer load, since movzxRmR can't represent that. + mov64MR + + // Loads the memory address of addr into dst. + lea + + // Sign-extended loads and moves: movs (bl bq wl wq lq) addr reg. + movsxRmR + + // Integer stores: mov (b w l q) reg addr. + movRM + + // Arithmetic shifts: (shl shr sar) (b w l q) imm reg. + shiftR + + // Arithmetic SIMD shifts. + xmmRmiReg + + // Integer comparisons/tests: cmp or test (b w l q) (reg addr imm) reg. + cmpRmiR + + // Materializes the requested condition code in the destination reg. + setcc + + // Integer conditional move. + // Overwrites the destination register. + cmove + + // pushq (reg addr imm) + push64 + + // popq reg + pop64 + + // XMM (scalar or vector) binary op: (add sub and or xor mul adc? sbb?) (32 64) (reg addr) reg + xmmRmR + + // XMM (scalar or vector) unary op: mov between XMM registers (32 64) (reg addr) reg. + // + // This differs from xmmRmR in that the dst register of xmmUnaryRmR is not used in the + // computation of the instruction dst value and so does not have to be a previously valid + // value. This is characteristic of mov instructions. + xmmUnaryRmR + + // XMM (scalar or vector) unary op with immediate: roundss, roundsd, etc. + // + // This differs from XMM_RM_R_IMM in that the dst register of + // XmmUnaryRmRImm is not used in the computation of the instruction dst + // value and so does not have to be a previously valid value. + xmmUnaryRmRImm + + // XMM (scalar or vector) unary op (from xmm to mem): stores, movd, movq + xmmMovRM + + // XMM (vector) unary op (to move a constant value into an xmm register): movups + xmmLoadConst + + // XMM (scalar) unary op (from xmm to integer reg): movd, movq, cvtts{s,d}2si + xmmToGpr + + // XMM (scalar) unary op (from integer to float reg): movd, movq, cvtsi2s{s,d} + gprToXmm + + // Converts an unsigned int64 to a float32/float64. + cvtUint64ToFloatSeq + + // Converts a scalar xmm to a signed int32/int64. + cvtFloatToSintSeq + + // Converts a scalar xmm to an unsigned int32/int64. + cvtFloatToUintSeq + + // A sequence to compute min/max with the proper NaN semantics for xmm registers. + xmmMinMaxSeq + + // Float comparisons/tests: cmp (b w l q) (reg addr imm) reg. + xmmCmpRmR + + // A binary XMM instruction with an 8-bit immediate: e.g. cmp (ps pd) imm (reg addr) reg + xmmRmRImm + + // Direct call: call simm32. + // Note that the offset is the relative to the *current RIP*, which points to the first byte of the next instruction. + call + + // Indirect call: callq (reg mem). + callIndirect + + // Return. + ret + + // Jump: jmp (reg, mem, imm32 or label) + jmp + + // Jump conditionally: jcond cond label. + jmpIf + + // jmpTableIsland is to emit the jump table. + jmpTableIsland + + // exitSequence exits the execution and go back to the Go world. + exitSequence + + // An instruction that will always trigger the illegal instruction exception. + ud2 + + // xchg is described in https://www.felixcloutier.com/x86/xchg. + // This instruction uses two operands, where one of them can be a memory address, and swaps their values. + // If the dst is a memory address, the execution is atomic. + xchg + + // lockcmpxchg is the cmpxchg instruction https://www.felixcloutier.com/x86/cmpxchg with a lock prefix. + lockcmpxchg + + // zeros puts zeros into the destination register. This is implemented as xor reg, reg for + // either integer or XMM registers. The reason why we have this instruction instead of using aluRmiR + // is that it requires the already-defined registers. From reg alloc's perspective, this defines + // the destination register and takes no inputs. + zeros + + // sourceOffsetInfo is a dummy instruction to emit source offset info. + // The existence of this instruction does not affect the execution. + sourceOffsetInfo + + // defineUninitializedReg is a no-op instruction that defines a register without a defining instruction. + defineUninitializedReg + + // fcvtToSintSequence is a sequence of instructions to convert a float to a signed integer. + fcvtToSintSequence + + // fcvtToUintSequence is a sequence of instructions to convert a float to an unsigned integer. + fcvtToUintSequence + + // xmmCMov is a conditional move instruction for XMM registers. Lowered after register allocation. + xmmCMov + + // idivRemSequence is a sequence of instructions to compute both the quotient and remainder of a division. + idivRemSequence + + // blendvpd is https://www.felixcloutier.com/x86/blendvpd. + blendvpd + + // mfence is https://www.felixcloutier.com/x86/mfence + mfence + + // lockxadd is xadd https://www.felixcloutier.com/x86/xadd with a lock prefix. + lockxadd + + // nopUseReg is a meta instruction that uses one register and does nothing. + nopUseReg + + // tailCall is a meta instruction that emits a tail call. + tailCall + + // tailCallIndirect is a meta instruction that emits a tail call with an indirect call. + tailCallIndirect + + instrMax +) + +func (i *instruction) asMFence() *instruction { + i.kind = mfence + return i +} + +func (i *instruction) asNopUseReg(r regalloc.VReg) *instruction { + i.kind = nopUseReg + i.op1 = newOperandReg(r) + return i +} + +func (i *instruction) asIdivRemSequence(execCtx, divisor, tmpGp regalloc.VReg, isDiv, signed, _64 bool) *instruction { + i.kind = idivRemSequence + i.op1 = newOperandReg(execCtx) + i.op2 = newOperandReg(divisor) + i.u1 = uint64(tmpGp) + if isDiv { + i.u2 |= 1 + } + if signed { + i.u2 |= 2 + } + if _64 { + i.u2 |= 4 + } + return i +} + +func (i *instruction) idivRemSequenceData() ( + execCtx, divisor, tmpGp regalloc.VReg, isDiv, signed, _64 bool, +) { + if i.kind != idivRemSequence { + panic("BUG") + } + return i.op1.reg(), i.op2.reg(), regalloc.VReg(i.u1), i.u2&1 != 0, i.u2&2 != 0, i.u2&4 != 0 +} + +func (i *instruction) asXmmCMov(cc cond, x operand, rd regalloc.VReg, size byte) *instruction { + i.kind = xmmCMov + i.op1 = x + i.op2 = newOperandReg(rd) + i.u1 = uint64(cc) + i.u2 = uint64(size) + return i +} + +func (i *instruction) asDefineUninitializedReg(r regalloc.VReg) *instruction { + i.kind = defineUninitializedReg + i.op2 = newOperandReg(r) + return i +} + +func (m *machine) allocateFcvtToUintSequence( + execCtx, src, tmpGp, tmpGp2, tmpXmm, tmpXmm2 regalloc.VReg, + src64, dst64, sat bool, +) *instruction { + i := m.allocateInstr() + i.kind = fcvtToUintSequence + op1a := m.amodePool.Allocate() + op2a := m.amodePool.Allocate() + i.op1 = newOperandMem(op1a) + i.op2 = newOperandMem(op2a) + if src64 { + op1a.imm32 = 1 + } else { + op1a.imm32 = 0 + } + if dst64 { + op1a.imm32 |= 2 + } + if sat { + op1a.imm32 |= 4 + } + + op1a.base = execCtx + op1a.index = src + op2a.base = tmpGp + op2a.index = tmpGp2 + i.u1 = uint64(tmpXmm) + i.u2 = uint64(tmpXmm2) + return i +} + +func (i *instruction) fcvtToUintSequenceData() ( + execCtx, src, tmpGp, tmpGp2, tmpXmm, tmpXmm2 regalloc.VReg, src64, dst64, sat bool, +) { + if i.kind != fcvtToUintSequence { + panic("BUG") + } + op1a := i.op1.addressMode() + op2a := i.op2.addressMode() + return op1a.base, op1a.index, op2a.base, op2a.index, regalloc.VReg(i.u1), regalloc.VReg(i.u2), + op1a.imm32&1 != 0, op1a.imm32&2 != 0, op1a.imm32&4 != 0 +} + +func (m *machine) allocateFcvtToSintSequence( + execCtx, src, tmpGp, tmpGp2, tmpXmm regalloc.VReg, + src64, dst64, sat bool, +) *instruction { + i := m.allocateInstr() + i.kind = fcvtToSintSequence + op1a := m.amodePool.Allocate() + op2a := m.amodePool.Allocate() + i.op1 = newOperandMem(op1a) + i.op2 = newOperandMem(op2a) + op1a.base = execCtx + op1a.index = src + op2a.base = tmpGp + op2a.index = tmpGp2 + i.u1 = uint64(tmpXmm) + if src64 { + i.u2 = 1 + } else { + i.u2 = 0 + } + if dst64 { + i.u2 |= 2 + } + if sat { + i.u2 |= 4 + } + return i +} + +func (i *instruction) fcvtToSintSequenceData() ( + execCtx, src, tmpGp, tmpGp2, tmpXmm regalloc.VReg, src64, dst64, sat bool, +) { + if i.kind != fcvtToSintSequence { + panic("BUG") + } + op1a := i.op1.addressMode() + op2a := i.op2.addressMode() + return op1a.base, op1a.index, op2a.base, op2a.index, regalloc.VReg(i.u1), + i.u2&1 != 0, i.u2&2 != 0, i.u2&4 != 0 +} + +func (k instructionKind) String() string { + switch k { + case nop0: + return "nop" + case ret: + return "ret" + case imm: + return "imm" + case aluRmiR: + return "aluRmiR" + case movRR: + return "movRR" + case xmmRmR: + return "xmmRmR" + case gprToXmm: + return "gprToXmm" + case xmmUnaryRmR: + return "xmmUnaryRmR" + case xmmUnaryRmRImm: + return "xmmUnaryRmRImm" + case unaryRmR: + return "unaryRmR" + case not: + return "not" + case neg: + return "neg" + case div: + return "div" + case mulHi: + return "mulHi" + case signExtendData: + return "signExtendData" + case movzxRmR: + return "movzxRmR" + case mov64MR: + return "mov64MR" + case lea: + return "lea" + case movsxRmR: + return "movsxRmR" + case movRM: + return "movRM" + case shiftR: + return "shiftR" + case xmmRmiReg: + return "xmmRmiReg" + case cmpRmiR: + return "cmpRmiR" + case setcc: + return "setcc" + case cmove: + return "cmove" + case push64: + return "push64" + case pop64: + return "pop64" + case xmmMovRM: + return "xmmMovRM" + case xmmLoadConst: + return "xmmLoadConst" + case xmmToGpr: + return "xmmToGpr" + case cvtUint64ToFloatSeq: + return "cvtUint64ToFloatSeq" + case cvtFloatToSintSeq: + return "cvtFloatToSintSeq" + case cvtFloatToUintSeq: + return "cvtFloatToUintSeq" + case xmmMinMaxSeq: + return "xmmMinMaxSeq" + case xmmCmpRmR: + return "xmmCmpRmR" + case xmmRmRImm: + return "xmmRmRImm" + case jmpIf: + return "jmpIf" + case jmp: + return "jmp" + case jmpTableIsland: + return "jmpTableIsland" + case exitSequence: + return "exit_sequence" + case ud2: + return "ud2" + case xchg: + return "xchg" + case zeros: + return "zeros" + case fcvtToSintSequence: + return "fcvtToSintSequence" + case fcvtToUintSequence: + return "fcvtToUintSequence" + case xmmCMov: + return "xmmCMov" + case idivRemSequence: + return "idivRemSequence" + case mfence: + return "mfence" + case lockcmpxchg: + return "lockcmpxchg" + case lockxadd: + return "lockxadd" + case tailCall: + return "tailCall" + case tailCallIndirect: + return "tailCallIndirect" + default: + panic("BUG") + } +} + +type aluRmiROpcode byte + +const ( + aluRmiROpcodeAdd aluRmiROpcode = iota + 1 + aluRmiROpcodeSub + aluRmiROpcodeAnd + aluRmiROpcodeOr + aluRmiROpcodeXor + aluRmiROpcodeMul +) + +func (a aluRmiROpcode) String() string { + switch a { + case aluRmiROpcodeAdd: + return "add" + case aluRmiROpcodeSub: + return "sub" + case aluRmiROpcodeAnd: + return "and" + case aluRmiROpcodeOr: + return "or" + case aluRmiROpcodeXor: + return "xor" + case aluRmiROpcodeMul: + return "imul" + default: + panic("BUG") + } +} + +func (i *instruction) asJmpIf(cond cond, target operand) *instruction { + i.kind = jmpIf + i.u1 = uint64(cond) + i.op1 = target + return i +} + +// asJmpTableSequence is used to emit the jump table. +// targetSliceIndex is the index of the target slice in machine.jmpTableTargets. +func (i *instruction) asJmpTableSequence(targetSliceIndex int, targetCount int) *instruction { + i.kind = jmpTableIsland + i.u1 = uint64(targetSliceIndex) + i.u2 = uint64(targetCount) + return i +} + +func (i *instruction) asJmp(target operand) *instruction { + i.kind = jmp + i.op1 = target + return i +} + +func (i *instruction) jmpLabel() label { + switch i.kind { + case jmp, jmpIf, lea, xmmUnaryRmR: + return i.op1.label() + default: + panic("BUG") + } +} + +func (i *instruction) asLEA(target operand, rd regalloc.VReg) *instruction { + i.kind = lea + i.op1 = target + i.op2 = newOperandReg(rd) + return i +} + +func (i *instruction) asCall(ref ssa.FuncRef, abi *backend.FunctionABI) *instruction { + i.kind = call + i.u1 = uint64(ref) + if abi != nil { + i.u2 = abi.ABIInfoAsUint64() + } + return i +} + +func (i *instruction) asCallIndirect(ptr operand, abi *backend.FunctionABI) *instruction { + if ptr.kind != operandKindReg && ptr.kind != operandKindMem { + panic("BUG") + } + i.kind = callIndirect + i.op1 = ptr + if abi != nil { + i.u2 = abi.ABIInfoAsUint64() + } + return i +} + +func (i *instruction) asTailCallReturnCall(ref ssa.FuncRef, abi *backend.FunctionABI) *instruction { + i.kind = tailCall + i.u1 = uint64(ref) + if abi != nil { + i.u2 = abi.ABIInfoAsUint64() + } + return i +} + +func (i *instruction) asTailCallReturnCallIndirect(ptr operand, abi *backend.FunctionABI) *instruction { + if ptr.kind != operandKindReg && ptr.kind != operandKindMem { + panic("BUG") + } + i.kind = tailCallIndirect + i.op1 = ptr + if abi != nil { + i.u2 = abi.ABIInfoAsUint64() + } + return i +} + +func (i *instruction) asRet() *instruction { + i.kind = ret + return i +} + +func (i *instruction) asImm(dst regalloc.VReg, value uint64, _64 bool) *instruction { + i.kind = imm + i.op2 = newOperandReg(dst) + i.u1 = value + i.b1 = _64 + return i +} + +func (i *instruction) asAluRmiR(op aluRmiROpcode, rm operand, rd regalloc.VReg, _64 bool) *instruction { + if rm.kind != operandKindReg && rm.kind != operandKindMem && rm.kind != operandKindImm32 { + panic("BUG") + } + i.kind = aluRmiR + i.op1 = rm + i.op2 = newOperandReg(rd) + i.u1 = uint64(op) + i.b1 = _64 + return i +} + +func (i *instruction) asZeros(dst regalloc.VReg) *instruction { + i.kind = zeros + i.op2 = newOperandReg(dst) + return i +} + +func (i *instruction) asBlendvpd(rm operand, rd regalloc.VReg) *instruction { + if rm.kind != operandKindReg && rm.kind != operandKindMem { + panic("BUG") + } + i.kind = blendvpd + i.op1 = rm + i.op2 = newOperandReg(rd) + return i +} + +func (i *instruction) asXmmRmR(op sseOpcode, rm operand, rd regalloc.VReg) *instruction { + if rm.kind != operandKindReg && rm.kind != operandKindMem { + panic("BUG") + } + i.kind = xmmRmR + i.op1 = rm + i.op2 = newOperandReg(rd) + i.u1 = uint64(op) + return i +} + +func (i *instruction) asXmmRmRImm(op sseOpcode, imm uint8, rm operand, rd regalloc.VReg) *instruction { + if rm.kind != operandKindReg && rm.kind != operandKindMem { + panic("BUG") + } + i.kind = xmmRmRImm + i.op1 = rm + i.op2 = newOperandReg(rd) + i.u1 = uint64(op) + i.u2 = uint64(imm) + return i +} + +func (i *instruction) asGprToXmm(op sseOpcode, rm operand, rd regalloc.VReg, _64 bool) *instruction { + if rm.kind != operandKindReg && rm.kind != operandKindMem { + panic("BUG") + } + i.kind = gprToXmm + i.op1 = rm + i.op2 = newOperandReg(rd) + i.u1 = uint64(op) + i.b1 = _64 + return i +} + +func (i *instruction) asEmitSourceOffsetInfo(l ssa.SourceOffset) *instruction { + i.kind = sourceOffsetInfo + i.u1 = uint64(l) + return i +} + +func (i *instruction) sourceOffsetInfo() ssa.SourceOffset { + return ssa.SourceOffset(i.u1) +} + +func (i *instruction) asXmmToGpr(op sseOpcode, rm, rd regalloc.VReg, _64 bool) *instruction { + i.kind = xmmToGpr + i.op1 = newOperandReg(rm) + i.op2 = newOperandReg(rd) + i.u1 = uint64(op) + i.b1 = _64 + return i +} + +func (i *instruction) asMovRM(rm regalloc.VReg, rd operand, size byte) *instruction { + if rd.kind != operandKindMem { + panic("BUG") + } + i.kind = movRM + i.op1 = newOperandReg(rm) + i.op2 = rd + i.u1 = uint64(size) + return i +} + +func (i *instruction) asMovsxRmR(ext extMode, src operand, rd regalloc.VReg) *instruction { + if src.kind != operandKindReg && src.kind != operandKindMem { + panic("BUG") + } + i.kind = movsxRmR + i.op1 = src + i.op2 = newOperandReg(rd) + i.u1 = uint64(ext) + return i +} + +func (i *instruction) asMovzxRmR(ext extMode, src operand, rd regalloc.VReg) *instruction { + if src.kind != operandKindReg && src.kind != operandKindMem { + panic("BUG") + } + i.kind = movzxRmR + i.op1 = src + i.op2 = newOperandReg(rd) + i.u1 = uint64(ext) + return i +} + +func (i *instruction) asSignExtendData(_64 bool) *instruction { + i.kind = signExtendData + i.b1 = _64 + return i +} + +func (i *instruction) asUD2() *instruction { + i.kind = ud2 + return i +} + +func (i *instruction) asDiv(rn operand, signed bool, _64 bool) *instruction { + i.kind = div + i.op1 = rn + i.b1 = _64 + if signed { + i.u1 = 1 + } + return i +} + +func (i *instruction) asMov64MR(rm operand, rd regalloc.VReg) *instruction { + if rm.kind != operandKindMem { + panic("BUG") + } + i.kind = mov64MR + i.op1 = rm + i.op2 = newOperandReg(rd) + return i +} + +func (i *instruction) asMovRR(rm, rd regalloc.VReg, _64 bool) *instruction { + i.kind = movRR + i.op1 = newOperandReg(rm) + i.op2 = newOperandReg(rd) + i.b1 = _64 + return i +} + +func (i *instruction) asNot(rm operand, _64 bool) *instruction { + if rm.kind != operandKindReg && rm.kind != operandKindMem { + panic("BUG") + } + i.kind = not + i.op1 = rm + i.b1 = _64 + return i +} + +func (i *instruction) asNeg(rm operand, _64 bool) *instruction { + if rm.kind != operandKindReg && rm.kind != operandKindMem { + panic("BUG") + } + i.kind = neg + i.op1 = rm + i.b1 = _64 + return i +} + +func (i *instruction) asMulHi(rm operand, signed, _64 bool) *instruction { + if rm.kind != operandKindReg && (rm.kind != operandKindMem) { + panic("BUG") + } + i.kind = mulHi + i.op1 = rm + i.b1 = _64 + if signed { + i.u1 = 1 + } + return i +} + +func (i *instruction) asUnaryRmR(op unaryRmROpcode, rm operand, rd regalloc.VReg, _64 bool) *instruction { + if rm.kind != operandKindReg && rm.kind != operandKindMem { + panic("BUG") + } + i.kind = unaryRmR + i.op1 = rm + i.op2 = newOperandReg(rd) + i.u1 = uint64(op) + i.b1 = _64 + return i +} + +func (i *instruction) asShiftR(op shiftROp, amount operand, rd regalloc.VReg, _64 bool) *instruction { + if amount.kind != operandKindReg && amount.kind != operandKindImm32 { + panic("BUG") + } + i.kind = shiftR + i.op1 = amount + i.op2 = newOperandReg(rd) + i.u1 = uint64(op) + i.b1 = _64 + return i +} + +func (i *instruction) asXmmRmiReg(op sseOpcode, rm operand, rd regalloc.VReg) *instruction { + if rm.kind != operandKindReg && rm.kind != operandKindImm32 && rm.kind != operandKindMem { + panic("BUG") + } + i.kind = xmmRmiReg + i.op1 = rm + i.op2 = newOperandReg(rd) + i.u1 = uint64(op) + return i +} + +func (i *instruction) asCmpRmiR(cmp bool, rm operand, rn regalloc.VReg, _64 bool) *instruction { + if rm.kind != operandKindReg && rm.kind != operandKindImm32 && rm.kind != operandKindMem { + panic("BUG") + } + i.kind = cmpRmiR + i.op1 = rm + i.op2 = newOperandReg(rn) + if cmp { + i.u1 = 1 + } + i.b1 = _64 + return i +} + +func (i *instruction) asSetcc(c cond, rd regalloc.VReg) *instruction { + i.kind = setcc + i.op2 = newOperandReg(rd) + i.u1 = uint64(c) + return i +} + +func (i *instruction) asCmove(c cond, rm operand, rd regalloc.VReg, _64 bool) *instruction { + i.kind = cmove + i.op1 = rm + i.op2 = newOperandReg(rd) + i.u1 = uint64(c) + i.b1 = _64 + return i +} + +func (m *machine) allocateExitSeq(execCtx regalloc.VReg) *instruction { + i := m.allocateInstr() + i.kind = exitSequence + i.op1 = newOperandReg(execCtx) + // Allocate the address mode that will be used in encoding the exit sequence. + i.op2 = newOperandMem(m.amodePool.Allocate()) + return i +} + +func (i *instruction) asXmmUnaryRmR(op sseOpcode, rm operand, rd regalloc.VReg) *instruction { + if rm.kind != operandKindReg && rm.kind != operandKindMem { + panic("BUG") + } + i.kind = xmmUnaryRmR + i.op1 = rm + i.op2 = newOperandReg(rd) + i.u1 = uint64(op) + return i +} + +func (i *instruction) asXmmUnaryRmRImm(op sseOpcode, imm byte, rm operand, rd regalloc.VReg) *instruction { + if rm.kind != operandKindReg && rm.kind != operandKindMem { + panic("BUG") + } + i.kind = xmmUnaryRmRImm + i.op1 = rm + i.op2 = newOperandReg(rd) + i.u1 = uint64(op) + i.u2 = uint64(imm) + return i +} + +func (i *instruction) asXmmCmpRmR(op sseOpcode, rm operand, rd regalloc.VReg) *instruction { + if rm.kind != operandKindReg && rm.kind != operandKindMem { + panic("BUG") + } + i.kind = xmmCmpRmR + i.op1 = rm + i.op2 = newOperandReg(rd) + i.u1 = uint64(op) + return i +} + +func (i *instruction) asXmmMovRM(op sseOpcode, rm regalloc.VReg, rd operand) *instruction { + if rd.kind != operandKindMem { + panic("BUG") + } + i.kind = xmmMovRM + i.op1 = newOperandReg(rm) + i.op2 = rd + i.u1 = uint64(op) + return i +} + +func (i *instruction) asPop64(rm regalloc.VReg) *instruction { + i.kind = pop64 + i.op1 = newOperandReg(rm) + return i +} + +func (i *instruction) asPush64(op operand) *instruction { + if op.kind != operandKindReg && op.kind != operandKindMem && op.kind != operandKindImm32 { + panic("BUG") + } + i.kind = push64 + i.op1 = op + return i +} + +func (i *instruction) asXCHG(rm regalloc.VReg, rd operand, size byte) *instruction { + i.kind = xchg + i.op1 = newOperandReg(rm) + i.op2 = rd + i.u1 = uint64(size) + return i +} + +func (i *instruction) asLockCmpXCHG(rm regalloc.VReg, rd *amode, size byte) *instruction { + i.kind = lockcmpxchg + i.op1 = newOperandReg(rm) + i.op2 = newOperandMem(rd) + i.u1 = uint64(size) + return i +} + +func (i *instruction) asLockXAdd(rm regalloc.VReg, rd *amode, size byte) *instruction { + i.kind = lockxadd + i.op1 = newOperandReg(rm) + i.op2 = newOperandMem(rd) + i.u1 = uint64(size) + return i +} + +type unaryRmROpcode byte + +const ( + unaryRmROpcodeBsr unaryRmROpcode = iota + unaryRmROpcodeBsf + unaryRmROpcodeLzcnt + unaryRmROpcodeTzcnt + unaryRmROpcodePopcnt +) + +func (u unaryRmROpcode) String() string { + switch u { + case unaryRmROpcodeBsr: + return "bsr" + case unaryRmROpcodeBsf: + return "bsf" + case unaryRmROpcodeLzcnt: + return "lzcnt" + case unaryRmROpcodeTzcnt: + return "tzcnt" + case unaryRmROpcodePopcnt: + return "popcnt" + default: + panic("BUG") + } +} + +type shiftROp byte + +const ( + shiftROpRotateLeft shiftROp = 0 + shiftROpRotateRight shiftROp = 1 + shiftROpShiftLeft shiftROp = 4 + shiftROpShiftRightLogical shiftROp = 5 + shiftROpShiftRightArithmetic shiftROp = 7 +) + +func (s shiftROp) String() string { + switch s { + case shiftROpRotateLeft: + return "rol" + case shiftROpRotateRight: + return "ror" + case shiftROpShiftLeft: + return "shl" + case shiftROpShiftRightLogical: + return "shr" + case shiftROpShiftRightArithmetic: + return "sar" + default: + panic("BUG") + } +} + +type sseOpcode byte + +const ( + sseOpcodeInvalid sseOpcode = iota + sseOpcodeAddps + sseOpcodeAddpd + sseOpcodeAddss + sseOpcodeAddsd + sseOpcodeAndps + sseOpcodeAndpd + sseOpcodeAndnps + sseOpcodeAndnpd + sseOpcodeBlendvps + sseOpcodeBlendvpd + sseOpcodeComiss + sseOpcodeComisd + sseOpcodeCmpps + sseOpcodeCmppd + sseOpcodeCmpss + sseOpcodeCmpsd + sseOpcodeCvtdq2ps + sseOpcodeCvtdq2pd + sseOpcodeCvtsd2ss + sseOpcodeCvtsd2si + sseOpcodeCvtsi2ss + sseOpcodeCvtsi2sd + sseOpcodeCvtss2si + sseOpcodeCvtss2sd + sseOpcodeCvttps2dq + sseOpcodeCvttss2si + sseOpcodeCvttsd2si + sseOpcodeDivps + sseOpcodeDivpd + sseOpcodeDivss + sseOpcodeDivsd + sseOpcodeInsertps + sseOpcodeMaxps + sseOpcodeMaxpd + sseOpcodeMaxss + sseOpcodeMaxsd + sseOpcodeMinps + sseOpcodeMinpd + sseOpcodeMinss + sseOpcodeMinsd + sseOpcodeMovaps + sseOpcodeMovapd + sseOpcodeMovd + sseOpcodeMovdqa + sseOpcodeMovdqu + sseOpcodeMovlhps + sseOpcodeMovmskps + sseOpcodeMovmskpd + sseOpcodeMovq + sseOpcodeMovss + sseOpcodeMovsd + sseOpcodeMovups + sseOpcodeMovupd + sseOpcodeMulps + sseOpcodeMulpd + sseOpcodeMulss + sseOpcodeMulsd + sseOpcodeOrps + sseOpcodeOrpd + sseOpcodePabsb + sseOpcodePabsw + sseOpcodePabsd + sseOpcodePackssdw + sseOpcodePacksswb + sseOpcodePackusdw + sseOpcodePackuswb + sseOpcodePaddb + sseOpcodePaddd + sseOpcodePaddq + sseOpcodePaddw + sseOpcodePaddsb + sseOpcodePaddsw + sseOpcodePaddusb + sseOpcodePaddusw + sseOpcodePalignr + sseOpcodePand + sseOpcodePandn + sseOpcodePavgb + sseOpcodePavgw + sseOpcodePcmpeqb + sseOpcodePcmpeqw + sseOpcodePcmpeqd + sseOpcodePcmpeqq + sseOpcodePcmpgtb + sseOpcodePcmpgtw + sseOpcodePcmpgtd + sseOpcodePcmpgtq + sseOpcodePextrb + sseOpcodePextrw + sseOpcodePextrd + sseOpcodePextrq + sseOpcodePinsrb + sseOpcodePinsrw + sseOpcodePinsrd + sseOpcodePinsrq + sseOpcodePmaddwd + sseOpcodePmaxsb + sseOpcodePmaxsw + sseOpcodePmaxsd + sseOpcodePmaxub + sseOpcodePmaxuw + sseOpcodePmaxud + sseOpcodePminsb + sseOpcodePminsw + sseOpcodePminsd + sseOpcodePminub + sseOpcodePminuw + sseOpcodePminud + sseOpcodePmovmskb + sseOpcodePmovsxbd + sseOpcodePmovsxbw + sseOpcodePmovsxbq + sseOpcodePmovsxwd + sseOpcodePmovsxwq + sseOpcodePmovsxdq + sseOpcodePmovzxbd + sseOpcodePmovzxbw + sseOpcodePmovzxbq + sseOpcodePmovzxwd + sseOpcodePmovzxwq + sseOpcodePmovzxdq + sseOpcodePmulld + sseOpcodePmullw + sseOpcodePmuludq + sseOpcodePor + sseOpcodePshufb + sseOpcodePshufd + sseOpcodePsllw + sseOpcodePslld + sseOpcodePsllq + sseOpcodePsraw + sseOpcodePsrad + sseOpcodePsrlw + sseOpcodePsrld + sseOpcodePsrlq + sseOpcodePsubb + sseOpcodePsubd + sseOpcodePsubq + sseOpcodePsubw + sseOpcodePsubsb + sseOpcodePsubsw + sseOpcodePsubusb + sseOpcodePsubusw + sseOpcodePtest + sseOpcodePunpckhbw + sseOpcodePunpcklbw + sseOpcodePxor + sseOpcodeRcpss + sseOpcodeRoundps + sseOpcodeRoundpd + sseOpcodeRoundss + sseOpcodeRoundsd + sseOpcodeRsqrtss + sseOpcodeSqrtps + sseOpcodeSqrtpd + sseOpcodeSqrtss + sseOpcodeSqrtsd + sseOpcodeSubps + sseOpcodeSubpd + sseOpcodeSubss + sseOpcodeSubsd + sseOpcodeUcomiss + sseOpcodeUcomisd + sseOpcodeXorps + sseOpcodeXorpd + sseOpcodePmulhrsw + sseOpcodeUnpcklps + sseOpcodeCvtps2pd + sseOpcodeCvtpd2ps + sseOpcodeCvttpd2dq + sseOpcodeShufps + sseOpcodePmaddubsw +) + +func (s sseOpcode) String() string { + switch s { + case sseOpcodeInvalid: + return "invalid" + case sseOpcodeAddps: + return "addps" + case sseOpcodeAddpd: + return "addpd" + case sseOpcodeAddss: + return "addss" + case sseOpcodeAddsd: + return "addsd" + case sseOpcodeAndps: + return "andps" + case sseOpcodeAndpd: + return "andpd" + case sseOpcodeAndnps: + return "andnps" + case sseOpcodeAndnpd: + return "andnpd" + case sseOpcodeBlendvps: + return "blendvps" + case sseOpcodeBlendvpd: + return "blendvpd" + case sseOpcodeComiss: + return "comiss" + case sseOpcodeComisd: + return "comisd" + case sseOpcodeCmpps: + return "cmpps" + case sseOpcodeCmppd: + return "cmppd" + case sseOpcodeCmpss: + return "cmpss" + case sseOpcodeCmpsd: + return "cmpsd" + case sseOpcodeCvtdq2ps: + return "cvtdq2ps" + case sseOpcodeCvtdq2pd: + return "cvtdq2pd" + case sseOpcodeCvtsd2ss: + return "cvtsd2ss" + case sseOpcodeCvtsd2si: + return "cvtsd2si" + case sseOpcodeCvtsi2ss: + return "cvtsi2ss" + case sseOpcodeCvtsi2sd: + return "cvtsi2sd" + case sseOpcodeCvtss2si: + return "cvtss2si" + case sseOpcodeCvtss2sd: + return "cvtss2sd" + case sseOpcodeCvttps2dq: + return "cvttps2dq" + case sseOpcodeCvttss2si: + return "cvttss2si" + case sseOpcodeCvttsd2si: + return "cvttsd2si" + case sseOpcodeDivps: + return "divps" + case sseOpcodeDivpd: + return "divpd" + case sseOpcodeDivss: + return "divss" + case sseOpcodeDivsd: + return "divsd" + case sseOpcodeInsertps: + return "insertps" + case sseOpcodeMaxps: + return "maxps" + case sseOpcodeMaxpd: + return "maxpd" + case sseOpcodeMaxss: + return "maxss" + case sseOpcodeMaxsd: + return "maxsd" + case sseOpcodeMinps: + return "minps" + case sseOpcodeMinpd: + return "minpd" + case sseOpcodeMinss: + return "minss" + case sseOpcodeMinsd: + return "minsd" + case sseOpcodeMovaps: + return "movaps" + case sseOpcodeMovapd: + return "movapd" + case sseOpcodeMovd: + return "movd" + case sseOpcodeMovdqa: + return "movdqa" + case sseOpcodeMovdqu: + return "movdqu" + case sseOpcodeMovlhps: + return "movlhps" + case sseOpcodeMovmskps: + return "movmskps" + case sseOpcodeMovmskpd: + return "movmskpd" + case sseOpcodeMovq: + return "movq" + case sseOpcodeMovss: + return "movss" + case sseOpcodeMovsd: + return "movsd" + case sseOpcodeMovups: + return "movups" + case sseOpcodeMovupd: + return "movupd" + case sseOpcodeMulps: + return "mulps" + case sseOpcodeMulpd: + return "mulpd" + case sseOpcodeMulss: + return "mulss" + case sseOpcodeMulsd: + return "mulsd" + case sseOpcodeOrps: + return "orps" + case sseOpcodeOrpd: + return "orpd" + case sseOpcodePabsb: + return "pabsb" + case sseOpcodePabsw: + return "pabsw" + case sseOpcodePabsd: + return "pabsd" + case sseOpcodePackssdw: + return "packssdw" + case sseOpcodePacksswb: + return "packsswb" + case sseOpcodePackusdw: + return "packusdw" + case sseOpcodePackuswb: + return "packuswb" + case sseOpcodePaddb: + return "paddb" + case sseOpcodePaddd: + return "paddd" + case sseOpcodePaddq: + return "paddq" + case sseOpcodePaddw: + return "paddw" + case sseOpcodePaddsb: + return "paddsb" + case sseOpcodePaddsw: + return "paddsw" + case sseOpcodePaddusb: + return "paddusb" + case sseOpcodePaddusw: + return "paddusw" + case sseOpcodePalignr: + return "palignr" + case sseOpcodePand: + return "pand" + case sseOpcodePandn: + return "pandn" + case sseOpcodePavgb: + return "pavgb" + case sseOpcodePavgw: + return "pavgw" + case sseOpcodePcmpeqb: + return "pcmpeqb" + case sseOpcodePcmpeqw: + return "pcmpeqw" + case sseOpcodePcmpeqd: + return "pcmpeqd" + case sseOpcodePcmpeqq: + return "pcmpeqq" + case sseOpcodePcmpgtb: + return "pcmpgtb" + case sseOpcodePcmpgtw: + return "pcmpgtw" + case sseOpcodePcmpgtd: + return "pcmpgtd" + case sseOpcodePcmpgtq: + return "pcmpgtq" + case sseOpcodePextrb: + return "pextrb" + case sseOpcodePextrw: + return "pextrw" + case sseOpcodePextrd: + return "pextrd" + case sseOpcodePextrq: + return "pextrq" + case sseOpcodePinsrb: + return "pinsrb" + case sseOpcodePinsrw: + return "pinsrw" + case sseOpcodePinsrd: + return "pinsrd" + case sseOpcodePinsrq: + return "pinsrq" + case sseOpcodePmaddwd: + return "pmaddwd" + case sseOpcodePmaxsb: + return "pmaxsb" + case sseOpcodePmaxsw: + return "pmaxsw" + case sseOpcodePmaxsd: + return "pmaxsd" + case sseOpcodePmaxub: + return "pmaxub" + case sseOpcodePmaxuw: + return "pmaxuw" + case sseOpcodePmaxud: + return "pmaxud" + case sseOpcodePminsb: + return "pminsb" + case sseOpcodePminsw: + return "pminsw" + case sseOpcodePminsd: + return "pminsd" + case sseOpcodePminub: + return "pminub" + case sseOpcodePminuw: + return "pminuw" + case sseOpcodePminud: + return "pminud" + case sseOpcodePmovmskb: + return "pmovmskb" + case sseOpcodePmovsxbd: + return "pmovsxbd" + case sseOpcodePmovsxbw: + return "pmovsxbw" + case sseOpcodePmovsxbq: + return "pmovsxbq" + case sseOpcodePmovsxwd: + return "pmovsxwd" + case sseOpcodePmovsxwq: + return "pmovsxwq" + case sseOpcodePmovsxdq: + return "pmovsxdq" + case sseOpcodePmovzxbd: + return "pmovzxbd" + case sseOpcodePmovzxbw: + return "pmovzxbw" + case sseOpcodePmovzxbq: + return "pmovzxbq" + case sseOpcodePmovzxwd: + return "pmovzxwd" + case sseOpcodePmovzxwq: + return "pmovzxwq" + case sseOpcodePmovzxdq: + return "pmovzxdq" + case sseOpcodePmulld: + return "pmulld" + case sseOpcodePmullw: + return "pmullw" + case sseOpcodePmuludq: + return "pmuludq" + case sseOpcodePor: + return "por" + case sseOpcodePshufb: + return "pshufb" + case sseOpcodePshufd: + return "pshufd" + case sseOpcodePsllw: + return "psllw" + case sseOpcodePslld: + return "pslld" + case sseOpcodePsllq: + return "psllq" + case sseOpcodePsraw: + return "psraw" + case sseOpcodePsrad: + return "psrad" + case sseOpcodePsrlw: + return "psrlw" + case sseOpcodePsrld: + return "psrld" + case sseOpcodePsrlq: + return "psrlq" + case sseOpcodePsubb: + return "psubb" + case sseOpcodePsubd: + return "psubd" + case sseOpcodePsubq: + return "psubq" + case sseOpcodePsubw: + return "psubw" + case sseOpcodePsubsb: + return "psubsb" + case sseOpcodePsubsw: + return "psubsw" + case sseOpcodePsubusb: + return "psubusb" + case sseOpcodePsubusw: + return "psubusw" + case sseOpcodePtest: + return "ptest" + case sseOpcodePunpckhbw: + return "punpckhbw" + case sseOpcodePunpcklbw: + return "punpcklbw" + case sseOpcodePxor: + return "pxor" + case sseOpcodeRcpss: + return "rcpss" + case sseOpcodeRoundps: + return "roundps" + case sseOpcodeRoundpd: + return "roundpd" + case sseOpcodeRoundss: + return "roundss" + case sseOpcodeRoundsd: + return "roundsd" + case sseOpcodeRsqrtss: + return "rsqrtss" + case sseOpcodeSqrtps: + return "sqrtps" + case sseOpcodeSqrtpd: + return "sqrtpd" + case sseOpcodeSqrtss: + return "sqrtss" + case sseOpcodeSqrtsd: + return "sqrtsd" + case sseOpcodeSubps: + return "subps" + case sseOpcodeSubpd: + return "subpd" + case sseOpcodeSubss: + return "subss" + case sseOpcodeSubsd: + return "subsd" + case sseOpcodeUcomiss: + return "ucomiss" + case sseOpcodeUcomisd: + return "ucomisd" + case sseOpcodeXorps: + return "xorps" + case sseOpcodeXorpd: + return "xorpd" + case sseOpcodePmulhrsw: + return "pmulhrsw" + case sseOpcodeUnpcklps: + return "unpcklps" + case sseOpcodeCvtps2pd: + return "cvtps2pd" + case sseOpcodeCvtpd2ps: + return "cvtpd2ps" + case sseOpcodeCvttpd2dq: + return "cvttpd2dq" + case sseOpcodeShufps: + return "shufps" + case sseOpcodePmaddubsw: + return "pmaddubsw" + default: + panic("BUG") + } +} + +type roundingMode uint8 + +const ( + roundingModeNearest roundingMode = iota + roundingModeDown + roundingModeUp + roundingModeZero +) + +func (r roundingMode) String() string { + switch r { + case roundingModeNearest: + return "nearest" + case roundingModeDown: + return "down" + case roundingModeUp: + return "up" + case roundingModeZero: + return "zero" + default: + panic("BUG") + } +} + +// cmpPred is the immediate value for a comparison operation in xmmRmRImm. +type cmpPred uint8 + +const ( + // cmpPredEQ_OQ is Equal (ordered, non-signaling) + cmpPredEQ_OQ cmpPred = iota + // cmpPredLT_OS is Less-than (ordered, signaling) + cmpPredLT_OS + // cmpPredLE_OS is Less-than-or-equal (ordered, signaling) + cmpPredLE_OS + // cmpPredUNORD_Q is Unordered (non-signaling) + cmpPredUNORD_Q + // cmpPredNEQ_UQ is Not-equal (unordered, non-signaling) + cmpPredNEQ_UQ + // cmpPredNLT_US is Not-less-than (unordered, signaling) + cmpPredNLT_US + // cmpPredNLE_US is Not-less-than-or-equal (unordered, signaling) + cmpPredNLE_US + // cmpPredORD_Q is Ordered (non-signaling) + cmpPredORD_Q + // cmpPredEQ_UQ is Equal (unordered, non-signaling) + cmpPredEQ_UQ + // cmpPredNGE_US is Not-greater-than-or-equal (unordered, signaling) + cmpPredNGE_US + // cmpPredNGT_US is Not-greater-than (unordered, signaling) + cmpPredNGT_US + // cmpPredFALSE_OQ is False (ordered, non-signaling) + cmpPredFALSE_OQ + // cmpPredNEQ_OQ is Not-equal (ordered, non-signaling) + cmpPredNEQ_OQ + // cmpPredGE_OS is Greater-than-or-equal (ordered, signaling) + cmpPredGE_OS + // cmpPredGT_OS is Greater-than (ordered, signaling) + cmpPredGT_OS + // cmpPredTRUE_UQ is True (unordered, non-signaling) + cmpPredTRUE_UQ + // Equal (ordered, signaling) + cmpPredEQ_OS + // Less-than (ordered, nonsignaling) + cmpPredLT_OQ + // Less-than-or-equal (ordered, nonsignaling) + cmpPredLE_OQ + // Unordered (signaling) + cmpPredUNORD_S + // Not-equal (unordered, signaling) + cmpPredNEQ_US + // Not-less-than (unordered, nonsignaling) + cmpPredNLT_UQ + // Not-less-than-or-equal (unordered, nonsignaling) + cmpPredNLE_UQ + // Ordered (signaling) + cmpPredORD_S + // Equal (unordered, signaling) + cmpPredEQ_US + // Not-greater-than-or-equal (unordered, non-signaling) + cmpPredNGE_UQ + // Not-greater-than (unordered, nonsignaling) + cmpPredNGT_UQ + // False (ordered, signaling) + cmpPredFALSE_OS + // Not-equal (ordered, signaling) + cmpPredNEQ_OS + // Greater-than-or-equal (ordered, nonsignaling) + cmpPredGE_OQ + // Greater-than (ordered, nonsignaling) + cmpPredGT_OQ + // True (unordered, signaling) + cmpPredTRUE_US +) + +func (r cmpPred) String() string { + switch r { + case cmpPredEQ_OQ: + return "eq_oq" + case cmpPredLT_OS: + return "lt_os" + case cmpPredLE_OS: + return "le_os" + case cmpPredUNORD_Q: + return "unord_q" + case cmpPredNEQ_UQ: + return "neq_uq" + case cmpPredNLT_US: + return "nlt_us" + case cmpPredNLE_US: + return "nle_us" + case cmpPredORD_Q: + return "ord_q" + case cmpPredEQ_UQ: + return "eq_uq" + case cmpPredNGE_US: + return "nge_us" + case cmpPredNGT_US: + return "ngt_us" + case cmpPredFALSE_OQ: + return "false_oq" + case cmpPredNEQ_OQ: + return "neq_oq" + case cmpPredGE_OS: + return "ge_os" + case cmpPredGT_OS: + return "gt_os" + case cmpPredTRUE_UQ: + return "true_uq" + case cmpPredEQ_OS: + return "eq_os" + case cmpPredLT_OQ: + return "lt_oq" + case cmpPredLE_OQ: + return "le_oq" + case cmpPredUNORD_S: + return "unord_s" + case cmpPredNEQ_US: + return "neq_us" + case cmpPredNLT_UQ: + return "nlt_uq" + case cmpPredNLE_UQ: + return "nle_uq" + case cmpPredORD_S: + return "ord_s" + case cmpPredEQ_US: + return "eq_us" + case cmpPredNGE_UQ: + return "nge_uq" + case cmpPredNGT_UQ: + return "ngt_uq" + case cmpPredFALSE_OS: + return "false_os" + case cmpPredNEQ_OS: + return "neq_os" + case cmpPredGE_OQ: + return "ge_oq" + case cmpPredGT_OQ: + return "gt_oq" + case cmpPredTRUE_US: + return "true_us" + default: + panic("BUG") + } +} + +func linkInstr(prev, next *instruction) *instruction { + prev.next = next + next.prev = prev + return next +} + +type defKind byte + +const ( + defKindNone defKind = iota + 1 + defKindOp2 + defKindCall + defKindDivRem +) + +var defKinds = [instrMax]defKind{ + nop0: defKindNone, + ret: defKindNone, + movRR: defKindOp2, + movRM: defKindNone, + xmmMovRM: defKindNone, + aluRmiR: defKindNone, + shiftR: defKindNone, + imm: defKindOp2, + unaryRmR: defKindOp2, + xmmRmiReg: defKindNone, + xmmUnaryRmR: defKindOp2, + xmmUnaryRmRImm: defKindOp2, + xmmCmpRmR: defKindNone, + xmmRmR: defKindNone, + xmmRmRImm: defKindNone, + mov64MR: defKindOp2, + movsxRmR: defKindOp2, + movzxRmR: defKindOp2, + gprToXmm: defKindOp2, + xmmToGpr: defKindOp2, + cmove: defKindNone, + call: defKindCall, + callIndirect: defKindCall, + ud2: defKindNone, + jmp: defKindNone, + jmpIf: defKindNone, + jmpTableIsland: defKindNone, + cmpRmiR: defKindNone, + exitSequence: defKindNone, + lea: defKindOp2, + setcc: defKindOp2, + zeros: defKindOp2, + sourceOffsetInfo: defKindNone, + fcvtToSintSequence: defKindNone, + defineUninitializedReg: defKindOp2, + fcvtToUintSequence: defKindNone, + xmmCMov: defKindOp2, + idivRemSequence: defKindDivRem, + blendvpd: defKindNone, + mfence: defKindNone, + xchg: defKindNone, + lockcmpxchg: defKindNone, + lockxadd: defKindNone, + neg: defKindNone, + nopUseReg: defKindNone, + tailCall: defKindCall, + tailCallIndirect: defKindCall, +} + +// String implements fmt.Stringer. +func (d defKind) String() string { + switch d { + case defKindNone: + return "none" + case defKindOp2: + return "op2" + case defKindCall: + return "call" + case defKindDivRem: + return "divrem" + default: + return "invalid" + } +} + +type useKind byte + +const ( + useKindNone useKind = iota + 1 + useKindOp1 + // useKindOp1Op2Reg is Op1 can be any operand, Op2 must be a register. + useKindOp1Op2Reg + // useKindOp1RegOp2 is Op1 must be a register, Op2 can be any operand. + useKindOp1RegOp2 + // useKindRaxOp1RegOp2 is Op1 must be a register, Op2 can be any operand, and RAX is used. + useKindRaxOp1RegOp2 + useKindDivRem + useKindBlendvpd + useKindCall + useKindCallInd + useKindTailCallInd + useKindFcvtToSintSequence + useKindFcvtToUintSequence +) + +var useKinds = [instrMax]useKind{ + nop0: useKindNone, + ret: useKindNone, + movRR: useKindOp1, + movRM: useKindOp1RegOp2, + xmmMovRM: useKindOp1RegOp2, + cmove: useKindOp1Op2Reg, + aluRmiR: useKindOp1Op2Reg, + shiftR: useKindOp1Op2Reg, + imm: useKindNone, + unaryRmR: useKindOp1, + xmmRmiReg: useKindOp1Op2Reg, + xmmUnaryRmR: useKindOp1, + xmmUnaryRmRImm: useKindOp1, + xmmCmpRmR: useKindOp1Op2Reg, + xmmRmR: useKindOp1Op2Reg, + xmmRmRImm: useKindOp1Op2Reg, + mov64MR: useKindOp1, + movzxRmR: useKindOp1, + movsxRmR: useKindOp1, + gprToXmm: useKindOp1, + xmmToGpr: useKindOp1, + call: useKindCall, + callIndirect: useKindCallInd, + ud2: useKindNone, + jmpIf: useKindOp1, + jmp: useKindOp1, + cmpRmiR: useKindOp1Op2Reg, + exitSequence: useKindOp1, + lea: useKindOp1, + jmpTableIsland: useKindNone, + setcc: useKindNone, + zeros: useKindNone, + sourceOffsetInfo: useKindNone, + fcvtToSintSequence: useKindFcvtToSintSequence, + defineUninitializedReg: useKindNone, + fcvtToUintSequence: useKindFcvtToUintSequence, + xmmCMov: useKindOp1, + idivRemSequence: useKindDivRem, + blendvpd: useKindBlendvpd, + mfence: useKindNone, + xchg: useKindOp1RegOp2, + lockcmpxchg: useKindRaxOp1RegOp2, + lockxadd: useKindOp1RegOp2, + neg: useKindOp1, + nopUseReg: useKindOp1, + tailCall: useKindCall, + tailCallIndirect: useKindTailCallInd, +} + +func (u useKind) String() string { + switch u { + case useKindNone: + return "none" + case useKindOp1: + return "op1" + case useKindOp1Op2Reg: + return "op1op2Reg" + case useKindOp1RegOp2: + return "op1RegOp2" + case useKindCall: + return "call" + case useKindCallInd: + return "callInd" + case useKindTailCallInd: + return "tailCallInd" + default: + return "invalid" + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go new file mode 100644 index 00000000000..d1eefbdb58c --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go @@ -0,0 +1,1714 @@ +package amd64 + +import ( + "fmt" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) { + switch kind := i.kind; kind { + case nop0, sourceOffsetInfo, defineUninitializedReg, fcvtToSintSequence, fcvtToUintSequence, nopUseReg: + case ret: + encodeRet(c) + case imm: + dst := regEncodings[i.op2.reg().RealReg()] + con := i.u1 + if i.b1 { // 64 bit. + if lower32willSignExtendTo64(con) { + // Sign extend mov(imm32). + encodeRegReg(c, + legacyPrefixesNone, + 0xc7, 1, + 0, + dst, + rexInfo(0).setW(), + ) + c.Emit4Bytes(uint32(con)) + } else { + c.EmitByte(rexEncodingW | dst.rexBit()) + c.EmitByte(0xb8 | dst.encoding()) + c.Emit8Bytes(con) + } + } else { + if dst.rexBit() > 0 { + c.EmitByte(rexEncodingDefault | 0x1) + } + c.EmitByte(0xb8 | dst.encoding()) + c.Emit4Bytes(uint32(con)) + } + + case aluRmiR: + var rex rexInfo + if i.b1 { + rex = rex.setW() + } else { + rex = rex.clearW() + } + + dst := regEncodings[i.op2.reg().RealReg()] + + aluOp := aluRmiROpcode(i.u1) + if aluOp == aluRmiROpcodeMul { + op1 := i.op1 + const regMemOpc, regMemOpcNum = 0x0FAF, 2 + switch op1.kind { + case operandKindReg: + src := regEncodings[op1.reg().RealReg()] + encodeRegReg(c, legacyPrefixesNone, regMemOpc, regMemOpcNum, dst, src, rex) + case operandKindMem: + m := i.op1.addressMode() + encodeRegMem(c, legacyPrefixesNone, regMemOpc, regMemOpcNum, dst, m, rex) + case operandKindImm32: + imm8 := lower8willSignExtendTo32(op1.imm32()) + var opc uint32 + if imm8 { + opc = 0x6b + } else { + opc = 0x69 + } + encodeRegReg(c, legacyPrefixesNone, opc, 1, dst, dst, rex) + if imm8 { + c.EmitByte(byte(op1.imm32())) + } else { + c.Emit4Bytes(op1.imm32()) + } + default: + panic("BUG: invalid operand kind") + } + } else { + const opcodeNum = 1 + var opcR, opcM, subOpcImm uint32 + switch aluOp { + case aluRmiROpcodeAdd: + opcR, opcM, subOpcImm = 0x01, 0x03, 0x0 + case aluRmiROpcodeSub: + opcR, opcM, subOpcImm = 0x29, 0x2b, 0x5 + case aluRmiROpcodeAnd: + opcR, opcM, subOpcImm = 0x21, 0x23, 0x4 + case aluRmiROpcodeOr: + opcR, opcM, subOpcImm = 0x09, 0x0b, 0x1 + case aluRmiROpcodeXor: + opcR, opcM, subOpcImm = 0x31, 0x33, 0x6 + default: + panic("BUG: invalid aluRmiROpcode") + } + + op1 := i.op1 + switch op1.kind { + case operandKindReg: + src := regEncodings[op1.reg().RealReg()] + encodeRegReg(c, legacyPrefixesNone, opcR, opcodeNum, src, dst, rex) + case operandKindMem: + m := i.op1.addressMode() + encodeRegMem(c, legacyPrefixesNone, opcM, opcodeNum, dst, m, rex) + case operandKindImm32: + imm8 := lower8willSignExtendTo32(op1.imm32()) + var opc uint32 + if imm8 { + opc = 0x83 + } else { + opc = 0x81 + } + encodeRegReg(c, legacyPrefixesNone, opc, opcodeNum, regEnc(subOpcImm), dst, rex) + if imm8 { + c.EmitByte(byte(op1.imm32())) + } else { + c.Emit4Bytes(op1.imm32()) + } + default: + panic("BUG: invalid operand kind") + } + } + + case movRR: + src := regEncodings[i.op1.reg().RealReg()] + dst := regEncodings[i.op2.reg().RealReg()] + var rex rexInfo + if i.b1 { + rex = rex.setW() + } else { + rex = rex.clearW() + } + encodeRegReg(c, legacyPrefixesNone, 0x89, 1, src, dst, rex) + + case xmmRmR, blendvpd: + op := sseOpcode(i.u1) + var legPrex legacyPrefixes + var opcode uint32 + var opcodeNum uint32 + switch op { + case sseOpcodeAddps: + legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F58, 2 + case sseOpcodeAddpd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F58, 2 + case sseOpcodeAddss: + legPrex, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F58, 2 + case sseOpcodeAddsd: + legPrex, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F58, 2 + case sseOpcodeAndps: + legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F54, 2 + case sseOpcodeAndpd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F54, 2 + case sseOpcodeAndnps: + legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F55, 2 + case sseOpcodeAndnpd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F55, 2 + case sseOpcodeBlendvps: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3814, 3 + case sseOpcodeBlendvpd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3815, 3 + case sseOpcodeDivps: + legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F5E, 2 + case sseOpcodeDivpd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F5E, 2 + case sseOpcodeDivss: + legPrex, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F5E, 2 + case sseOpcodeDivsd: + legPrex, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F5E, 2 + case sseOpcodeMaxps: + legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F5F, 2 + case sseOpcodeMaxpd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F5F, 2 + case sseOpcodeMaxss: + legPrex, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F5F, 2 + case sseOpcodeMaxsd: + legPrex, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F5F, 2 + case sseOpcodeMinps: + legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F5D, 2 + case sseOpcodeMinpd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F5D, 2 + case sseOpcodeMinss: + legPrex, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F5D, 2 + case sseOpcodeMinsd: + legPrex, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F5D, 2 + case sseOpcodeMovlhps: + legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F16, 2 + case sseOpcodeMovsd: + legPrex, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F10, 2 + case sseOpcodeMulps: + legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F59, 2 + case sseOpcodeMulpd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F59, 2 + case sseOpcodeMulss: + legPrex, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F59, 2 + case sseOpcodeMulsd: + legPrex, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F59, 2 + case sseOpcodeOrpd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F56, 2 + case sseOpcodeOrps: + legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F56, 2 + case sseOpcodePackssdw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F6B, 2 + case sseOpcodePacksswb: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F63, 2 + case sseOpcodePackusdw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F382B, 3 + case sseOpcodePackuswb: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F67, 2 + case sseOpcodePaddb: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FFC, 2 + case sseOpcodePaddd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FFE, 2 + case sseOpcodePaddq: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FD4, 2 + case sseOpcodePaddw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FFD, 2 + case sseOpcodePaddsb: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FEC, 2 + case sseOpcodePaddsw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FED, 2 + case sseOpcodePaddusb: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FDC, 2 + case sseOpcodePaddusw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FDD, 2 + case sseOpcodePand: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FDB, 2 + case sseOpcodePandn: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FDF, 2 + case sseOpcodePavgb: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FE0, 2 + case sseOpcodePavgw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FE3, 2 + case sseOpcodePcmpeqb: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F74, 2 + case sseOpcodePcmpeqw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F75, 2 + case sseOpcodePcmpeqd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F76, 2 + case sseOpcodePcmpeqq: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3829, 3 + case sseOpcodePcmpgtb: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F64, 2 + case sseOpcodePcmpgtw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F65, 2 + case sseOpcodePcmpgtd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F66, 2 + case sseOpcodePcmpgtq: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3837, 3 + case sseOpcodePmaddwd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FF5, 2 + case sseOpcodePmaxsb: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F383C, 3 + case sseOpcodePmaxsw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FEE, 2 + case sseOpcodePmaxsd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F383D, 3 + case sseOpcodePmaxub: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FDE, 2 + case sseOpcodePmaxuw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F383E, 3 + case sseOpcodePmaxud: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F383F, 3 + case sseOpcodePminsb: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3838, 3 + case sseOpcodePminsw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FEA, 2 + case sseOpcodePminsd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3839, 3 + case sseOpcodePminub: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FDA, 2 + case sseOpcodePminuw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F383A, 3 + case sseOpcodePminud: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F383B, 3 + case sseOpcodePmulld: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3840, 3 + case sseOpcodePmullw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FD5, 2 + case sseOpcodePmuludq: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FF4, 2 + case sseOpcodePor: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FEB, 2 + case sseOpcodePshufb: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3800, 3 + case sseOpcodePsubb: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FF8, 2 + case sseOpcodePsubd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FFA, 2 + case sseOpcodePsubq: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FFB, 2 + case sseOpcodePsubw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FF9, 2 + case sseOpcodePsubsb: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FE8, 2 + case sseOpcodePsubsw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FE9, 2 + case sseOpcodePsubusb: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FD8, 2 + case sseOpcodePsubusw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FD9, 2 + case sseOpcodePunpckhbw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F68, 2 + case sseOpcodePunpcklbw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F60, 2 + case sseOpcodePxor: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FEF, 2 + case sseOpcodeSubps: + legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F5C, 2 + case sseOpcodeSubpd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F5C, 2 + case sseOpcodeSubss: + legPrex, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F5C, 2 + case sseOpcodeSubsd: + legPrex, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F5C, 2 + case sseOpcodeXorps: + legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F57, 2 + case sseOpcodeXorpd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F57, 2 + case sseOpcodePmulhrsw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F380B, 3 + case sseOpcodeUnpcklps: + legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F14, 2 + case sseOpcodePmaddubsw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3804, 3 + default: + if kind == blendvpd { + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3815, 3 + } else { + panic(fmt.Sprintf("Unsupported sseOpcode: %s", op)) + } + } + + dst := regEncodings[i.op2.reg().RealReg()] + + rex := rexInfo(0).clearW() + op1 := i.op1 + if op1.kind == operandKindReg { + src := regEncodings[op1.reg().RealReg()] + encodeRegReg(c, legPrex, opcode, opcodeNum, dst, src, rex) + } else if i.op1.kind == operandKindMem { + m := i.op1.addressMode() + encodeRegMem(c, legPrex, opcode, opcodeNum, dst, m, rex) + } else { + panic("BUG: invalid operand kind") + } + + case gprToXmm: + var legPrefix legacyPrefixes + var opcode uint32 + const opcodeNum = 2 + switch sseOpcode(i.u1) { + case sseOpcodeMovd, sseOpcodeMovq: + legPrefix, opcode = legacyPrefixes0x66, 0x0f6e + case sseOpcodeCvtsi2ss: + legPrefix, opcode = legacyPrefixes0xF3, 0x0f2a + case sseOpcodeCvtsi2sd: + legPrefix, opcode = legacyPrefixes0xF2, 0x0f2a + default: + panic(fmt.Sprintf("Unsupported sseOpcode: %s", sseOpcode(i.u1))) + } + + var rex rexInfo + if i.b1 { + rex = rex.setW() + } else { + rex = rex.clearW() + } + dst := regEncodings[i.op2.reg().RealReg()] + + op1 := i.op1 + if op1.kind == operandKindReg { + src := regEncodings[op1.reg().RealReg()] + encodeRegReg(c, legPrefix, opcode, opcodeNum, dst, src, rex) + } else if i.op1.kind == operandKindMem { + m := i.op1.addressMode() + encodeRegMem(c, legPrefix, opcode, opcodeNum, dst, m, rex) + } else { + panic("BUG: invalid operand kind") + } + + case xmmUnaryRmR: + var prefix legacyPrefixes + var opcode uint32 + var opcodeNum uint32 + op := sseOpcode(i.u1) + switch op { + case sseOpcodeCvtss2sd: + prefix, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F5A, 2 + case sseOpcodeCvtsd2ss: + prefix, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F5A, 2 + case sseOpcodeMovaps: + prefix, opcode, opcodeNum = legacyPrefixesNone, 0x0F28, 2 + case sseOpcodeMovapd: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F28, 2 + case sseOpcodeMovdqa: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F6F, 2 + case sseOpcodeMovdqu: + prefix, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F6F, 2 + case sseOpcodeMovsd: + prefix, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F10, 2 + case sseOpcodeMovss: + prefix, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F10, 2 + case sseOpcodeMovups: + prefix, opcode, opcodeNum = legacyPrefixesNone, 0x0F10, 2 + case sseOpcodeMovupd: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F10, 2 + case sseOpcodePabsb: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F381C, 3 + case sseOpcodePabsw: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F381D, 3 + case sseOpcodePabsd: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F381E, 3 + case sseOpcodePmovsxbd: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3821, 3 + case sseOpcodePmovsxbw: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3820, 3 + case sseOpcodePmovsxbq: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3822, 3 + case sseOpcodePmovsxwd: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3823, 3 + case sseOpcodePmovsxwq: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3824, 3 + case sseOpcodePmovsxdq: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3825, 3 + case sseOpcodePmovzxbd: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3831, 3 + case sseOpcodePmovzxbw: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3830, 3 + case sseOpcodePmovzxbq: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3832, 3 + case sseOpcodePmovzxwd: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3833, 3 + case sseOpcodePmovzxwq: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3834, 3 + case sseOpcodePmovzxdq: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3835, 3 + case sseOpcodeSqrtps: + prefix, opcode, opcodeNum = legacyPrefixesNone, 0x0F51, 2 + case sseOpcodeSqrtpd: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F51, 2 + case sseOpcodeSqrtss: + prefix, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F51, 2 + case sseOpcodeSqrtsd: + prefix, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F51, 2 + case sseOpcodeXorps: + prefix, opcode, opcodeNum = legacyPrefixesNone, 0x0F57, 2 + case sseOpcodeXorpd: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F57, 2 + case sseOpcodeCvtdq2ps: + prefix, opcode, opcodeNum = legacyPrefixesNone, 0x0F5B, 2 + case sseOpcodeCvtdq2pd: + prefix, opcode, opcodeNum = legacyPrefixes0xF3, 0x0FE6, 2 + case sseOpcodeCvtps2pd: + prefix, opcode, opcodeNum = legacyPrefixesNone, 0x0F5A, 2 + case sseOpcodeCvtpd2ps: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F5A, 2 + case sseOpcodeCvttps2dq: + prefix, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F5B, 2 + case sseOpcodeCvttpd2dq: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0FE6, 2 + default: + panic(fmt.Sprintf("Unsupported sseOpcode: %s", op)) + } + + dst := regEncodings[i.op2.reg().RealReg()] + + rex := rexInfo(0).clearW() + op1 := i.op1 + if op1.kind == operandKindReg { + src := regEncodings[op1.reg().RealReg()] + encodeRegReg(c, prefix, opcode, opcodeNum, dst, src, rex) + } else if i.op1.kind == operandKindMem { + m := i.op1.addressMode() + needsLabelResolution = encodeRegMem(c, prefix, opcode, opcodeNum, dst, m, rex) + } else { + panic("BUG: invalid operand kind") + } + + case xmmUnaryRmRImm: + var prefix legacyPrefixes + var opcode uint32 + var opcodeNum uint32 + op := sseOpcode(i.u1) + switch op { + case sseOpcodeRoundps: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0f3a08, 3 + case sseOpcodeRoundss: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0f3a0a, 3 + case sseOpcodeRoundpd: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0f3a09, 3 + case sseOpcodeRoundsd: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0f3a0b, 3 + } + rex := rexInfo(0).clearW() + dst := regEncodings[i.op2.reg().RealReg()] + op1 := i.op1 + if op1.kind == operandKindReg { + src := regEncodings[op1.reg().RealReg()] + encodeRegReg(c, prefix, opcode, opcodeNum, dst, src, rex) + } else if i.op1.kind == operandKindMem { + m := i.op1.addressMode() + encodeRegMem(c, prefix, opcode, opcodeNum, dst, m, rex) + } else { + panic("BUG: invalid operand kind") + } + + c.EmitByte(byte(i.u2)) + + case unaryRmR: + var prefix legacyPrefixes + var opcode uint32 + var opcodeNum uint32 + op := unaryRmROpcode(i.u1) + // We assume size is either 32 or 64. + switch op { + case unaryRmROpcodeBsr: + prefix, opcode, opcodeNum = legacyPrefixesNone, 0x0fbd, 2 + case unaryRmROpcodeBsf: + prefix, opcode, opcodeNum = legacyPrefixesNone, 0x0fbc, 2 + case unaryRmROpcodeLzcnt: + prefix, opcode, opcodeNum = legacyPrefixes0xF3, 0x0fbd, 2 + case unaryRmROpcodeTzcnt: + prefix, opcode, opcodeNum = legacyPrefixes0xF3, 0x0fbc, 2 + case unaryRmROpcodePopcnt: + prefix, opcode, opcodeNum = legacyPrefixes0xF3, 0x0fb8, 2 + default: + panic(fmt.Sprintf("Unsupported unaryRmROpcode: %s", op)) + } + + dst := regEncodings[i.op2.reg().RealReg()] + + rex := rexInfo(0) + if i.b1 { // 64 bit. + rex = rexInfo(0).setW() + } else { + rex = rexInfo(0).clearW() + } + op1 := i.op1 + if op1.kind == operandKindReg { + src := regEncodings[op1.reg().RealReg()] + encodeRegReg(c, prefix, opcode, opcodeNum, dst, src, rex) + } else if i.op1.kind == operandKindMem { + m := i.op1.addressMode() + encodeRegMem(c, prefix, opcode, opcodeNum, dst, m, rex) + } else { + panic("BUG: invalid operand kind") + } + + case not: + var prefix legacyPrefixes + src := regEncodings[i.op1.reg().RealReg()] + rex := rexInfo(0) + if i.b1 { // 64 bit. + rex = rexInfo(0).setW() + } else { + rex = rexInfo(0).clearW() + } + subopcode := uint8(2) + encodeEncEnc(c, prefix, 0xf7, 1, subopcode, uint8(src), rex) + + case neg: + var prefix legacyPrefixes + src := regEncodings[i.op1.reg().RealReg()] + rex := rexInfo(0) + if i.b1 { // 64 bit. + rex = rexInfo(0).setW() + } else { + rex = rexInfo(0).clearW() + } + subopcode := uint8(3) + encodeEncEnc(c, prefix, 0xf7, 1, subopcode, uint8(src), rex) + + case div: + rex := rexInfo(0) + if i.b1 { // 64 bit. + rex = rexInfo(0).setW() + } else { + rex = rexInfo(0).clearW() + } + var subopcode uint8 + if i.u1 != 0 { // Signed. + subopcode = 7 + } else { + subopcode = 6 + } + + divisor := i.op1 + if divisor.kind == operandKindReg { + src := regEncodings[divisor.reg().RealReg()] + encodeEncEnc(c, legacyPrefixesNone, 0xf7, 1, subopcode, uint8(src), rex) + } else if divisor.kind == operandKindMem { + m := divisor.addressMode() + encodeEncMem(c, legacyPrefixesNone, 0xf7, 1, subopcode, m, rex) + } else { + panic("BUG: invalid operand kind") + } + + case mulHi: + var prefix legacyPrefixes + rex := rexInfo(0) + if i.b1 { // 64 bit. + rex = rexInfo(0).setW() + } else { + rex = rexInfo(0).clearW() + } + + signed := i.u1 != 0 + var subopcode uint8 + if signed { + subopcode = 5 + } else { + subopcode = 4 + } + + // src1 is implicitly rax, + // dst_lo is implicitly rax, + // dst_hi is implicitly rdx. + src2 := i.op1 + if src2.kind == operandKindReg { + src := regEncodings[src2.reg().RealReg()] + encodeEncEnc(c, prefix, 0xf7, 1, subopcode, uint8(src), rex) + } else if src2.kind == operandKindMem { + m := src2.addressMode() + encodeEncMem(c, prefix, 0xf7, 1, subopcode, m, rex) + } else { + panic("BUG: invalid operand kind") + } + + case signExtendData: + if i.b1 { // 64 bit. + c.EmitByte(0x48) + c.EmitByte(0x99) + } else { + c.EmitByte(0x99) + } + case movzxRmR, movsxRmR: + signed := i.kind == movsxRmR + + ext := extMode(i.u1) + var opcode uint32 + var opcodeNum uint32 + var rex rexInfo + switch ext { + case extModeBL: + if signed { + opcode, opcodeNum, rex = 0x0fbe, 2, rex.clearW() + } else { + opcode, opcodeNum, rex = 0x0fb6, 2, rex.clearW() + } + case extModeBQ: + if signed { + opcode, opcodeNum, rex = 0x0fbe, 2, rex.setW() + } else { + opcode, opcodeNum, rex = 0x0fb6, 2, rex.setW() + } + case extModeWL: + if signed { + opcode, opcodeNum, rex = 0x0fbf, 2, rex.clearW() + } else { + opcode, opcodeNum, rex = 0x0fb7, 2, rex.clearW() + } + case extModeWQ: + if signed { + opcode, opcodeNum, rex = 0x0fbf, 2, rex.setW() + } else { + opcode, opcodeNum, rex = 0x0fb7, 2, rex.setW() + } + case extModeLQ: + if signed { + opcode, opcodeNum, rex = 0x63, 1, rex.setW() + } else { + opcode, opcodeNum, rex = 0x8b, 1, rex.clearW() + } + default: + panic("BUG: invalid extMode") + } + + op := i.op1 + dst := regEncodings[i.op2.reg().RealReg()] + switch op.kind { + case operandKindReg: + src := regEncodings[op.reg().RealReg()] + if ext == extModeBL || ext == extModeBQ { + // Some destinations must be encoded with REX.R = 1. + if e := src.encoding(); e >= 4 && e <= 7 { + rex = rex.always() + } + } + encodeRegReg(c, legacyPrefixesNone, opcode, opcodeNum, dst, src, rex) + case operandKindMem: + m := op.addressMode() + encodeRegMem(c, legacyPrefixesNone, opcode, opcodeNum, dst, m, rex) + default: + panic("BUG: invalid operand kind") + } + + case mov64MR: + m := i.op1.addressMode() + encodeLoad64(c, m, i.op2.reg().RealReg()) + + case lea: + needsLabelResolution = true + dst := regEncodings[i.op2.reg().RealReg()] + rex := rexInfo(0).setW() + const opcode, opcodeNum = 0x8d, 1 + switch i.op1.kind { + case operandKindMem: + a := i.op1.addressMode() + encodeRegMem(c, legacyPrefixesNone, opcode, opcodeNum, dst, a, rex) + case operandKindLabel: + rex.encode(c, regRexBit(byte(dst)), 0) + c.EmitByte(byte((opcode) & 0xff)) + + // Indicate "LEAQ [RIP + 32bit displacement]. + // https://wiki.osdev.org/X86-64_Instruction_Encoding#32.2F64-bit_addressing + c.EmitByte(encodeModRM(0b00, dst.encoding(), 0b101)) + + // This will be resolved later, so we just emit a placeholder (0xffffffff for testing). + c.Emit4Bytes(0xffffffff) + default: + panic("BUG: invalid operand kind") + } + + case movRM: + m := i.op2.addressMode() + src := regEncodings[i.op1.reg().RealReg()] + + var rex rexInfo + switch i.u1 { + case 1: + if e := src.encoding(); e >= 4 && e <= 7 { + rex = rex.always() + } + encodeRegMem(c, legacyPrefixesNone, 0x88, 1, src, m, rex.clearW()) + case 2: + encodeRegMem(c, legacyPrefixes0x66, 0x89, 1, src, m, rex.clearW()) + case 4: + encodeRegMem(c, legacyPrefixesNone, 0x89, 1, src, m, rex.clearW()) + case 8: + encodeRegMem(c, legacyPrefixesNone, 0x89, 1, src, m, rex.setW()) + default: + panic(fmt.Sprintf("BUG: invalid size %d: %s", i.u1, i.String())) + } + + case shiftR: + src := regEncodings[i.op2.reg().RealReg()] + amount := i.op1 + + var opcode uint32 + var prefix legacyPrefixes + rex := rexInfo(0) + if i.b1 { // 64 bit. + rex = rexInfo(0).setW() + } else { + rex = rexInfo(0).clearW() + } + + switch amount.kind { + case operandKindReg: + if amount.reg() != rcxVReg { + panic("BUG: invalid reg operand: must be rcx") + } + opcode, prefix = 0xd3, legacyPrefixesNone + encodeEncEnc(c, prefix, opcode, 1, uint8(i.u1), uint8(src), rex) + case operandKindImm32: + opcode, prefix = 0xc1, legacyPrefixesNone + encodeEncEnc(c, prefix, opcode, 1, uint8(i.u1), uint8(src), rex) + c.EmitByte(byte(amount.imm32())) + default: + panic("BUG: invalid operand kind") + } + case xmmRmiReg: + const legPrefix = legacyPrefixes0x66 + rex := rexInfo(0).clearW() + dst := regEncodings[i.op2.reg().RealReg()] + + var opcode uint32 + var regDigit uint8 + + op := sseOpcode(i.u1) + op1 := i.op1 + if i.op1.kind == operandKindImm32 { + switch op { + case sseOpcodePsllw: + opcode, regDigit = 0x0f71, 6 + case sseOpcodePslld: + opcode, regDigit = 0x0f72, 6 + case sseOpcodePsllq: + opcode, regDigit = 0x0f73, 6 + case sseOpcodePsraw: + opcode, regDigit = 0x0f71, 4 + case sseOpcodePsrad: + opcode, regDigit = 0x0f72, 4 + case sseOpcodePsrlw: + opcode, regDigit = 0x0f71, 2 + case sseOpcodePsrld: + opcode, regDigit = 0x0f72, 2 + case sseOpcodePsrlq: + opcode, regDigit = 0x0f73, 2 + default: + panic("invalid opcode") + } + + encodeEncEnc(c, legPrefix, opcode, 2, regDigit, uint8(dst), rex) + imm32 := op1.imm32() + if imm32 > 0xff&imm32 { + panic("immediate value does not fit 1 byte") + } + c.EmitByte(uint8(imm32)) + } else { + switch op { + case sseOpcodePsllw: + opcode = 0x0ff1 + case sseOpcodePslld: + opcode = 0x0ff2 + case sseOpcodePsllq: + opcode = 0x0ff3 + case sseOpcodePsraw: + opcode = 0x0fe1 + case sseOpcodePsrad: + opcode = 0x0fe2 + case sseOpcodePsrlw: + opcode = 0x0fd1 + case sseOpcodePsrld: + opcode = 0x0fd2 + case sseOpcodePsrlq: + opcode = 0x0fd3 + default: + panic("invalid opcode") + } + + if op1.kind == operandKindReg { + reg := regEncodings[op1.reg().RealReg()] + encodeRegReg(c, legPrefix, opcode, 2, dst, reg, rex) + } else if op1.kind == operandKindMem { + m := op1.addressMode() + encodeRegMem(c, legPrefix, opcode, 2, dst, m, rex) + } else { + panic("BUG: invalid operand kind") + } + } + + case cmpRmiR: + var opcode uint32 + isCmp := i.u1 != 0 + rex := rexInfo(0) + _64 := i.b1 + if _64 { // 64 bit. + rex = rex.setW() + } else { + rex = rex.clearW() + } + dst := regEncodings[i.op2.reg().RealReg()] + op1 := i.op1 + switch op1.kind { + case operandKindReg: + reg := regEncodings[op1.reg().RealReg()] + if isCmp { + opcode = 0x39 + } else { + opcode = 0x85 + } + // Here we swap the encoding of the operands for CMP to be consistent with the output of LLVM/GCC. + encodeRegReg(c, legacyPrefixesNone, opcode, 1, reg, dst, rex) + + case operandKindMem: + if isCmp { + opcode = 0x3b + } else { + opcode = 0x85 + } + m := op1.addressMode() + encodeRegMem(c, legacyPrefixesNone, opcode, 1, dst, m, rex) + + case operandKindImm32: + imm32 := op1.imm32() + useImm8 := isCmp && lower8willSignExtendTo32(imm32) + var subopcode uint8 + + switch { + case isCmp && useImm8: + opcode, subopcode = 0x83, 7 + case isCmp && !useImm8: + opcode, subopcode = 0x81, 7 + default: + opcode, subopcode = 0xf7, 0 + } + encodeEncEnc(c, legacyPrefixesNone, opcode, 1, subopcode, uint8(dst), rex) + if useImm8 { + c.EmitByte(uint8(imm32)) + } else { + c.Emit4Bytes(imm32) + } + + default: + panic("BUG: invalid operand kind") + } + case setcc: + cc := cond(i.u1) + dst := regEncodings[i.op2.reg().RealReg()] + rex := rexInfo(0).clearW().always() + opcode := uint32(0x0f90) + uint32(cc) + encodeEncEnc(c, legacyPrefixesNone, opcode, 2, 0, uint8(dst), rex) + case cmove: + cc := cond(i.u1) + dst := regEncodings[i.op2.reg().RealReg()] + rex := rexInfo(0) + if i.b1 { // 64 bit. + rex = rex.setW() + } else { + rex = rex.clearW() + } + opcode := uint32(0x0f40) + uint32(cc) + src := i.op1 + switch src.kind { + case operandKindReg: + srcReg := regEncodings[src.reg().RealReg()] + encodeRegReg(c, legacyPrefixesNone, opcode, 2, dst, srcReg, rex) + case operandKindMem: + m := src.addressMode() + encodeRegMem(c, legacyPrefixesNone, opcode, 2, dst, m, rex) + default: + panic("BUG: invalid operand kind") + } + case push64: + op := i.op1 + + switch op.kind { + case operandKindReg: + dst := regEncodings[op.reg().RealReg()] + if dst.rexBit() > 0 { + c.EmitByte(rexEncodingDefault | 0x1) + } + c.EmitByte(0x50 | dst.encoding()) + case operandKindMem: + m := op.addressMode() + encodeRegMem( + c, legacyPrefixesNone, 0xff, 1, regEnc(6), m, rexInfo(0).clearW(), + ) + case operandKindImm32: + c.EmitByte(0x68) + c.Emit4Bytes(op.imm32()) + default: + panic("BUG: invalid operand kind") + } + + case pop64: + dst := regEncodings[i.op1.reg().RealReg()] + if dst.rexBit() > 0 { + c.EmitByte(rexEncodingDefault | 0x1) + } + c.EmitByte(0x58 | dst.encoding()) + + case xmmMovRM: + var legPrefix legacyPrefixes + var opcode uint32 + const opcodeNum = 2 + switch sseOpcode(i.u1) { + case sseOpcodeMovaps: + legPrefix, opcode = legacyPrefixesNone, 0x0f29 + case sseOpcodeMovapd: + legPrefix, opcode = legacyPrefixes0x66, 0x0f29 + case sseOpcodeMovdqa: + legPrefix, opcode = legacyPrefixes0x66, 0x0f7f + case sseOpcodeMovdqu: + legPrefix, opcode = legacyPrefixes0xF3, 0x0f7f + case sseOpcodeMovss: + legPrefix, opcode = legacyPrefixes0xF3, 0x0f11 + case sseOpcodeMovsd: + legPrefix, opcode = legacyPrefixes0xF2, 0x0f11 + case sseOpcodeMovups: + legPrefix, opcode = legacyPrefixesNone, 0x0f11 + case sseOpcodeMovupd: + legPrefix, opcode = legacyPrefixes0x66, 0x0f11 + default: + panic(fmt.Sprintf("Unsupported sseOpcode: %s", sseOpcode(i.u1))) + } + + dst := regEncodings[i.op1.reg().RealReg()] + encodeRegMem(c, legPrefix, opcode, opcodeNum, dst, i.op2.addressMode(), rexInfo(0).clearW()) + case xmmLoadConst: + panic("TODO") + case xmmToGpr: + var legPrefix legacyPrefixes + var opcode uint32 + var argSwap bool + const opcodeNum = 2 + switch sseOpcode(i.u1) { + case sseOpcodeMovd, sseOpcodeMovq: + legPrefix, opcode, argSwap = legacyPrefixes0x66, 0x0f7e, false + case sseOpcodeMovmskps: + legPrefix, opcode, argSwap = legacyPrefixesNone, 0x0f50, true + case sseOpcodeMovmskpd: + legPrefix, opcode, argSwap = legacyPrefixes0x66, 0x0f50, true + case sseOpcodePmovmskb: + legPrefix, opcode, argSwap = legacyPrefixes0x66, 0x0fd7, true + case sseOpcodeCvttss2si: + legPrefix, opcode, argSwap = legacyPrefixes0xF3, 0x0f2c, true + case sseOpcodeCvttsd2si: + legPrefix, opcode, argSwap = legacyPrefixes0xF2, 0x0f2c, true + default: + panic(fmt.Sprintf("Unsupported sseOpcode: %s", sseOpcode(i.u1))) + } + + var rex rexInfo + if i.b1 { + rex = rex.setW() + } else { + rex = rex.clearW() + } + src := regEncodings[i.op1.reg().RealReg()] + dst := regEncodings[i.op2.reg().RealReg()] + if argSwap { + src, dst = dst, src + } + encodeRegReg(c, legPrefix, opcode, opcodeNum, src, dst, rex) + + case cvtUint64ToFloatSeq: + panic("TODO") + case cvtFloatToSintSeq: + panic("TODO") + case cvtFloatToUintSeq: + panic("TODO") + case xmmMinMaxSeq: + panic("TODO") + case xmmCmpRmR: + var prefix legacyPrefixes + var opcode uint32 + var opcodeNum uint32 + rex := rexInfo(0) + _64 := i.b1 + if _64 { // 64 bit. + rex = rex.setW() + } else { + rex = rex.clearW() + } + + op := sseOpcode(i.u1) + switch op { + case sseOpcodePtest: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0f3817, 3 + case sseOpcodeUcomisd: + prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0f2e, 2 + case sseOpcodeUcomiss: + prefix, opcode, opcodeNum = legacyPrefixesNone, 0x0f2e, 2 + default: + panic(fmt.Sprintf("Unsupported sseOpcode: %s", op)) + } + + dst := regEncodings[i.op2.reg().RealReg()] + op1 := i.op1 + switch op1.kind { + case operandKindReg: + reg := regEncodings[op1.reg().RealReg()] + encodeRegReg(c, prefix, opcode, opcodeNum, dst, reg, rex) + + case operandKindMem: + m := op1.addressMode() + encodeRegMem(c, prefix, opcode, opcodeNum, dst, m, rex) + + default: + panic("BUG: invalid operand kind") + } + case xmmRmRImm: + op := sseOpcode(i.u1) + var legPrex legacyPrefixes + var opcode uint32 + var opcodeNum uint32 + var swap bool + switch op { + case sseOpcodeCmpps: + legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0FC2, 2 + case sseOpcodeCmppd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FC2, 2 + case sseOpcodeCmpss: + legPrex, opcode, opcodeNum = legacyPrefixes0xF3, 0x0FC2, 2 + case sseOpcodeCmpsd: + legPrex, opcode, opcodeNum = legacyPrefixes0xF2, 0x0FC2, 2 + case sseOpcodeInsertps: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3A21, 3 + case sseOpcodePalignr: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3A0F, 3 + case sseOpcodePinsrb: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3A20, 3 + case sseOpcodePinsrw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FC4, 2 + case sseOpcodePinsrd, sseOpcodePinsrq: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3A22, 3 + case sseOpcodePextrb: + swap = true + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3A14, 3 + case sseOpcodePextrw: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FC5, 2 + case sseOpcodePextrd, sseOpcodePextrq: + swap = true + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3A16, 3 + case sseOpcodePshufd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F70, 2 + case sseOpcodeRoundps: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3A08, 3 + case sseOpcodeRoundpd: + legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3A09, 3 + case sseOpcodeShufps: + legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0FC6, 2 + default: + panic(fmt.Sprintf("Unsupported sseOpcode: %s", op)) + } + + dst := regEncodings[i.op2.reg().RealReg()] + + var rex rexInfo + if op == sseOpcodePextrq || op == sseOpcodePinsrq { + rex = rexInfo(0).setW() + } else { + rex = rexInfo(0).clearW() + } + op1 := i.op1 + if op1.kind == operandKindReg { + src := regEncodings[op1.reg().RealReg()] + if swap { + src, dst = dst, src + } + encodeRegReg(c, legPrex, opcode, opcodeNum, dst, src, rex) + } else if i.op1.kind == operandKindMem { + if swap { + panic("BUG: this is not possible to encode") + } + m := i.op1.addressMode() + encodeRegMem(c, legPrex, opcode, opcodeNum, dst, m, rex) + } else { + panic("BUG: invalid operand kind") + } + + c.EmitByte(byte(i.u2)) + + case jmp: + const ( + regMemOpcode = 0xff + regMemOpcodeNum = 1 + regMemSubOpcode = 4 + ) + op := i.op1 + switch op.kind { + case operandKindLabel: + needsLabelResolution = true + fallthrough + case operandKindImm32: + c.EmitByte(0xe9) + c.Emit4Bytes(op.imm32()) + case operandKindMem: + m := op.addressMode() + encodeRegMem(c, + legacyPrefixesNone, + regMemOpcode, regMemOpcodeNum, + regMemSubOpcode, m, rexInfo(0).clearW(), + ) + case operandKindReg: + r := op.reg().RealReg() + encodeRegReg( + c, + legacyPrefixesNone, + regMemOpcode, regMemOpcodeNum, + regMemSubOpcode, + regEncodings[r], rexInfo(0).clearW(), + ) + default: + panic("BUG: invalid operand kind") + } + + case jmpIf: + op := i.op1 + switch op.kind { + case operandKindLabel: + needsLabelResolution = true + fallthrough + case operandKindImm32: + c.EmitByte(0x0f) + c.EmitByte(0x80 | cond(i.u1).encoding()) + c.Emit4Bytes(op.imm32()) + default: + panic("BUG: invalid operand kind") + } + + case jmpTableIsland: + needsLabelResolution = true + for tc := uint64(0); tc < i.u2; tc++ { + c.Emit8Bytes(0) + } + + case exitSequence: + execCtx := i.op1.reg() + allocatedAmode := i.op2.addressMode() + + // Restore the RBP, RSP, and return to the Go code: + *allocatedAmode = amode{ + kindWithShift: uint32(amodeImmReg), base: execCtx, + imm32: wazevoapi.ExecutionContextOffsetOriginalFramePointer.U32(), + } + encodeLoad64(c, allocatedAmode, rbp) + allocatedAmode.imm32 = wazevoapi.ExecutionContextOffsetOriginalStackPointer.U32() + encodeLoad64(c, allocatedAmode, rsp) + encodeRet(c) + + case ud2: + c.EmitByte(0x0f) + c.EmitByte(0x0b) + + case call: + c.EmitByte(0xe8) + // Meaning that the call target is a function value, and requires relocation. + c.AddRelocationInfo(ssa.FuncRef(i.u1), false) + // Note that this is zero as a placeholder for the call target if it's a function value. + c.Emit4Bytes(uint32(i.u2)) + + case callIndirect: + op := i.op1 + + const opcodeNum = 1 + const opcode = 0xff + rex := rexInfo(0).clearW() + switch op.kind { + case operandKindReg: + dst := regEncodings[op.reg().RealReg()] + encodeRegReg(c, + legacyPrefixesNone, + opcode, opcodeNum, + regEnc(2), + dst, + rex, + ) + case operandKindMem: + m := op.addressMode() + encodeRegMem(c, + legacyPrefixesNone, + opcode, opcodeNum, + regEnc(2), + m, + rex, + ) + default: + panic("BUG: invalid operand kind") + } + + case tailCall: + // Encode as jmp. + c.EmitByte(0xe9) + // Meaning that the call target is a function value, and requires relocation. + c.AddRelocationInfo(ssa.FuncRef(i.u1), true) + // Note that this is zero as a placeholder for the call target if it's a function value. + c.Emit4Bytes(uint32(i.u2)) + + case tailCallIndirect: + op := i.op1 + + const opcodeNum = 1 + const opcode = 0xff + const regMemSubOpcode = 4 + rex := rexInfo(0).clearW() + switch op.kind { + // Indirect tail calls always take a register as the target. + // Note: the register should be a callee-saved register (usually r11). + case operandKindReg: + dst := regEncodings[op.reg().RealReg()] + encodeRegReg(c, + legacyPrefixesNone, + opcode, opcodeNum, + regMemSubOpcode, + dst, + rex, + ) + default: + panic("BUG: invalid operand kind") + } + + case xchg: + src, dst := regEncodings[i.op1.reg().RealReg()], i.op2 + size := i.u1 + + var rex rexInfo + var opcode uint32 + lp := legacyPrefixesNone + switch size { + case 8: + opcode = 0x87 + rex = rexInfo(0).setW() + case 4: + opcode = 0x87 + rex = rexInfo(0).clearW() + case 2: + lp = legacyPrefixes0x66 + opcode = 0x87 + rex = rexInfo(0).clearW() + case 1: + opcode = 0x86 + if i.op2.kind == operandKindReg { + panic("TODO?: xchg on two 1-byte registers") + } + // Some destinations must be encoded with REX.R = 1. + if e := src.encoding(); e >= 4 && e <= 7 { + rex = rexInfo(0).always() + } + default: + panic(fmt.Sprintf("BUG: invalid size %d: %s", size, i.String())) + } + + switch dst.kind { + case operandKindMem: + m := dst.addressMode() + encodeRegMem(c, lp, opcode, 1, src, m, rex) + case operandKindReg: + r := dst.reg().RealReg() + encodeRegReg(c, lp, opcode, 1, src, regEncodings[r], rex) + default: + panic("BUG: invalid operand kind") + } + + case lockcmpxchg: + src, dst := regEncodings[i.op1.reg().RealReg()], i.op2 + size := i.u1 + + var rex rexInfo + var opcode uint32 + lp := legacyPrefixes0xF0 // Lock prefix. + switch size { + case 8: + opcode = 0x0FB1 + rex = rexInfo(0).setW() + case 4: + opcode = 0x0FB1 + rex = rexInfo(0).clearW() + case 2: + lp = legacyPrefixes0x660xF0 // Legacy prefix + Lock prefix. + opcode = 0x0FB1 + rex = rexInfo(0).clearW() + case 1: + opcode = 0x0FB0 + // Some destinations must be encoded with REX.R = 1. + if e := src.encoding(); e >= 4 && e <= 7 { + rex = rexInfo(0).always() + } + default: + panic(fmt.Sprintf("BUG: invalid size %d: %s", size, i.String())) + } + + switch dst.kind { + case operandKindMem: + m := dst.addressMode() + encodeRegMem(c, lp, opcode, 2, src, m, rex) + default: + panic("BUG: invalid operand kind") + } + + case lockxadd: + src, dst := regEncodings[i.op1.reg().RealReg()], i.op2 + size := i.u1 + + var rex rexInfo + var opcode uint32 + lp := legacyPrefixes0xF0 // Lock prefix. + switch size { + case 8: + opcode = 0x0FC1 + rex = rexInfo(0).setW() + case 4: + opcode = 0x0FC1 + rex = rexInfo(0).clearW() + case 2: + lp = legacyPrefixes0x660xF0 // Legacy prefix + Lock prefix. + opcode = 0x0FC1 + rex = rexInfo(0).clearW() + case 1: + opcode = 0x0FC0 + // Some destinations must be encoded with REX.R = 1. + if e := src.encoding(); e >= 4 && e <= 7 { + rex = rexInfo(0).always() + } + default: + panic(fmt.Sprintf("BUG: invalid size %d: %s", size, i.String())) + } + + switch dst.kind { + case operandKindMem: + m := dst.addressMode() + encodeRegMem(c, lp, opcode, 2, src, m, rex) + default: + panic("BUG: invalid operand kind") + } + + case zeros: + r := i.op2.reg() + if r.RegType() == regalloc.RegTypeInt { + i.asAluRmiR(aluRmiROpcodeXor, newOperandReg(r), r, true) + } else { + i.asXmmRmR(sseOpcodePxor, newOperandReg(r), r) + } + i.encode(c) + + case mfence: + // https://www.felixcloutier.com/x86/mfence + c.EmitByte(0x0f) + c.EmitByte(0xae) + c.EmitByte(0xf0) + + default: + panic(fmt.Sprintf("TODO: %v", i.kind)) + } + return +} + +func encodeLoad64(c backend.Compiler, m *amode, rd regalloc.RealReg) { + dst := regEncodings[rd] + encodeRegMem(c, legacyPrefixesNone, 0x8b, 1, dst, m, rexInfo(0).setW()) +} + +func encodeRet(c backend.Compiler) { + c.EmitByte(0xc3) +} + +func encodeEncEnc( + c backend.Compiler, + legPrefixes legacyPrefixes, + opcodes uint32, + opcodeNum uint32, + r uint8, + rm uint8, + rex rexInfo, +) { + legPrefixes.encode(c) + rex.encode(c, r>>3, rm>>3) + + for opcodeNum > 0 { + opcodeNum-- + c.EmitByte(byte((opcodes >> (opcodeNum << 3)) & 0xff)) + } + c.EmitByte(encodeModRM(3, r&7, rm&7)) +} + +func encodeRegReg( + c backend.Compiler, + legPrefixes legacyPrefixes, + opcodes uint32, + opcodeNum uint32, + r regEnc, + rm regEnc, + rex rexInfo, +) { + encodeEncEnc(c, legPrefixes, opcodes, opcodeNum, uint8(r), uint8(rm), rex) +} + +func encodeModRM(mod byte, reg byte, rm byte) byte { + return mod<<6 | reg<<3 | rm +} + +func encodeSIB(shift byte, encIndex byte, encBase byte) byte { + return shift<<6 | encIndex<<3 | encBase +} + +func encodeRegMem( + c backend.Compiler, legPrefixes legacyPrefixes, opcodes uint32, opcodeNum uint32, r regEnc, m *amode, rex rexInfo, +) (needsLabelResolution bool) { + needsLabelResolution = encodeEncMem(c, legPrefixes, opcodes, opcodeNum, uint8(r), m, rex) + return +} + +func encodeEncMem( + c backend.Compiler, legPrefixes legacyPrefixes, opcodes uint32, opcodeNum uint32, r uint8, m *amode, rex rexInfo, +) (needsLabelResolution bool) { + legPrefixes.encode(c) + + const ( + modNoDisplacement = 0b00 + modShortDisplacement = 0b01 + modLongDisplacement = 0b10 + + useSBI = 4 // the encoding of rsp or r12 register. + ) + + switch m.kind() { + case amodeImmReg, amodeImmRBP: + base := m.base.RealReg() + baseEnc := regEncodings[base] + + rex.encode(c, regRexBit(r), baseEnc.rexBit()) + + for opcodeNum > 0 { + opcodeNum-- + c.EmitByte(byte((opcodes >> (opcodeNum << 3)) & 0xff)) + } + + // SIB byte is the last byte of the memory encoding before the displacement + const sibByte = 0x24 // == encodeSIB(0, 4, 4) + + immZero, baseRbp, baseR13 := m.imm32 == 0, base == rbp, base == r13 + short := lower8willSignExtendTo32(m.imm32) + rspOrR12 := base == rsp || base == r12 + + if immZero && !baseRbp && !baseR13 { // rbp or r13 can't be used as base for without displacement encoding. + c.EmitByte(encodeModRM(modNoDisplacement, regEncoding(r), baseEnc.encoding())) + if rspOrR12 { + c.EmitByte(sibByte) + } + } else if short { // Note: this includes the case where m.imm32 == 0 && base == rbp || base == r13. + c.EmitByte(encodeModRM(modShortDisplacement, regEncoding(r), baseEnc.encoding())) + if rspOrR12 { + c.EmitByte(sibByte) + } + c.EmitByte(byte(m.imm32)) + } else { + c.EmitByte(encodeModRM(modLongDisplacement, regEncoding(r), baseEnc.encoding())) + if rspOrR12 { + c.EmitByte(sibByte) + } + c.Emit4Bytes(m.imm32) + } + + case amodeRegRegShift: + base := m.base.RealReg() + baseEnc := regEncodings[base] + index := m.index.RealReg() + indexEnc := regEncodings[index] + + if index == rsp { + panic("BUG: rsp can't be used as index of addressing mode") + } + + rex.encodeForIndex(c, regEnc(r), indexEnc, baseEnc) + + for opcodeNum > 0 { + opcodeNum-- + c.EmitByte(byte((opcodes >> (opcodeNum << 3)) & 0xff)) + } + + immZero, baseRbp, baseR13 := m.imm32 == 0, base == rbp, base == r13 + if immZero && !baseRbp && !baseR13 { // rbp or r13 can't be used as base for without displacement encoding. (curious why? because it's interpreted as RIP relative addressing). + c.EmitByte(encodeModRM(modNoDisplacement, regEncoding(r), useSBI)) + c.EmitByte(encodeSIB(m.shift(), indexEnc.encoding(), baseEnc.encoding())) + } else if lower8willSignExtendTo32(m.imm32) { + c.EmitByte(encodeModRM(modShortDisplacement, regEncoding(r), useSBI)) + c.EmitByte(encodeSIB(m.shift(), indexEnc.encoding(), baseEnc.encoding())) + c.EmitByte(byte(m.imm32)) + } else { + c.EmitByte(encodeModRM(modLongDisplacement, regEncoding(r), useSBI)) + c.EmitByte(encodeSIB(m.shift(), indexEnc.encoding(), baseEnc.encoding())) + c.Emit4Bytes(m.imm32) + } + + case amodeRipRel: + rex.encode(c, regRexBit(r), 0) + for opcodeNum > 0 { + opcodeNum-- + c.EmitByte(byte((opcodes >> (opcodeNum << 3)) & 0xff)) + } + + // Indicate "LEAQ [RIP + 32bit displacement]. + // https://wiki.osdev.org/X86-64_Instruction_Encoding#32.2F64-bit_addressing + c.EmitByte(encodeModRM(0b00, regEncoding(r), 0b101)) + + // This will be resolved later, so we just emit a placeholder. + needsLabelResolution = true + c.Emit4Bytes(0) + + default: + panic("BUG: invalid addressing mode") + } + return +} + +const ( + rexEncodingDefault byte = 0x40 + rexEncodingW = rexEncodingDefault | 0x08 +) + +// rexInfo is a bit set to indicate: +// +// 0x01: W bit must be cleared. +// 0x02: REX prefix must be emitted. +type rexInfo byte + +func (ri rexInfo) setW() rexInfo { + return ri | 0x01 +} + +func (ri rexInfo) clearW() rexInfo { + return ri & 0x02 +} + +func (ri rexInfo) always() rexInfo { + return ri | 0x02 +} + +func (ri rexInfo) notAlways() rexInfo { //nolint + return ri & 0x01 +} + +func (ri rexInfo) encode(c backend.Compiler, r uint8, b uint8) { + var w byte = 0 + if ri&0x01 != 0 { + w = 0x01 + } + rex := rexEncodingDefault | w<<3 | r<<2 | b + if rex != rexEncodingDefault || ri&0x02 != 0 { + c.EmitByte(rex) + } +} + +func (ri rexInfo) encodeForIndex(c backend.Compiler, encR regEnc, encIndex regEnc, encBase regEnc) { + var w byte = 0 + if ri&0x01 != 0 { + w = 0x01 + } + r := encR.rexBit() + x := encIndex.rexBit() + b := encBase.rexBit() + rex := byte(0x40) | w<<3 | r<<2 | x<<1 | b + if rex != 0x40 || ri&0x02 != 0 { + c.EmitByte(rex) + } +} + +type regEnc byte + +func (r regEnc) rexBit() byte { + return regRexBit(byte(r)) +} + +func (r regEnc) encoding() byte { + return regEncoding(byte(r)) +} + +func regRexBit(r byte) byte { + return r >> 3 +} + +func regEncoding(r byte) byte { + return r & 0x07 +} + +var regEncodings = [...]regEnc{ + rax: 0b000, + rcx: 0b001, + rdx: 0b010, + rbx: 0b011, + rsp: 0b100, + rbp: 0b101, + rsi: 0b110, + rdi: 0b111, + r8: 0b1000, + r9: 0b1001, + r10: 0b1010, + r11: 0b1011, + r12: 0b1100, + r13: 0b1101, + r14: 0b1110, + r15: 0b1111, + xmm0: 0b000, + xmm1: 0b001, + xmm2: 0b010, + xmm3: 0b011, + xmm4: 0b100, + xmm5: 0b101, + xmm6: 0b110, + xmm7: 0b111, + xmm8: 0b1000, + xmm9: 0b1001, + xmm10: 0b1010, + xmm11: 0b1011, + xmm12: 0b1100, + xmm13: 0b1101, + xmm14: 0b1110, + xmm15: 0b1111, +} + +type legacyPrefixes byte + +const ( + legacyPrefixesNone legacyPrefixes = iota + legacyPrefixes0x66 + legacyPrefixes0xF0 + legacyPrefixes0x660xF0 + legacyPrefixes0xF2 + legacyPrefixes0xF3 +) + +func (p legacyPrefixes) encode(c backend.Compiler) { + switch p { + case legacyPrefixesNone: + case legacyPrefixes0x66: + c.EmitByte(0x66) + case legacyPrefixes0xF0: + c.EmitByte(0xf0) + case legacyPrefixes0x660xF0: + c.EmitByte(0x66) + c.EmitByte(0xf0) + case legacyPrefixes0xF2: + c.EmitByte(0xf2) + case legacyPrefixes0xF3: + c.EmitByte(0xf3) + default: + panic("BUG: invalid legacy prefix") + } +} + +func lower32willSignExtendTo64(x uint64) bool { + xs := int64(x) + return xs == int64(uint64(int32(xs))) +} + +func lower8willSignExtendTo32(x uint32) bool { + xs := int32(x) + return xs == ((xs << 24) >> 24) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/lower_constant.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/lower_constant.go new file mode 100644 index 00000000000..55d05ef63a6 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/lower_constant.go @@ -0,0 +1,71 @@ +package amd64 + +import ( + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" +) + +// lowerConstant allocates a new VReg and inserts the instruction to load the constant value. +func (m *machine) lowerConstant(instr *ssa.Instruction) (vr regalloc.VReg) { + val := instr.Return() + valType := val.Type() + + vr = m.c.AllocateVReg(valType) + m.insertLoadConstant(instr, vr) + return +} + +// InsertLoadConstantBlockArg implements backend.Machine. +func (m *machine) InsertLoadConstantBlockArg(instr *ssa.Instruction, vr regalloc.VReg) { + m.insertLoadConstant(instr, vr) +} + +func (m *machine) insertLoadConstant(instr *ssa.Instruction, vr regalloc.VReg) { + val := instr.Return() + valType := val.Type() + v := instr.ConstantVal() + + bits := valType.Bits() + if bits < 64 { // Clear the redundant bits just in case it's unexpectedly sign-extended, etc. + v = v & ((1 << valType.Bits()) - 1) + } + + switch valType { + case ssa.TypeF32, ssa.TypeF64: + m.lowerFconst(vr, v, bits == 64) + case ssa.TypeI32, ssa.TypeI64: + m.lowerIconst(vr, v, bits == 64) + default: + panic("BUG") + } +} + +func (m *machine) lowerFconst(dst regalloc.VReg, c uint64, _64 bool) { + if c == 0 { + xor := m.allocateInstr().asZeros(dst) + m.insert(xor) + } else { + var tmpType ssa.Type + if _64 { + tmpType = ssa.TypeI64 + } else { + tmpType = ssa.TypeI32 + } + tmpInt := m.c.AllocateVReg(tmpType) + loadToGP := m.allocateInstr().asImm(tmpInt, c, _64) + m.insert(loadToGP) + + movToXmm := m.allocateInstr().asGprToXmm(sseOpcodeMovq, newOperandReg(tmpInt), dst, _64) + m.insert(movToXmm) + } +} + +func (m *machine) lowerIconst(dst regalloc.VReg, c uint64, _64 bool) { + i := m.allocateInstr() + if c == 0 { + i.asZeros(dst) + } else { + i.asImm(dst, c, _64) + } + m.insert(i) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/lower_mem.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/lower_mem.go new file mode 100644 index 00000000000..befe8c6436a --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/lower_mem.go @@ -0,0 +1,187 @@ +package amd64 + +import ( + "fmt" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" +) + +var addendsMatchOpcodes = [...]ssa.Opcode{ssa.OpcodeUExtend, ssa.OpcodeSExtend, ssa.OpcodeIadd, ssa.OpcodeIconst, ssa.OpcodeIshl} + +type addend struct { + r regalloc.VReg + off int64 + shift byte +} + +func (a addend) String() string { + return fmt.Sprintf("addend{r=%s, off=%d, shift=%d}", a.r, a.off, a.shift) +} + +// lowerToAddressMode converts a pointer to an addressMode that can be used as an operand for load/store instructions. +func (m *machine) lowerToAddressMode(ptr ssa.Value, offsetBase uint32) (am *amode) { + def := m.c.ValueDefinition(ptr) + + if offsetBase&0x80000000 != 0 { + // Special casing the huge base offset whose MSB is set. In x64, the immediate is always + // sign-extended, but our IR semantics requires the offset base is always unsigned. + // Note that this should be extremely rare or even this shouldn't hit in the real application, + // therefore we don't need to optimize this case in my opinion. + + a := m.lowerAddend(def) + off64 := a.off + int64(offsetBase) + offsetBaseReg := m.c.AllocateVReg(ssa.TypeI64) + m.lowerIconst(offsetBaseReg, uint64(off64), true) + if a.r != regalloc.VRegInvalid { + return m.newAmodeRegRegShift(0, offsetBaseReg, a.r, a.shift) + } else { + return m.newAmodeImmReg(0, offsetBaseReg) + } + } + + if op := m.c.MatchInstrOneOf(def, addendsMatchOpcodes[:]); op == ssa.OpcodeIadd { + add := def.Instr + x, y := add.Arg2() + xDef, yDef := m.c.ValueDefinition(x), m.c.ValueDefinition(y) + ax := m.lowerAddend(xDef) + ay := m.lowerAddend(yDef) + add.MarkLowered() + return m.lowerAddendsToAmode(ax, ay, offsetBase) + } else { + // If it is not an Iadd, then we lower the one addend. + a := m.lowerAddend(def) + // off is always 0 if r is valid. + if a.r != regalloc.VRegInvalid { + if a.shift != 0 { + tmpReg := m.c.AllocateVReg(ssa.TypeI64) + m.lowerIconst(tmpReg, 0, true) + return m.newAmodeRegRegShift(offsetBase, tmpReg, a.r, a.shift) + } + return m.newAmodeImmReg(offsetBase, a.r) + } else { + off64 := a.off + int64(offsetBase) + tmpReg := m.c.AllocateVReg(ssa.TypeI64) + m.lowerIconst(tmpReg, uint64(off64), true) + return m.newAmodeImmReg(0, tmpReg) + } + } +} + +func (m *machine) lowerAddendsToAmode(x, y addend, offBase uint32) *amode { + if x.r != regalloc.VRegInvalid && x.off != 0 || y.r != regalloc.VRegInvalid && y.off != 0 { + panic("invalid input") + } + + u64 := uint64(x.off+y.off) + uint64(offBase) + if u64 != 0 { + if _, ok := asImm32(u64, false); !ok { + tmpReg := m.c.AllocateVReg(ssa.TypeI64) + m.lowerIconst(tmpReg, u64, true) + // Blank u64 as it has been already lowered. + u64 = 0 + + if x.r == regalloc.VRegInvalid { + x.r = tmpReg + } else if y.r == regalloc.VRegInvalid { + y.r = tmpReg + } else { + // We already know that either rx or ry is invalid, + // so we overwrite it with the temporary register. + panic("BUG") + } + } + } + + u32 := uint32(u64) + switch { + // We assume rx, ry are valid iff offx, offy are 0. + case x.r != regalloc.VRegInvalid && y.r != regalloc.VRegInvalid: + switch { + case x.shift != 0 && y.shift != 0: + // Cannot absorb two shifted registers, must lower one to a shift instruction. + shifted := m.allocateInstr() + shifted.asShiftR(shiftROpShiftLeft, newOperandImm32(uint32(x.shift)), x.r, true) + m.insert(shifted) + + return m.newAmodeRegRegShift(u32, x.r, y.r, y.shift) + case x.shift != 0 && y.shift == 0: + // Swap base and index. + x, y = y, x + fallthrough + default: + return m.newAmodeRegRegShift(u32, x.r, y.r, y.shift) + } + case x.r == regalloc.VRegInvalid && y.r != regalloc.VRegInvalid: + x, y = y, x + fallthrough + case x.r != regalloc.VRegInvalid && y.r == regalloc.VRegInvalid: + if x.shift != 0 { + zero := m.c.AllocateVReg(ssa.TypeI64) + m.lowerIconst(zero, 0, true) + return m.newAmodeRegRegShift(u32, zero, x.r, x.shift) + } + return m.newAmodeImmReg(u32, x.r) + default: // Both are invalid: use the offset. + tmpReg := m.c.AllocateVReg(ssa.TypeI64) + m.lowerIconst(tmpReg, u64, true) + return m.newAmodeImmReg(0, tmpReg) + } +} + +func (m *machine) lowerAddend(x backend.SSAValueDefinition) addend { + if !x.IsFromInstr() { + return addend{m.c.VRegOf(x.V), 0, 0} + } + // Ensure the addend is not referenced in multiple places; we will discard nested Iadds. + op := m.c.MatchInstrOneOf(x, addendsMatchOpcodes[:]) + if op != ssa.OpcodeInvalid && op != ssa.OpcodeIadd { + return m.lowerAddendFromInstr(x.Instr) + } + p := m.getOperand_Reg(x) + return addend{p.reg(), 0, 0} +} + +// lowerAddendFromInstr takes an instruction returns a Vreg and an offset that can be used in an address mode. +// The Vreg is regalloc.VRegInvalid if the addend cannot be lowered to a register. +// The offset is 0 if the addend can be lowered to a register. +func (m *machine) lowerAddendFromInstr(instr *ssa.Instruction) addend { + instr.MarkLowered() + switch op := instr.Opcode(); op { + case ssa.OpcodeIconst: + u64 := instr.ConstantVal() + if instr.Return().Type().Bits() == 32 { + return addend{regalloc.VRegInvalid, int64(int32(u64)), 0} // sign-extend. + } else { + return addend{regalloc.VRegInvalid, int64(u64), 0} + } + case ssa.OpcodeUExtend, ssa.OpcodeSExtend: + input := instr.Arg() + inputDef := m.c.ValueDefinition(input) + if input.Type().Bits() != 32 { + panic("BUG: invalid input type " + input.Type().String()) + } + constInst := inputDef.IsFromInstr() && inputDef.Instr.Constant() + switch { + case constInst && op == ssa.OpcodeSExtend: + return addend{regalloc.VRegInvalid, int64(uint32(inputDef.Instr.ConstantVal())), 0} + case constInst && op == ssa.OpcodeUExtend: + return addend{regalloc.VRegInvalid, int64(int32(inputDef.Instr.ConstantVal())), 0} // sign-extend! + default: + r := m.getOperand_Reg(inputDef) + return addend{r.reg(), 0, 0} + } + case ssa.OpcodeIshl: + // If the addend is a shift, we can only handle it if the shift amount is a constant. + x, amount := instr.Arg2() + amountDef := m.c.ValueDefinition(amount) + if amountDef.IsFromInstr() && amountDef.Instr.Constant() && amountDef.Instr.ConstantVal() <= 3 { + r := m.getOperand_Reg(m.c.ValueDefinition(x)) + return addend{r.reg(), 0, uint8(amountDef.Instr.ConstantVal())} + } + r := m.getOperand_Reg(m.c.ValueDefinition(x)) + return addend{r.reg(), 0, 0} + } + panic("BUG: invalid opcode") +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine.go new file mode 100644 index 00000000000..66901f776e1 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine.go @@ -0,0 +1,3782 @@ +package amd64 + +import ( + "context" + "encoding/binary" + "fmt" + "math" + "strings" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" + "github.com/tetratelabs/wazero/internal/platform" +) + +// NewBackend returns a new backend for arm64. +func NewBackend() backend.Machine { + m := &machine{ + cpuFeatures: platform.CpuFeatures, + regAlloc: regalloc.NewAllocator[*instruction, *labelPosition, *regAllocFn](regInfo), + spillSlots: map[regalloc.VRegID]int64{}, + amodePool: wazevoapi.NewPool[amode](nil), + labelPositionPool: wazevoapi.NewIDedPool[labelPosition](resetLabelPosition), + instrPool: wazevoapi.NewPool[instruction](resetInstruction), + constSwizzleMaskConstIndex: -1, + constSqmulRoundSatIndex: -1, + constI8x16SHLMaskTableIndex: -1, + constI8x16LogicalSHRMaskTableIndex: -1, + constF64x2CvtFromIMaskIndex: -1, + constTwop52Index: -1, + constI32sMaxOnF64x2Index: -1, + constI32uMaxOnF64x2Index: -1, + constAllOnesI8x16Index: -1, + constAllOnesI16x8Index: -1, + constExtAddPairwiseI16x8uMask1Index: -1, + constExtAddPairwiseI16x8uMask2Index: -1, + } + m.regAllocFn.m = m + return m +} + +type ( + // machine implements backend.Machine for amd64. + machine struct { + c backend.Compiler + stackBoundsCheckDisabled bool + + instrPool wazevoapi.Pool[instruction] + amodePool wazevoapi.Pool[amode] + + cpuFeatures platform.CpuFeatureFlags + + regAlloc regalloc.Allocator[*instruction, *labelPosition, *regAllocFn] + regAllocFn regAllocFn + regAllocStarted bool + + // labelPositionPool is the pool of labelPosition. The id is the label where + // if the label is less than the maxSSABlockID, it's the ssa.BasicBlockID. + labelPositionPool wazevoapi.IDedPool[labelPosition] + // nextLabel is the next label to be allocated. The first free label comes after maxSSABlockID + // so that we can have an identical label for the SSA block ID, which is useful for debugging. + nextLabel label + // rootInstr is the first instruction of the function. + rootInstr *instruction + // currentLabelPos is the currently-compiled ssa.BasicBlock's labelPosition. + currentLabelPos *labelPosition + // orderedSSABlockLabelPos is the ordered list of labelPosition in the generated code for each ssa.BasicBlock. + orderedSSABlockLabelPos []*labelPosition + // returnLabelPos is the labelPosition for the return block. + returnLabelPos labelPosition + // perBlockHead and perBlockEnd are the head and tail of the instruction list per currently-compiled ssa.BasicBlock. + perBlockHead, perBlockEnd *instruction + // pendingInstructions are the instructions which are not yet emitted into the instruction list. + pendingInstructions []*instruction + // maxSSABlockID is the maximum ssa.BasicBlockID in the current function. + maxSSABlockID label + + spillSlotSize int64 + spillSlots map[regalloc.VRegID]int64 + currentABI *backend.FunctionABI + clobberedRegs []regalloc.VReg + + maxRequiredStackSizeForCalls int64 + + labelResolutionPends []labelResolutionPend + + // jmpTableTargets holds the labels of the jump table targets. + jmpTableTargets [][]uint32 + // jmpTableTargetNext is the index to the jmpTableTargets slice to be used for the next jump table. + jmpTableTargetsNext int + consts []_const + + constSwizzleMaskConstIndex, constSqmulRoundSatIndex, + constI8x16SHLMaskTableIndex, constI8x16LogicalSHRMaskTableIndex, + constF64x2CvtFromIMaskIndex, constTwop52Index, + constI32sMaxOnF64x2Index, constI32uMaxOnF64x2Index, + constAllOnesI8x16Index, constAllOnesI16x8Index, + constExtAddPairwiseI16x8uMask1Index, constExtAddPairwiseI16x8uMask2Index int + } + + _const struct { + lo, hi uint64 + _var []byte + label label + labelPos *labelPosition + } + + labelResolutionPend struct { + instr *instruction + instrOffset int64 + // imm32Offset is the offset of the last 4 bytes of the instruction. + imm32Offset int64 + } +) + +type ( + // label represents a position in the generated code which is either + // a real instruction or the constant InstructionPool (e.g. jump tables). + // + // This is exactly the same as the traditional "label" in assembly code. + label uint32 + + // labelPosition represents the regions of the generated code which the label represents. + // This implements regalloc.Block. + labelPosition struct { + // sb is not nil if this corresponds to a ssa.BasicBlock. + sb ssa.BasicBlock + // cur is used to walk through the instructions in the block during the register allocation. + cur, + // begin and end are the first and last instructions of the block. + begin, end *instruction + // binaryOffset is the offset in the binary where the label is located. + binaryOffset int64 + } +) + +// String implements backend.Machine. +func (l label) String() string { + return fmt.Sprintf("L%d", l) +} + +func resetLabelPosition(l *labelPosition) { + *l = labelPosition{} +} + +const labelReturn = math.MaxUint32 + +func ssaBlockLabel(sb ssa.BasicBlock) label { + if sb.ReturnBlock() { + return labelReturn + } + return label(sb.ID()) +} + +// getOrAllocateSSABlockLabelPosition returns the labelPosition for the given basic block. +func (m *machine) getOrAllocateSSABlockLabelPosition(sb ssa.BasicBlock) *labelPosition { + if sb.ReturnBlock() { + m.returnLabelPos.sb = sb + return &m.returnLabelPos + } + + l := ssaBlockLabel(sb) + pos := m.labelPositionPool.GetOrAllocate(int(l)) + pos.sb = sb + return pos +} + +func (m *machine) getOrAllocateConstLabel(i *int, _var []byte) label { + index := *i + if index == -1 { + l, pos := m.allocateLabel() + index = len(m.consts) + m.consts = append(m.consts, _const{ + _var: _var, + label: l, + labelPos: pos, + }) + *i = index + } + return m.consts[index].label +} + +// Reset implements backend.Machine. +func (m *machine) Reset() { + m.consts = m.consts[:0] + m.clobberedRegs = m.clobberedRegs[:0] + for key := range m.spillSlots { + m.clobberedRegs = append(m.clobberedRegs, regalloc.VReg(key)) + } + for _, key := range m.clobberedRegs { + delete(m.spillSlots, regalloc.VRegID(key)) + } + + m.stackBoundsCheckDisabled = false + m.regAlloc.Reset() + m.labelPositionPool.Reset() + m.instrPool.Reset() + m.regAllocStarted = false + m.clobberedRegs = m.clobberedRegs[:0] + + m.spillSlotSize = 0 + m.maxRequiredStackSizeForCalls = 0 + m.perBlockHead, m.perBlockEnd, m.rootInstr = nil, nil, nil + m.pendingInstructions = m.pendingInstructions[:0] + m.orderedSSABlockLabelPos = m.orderedSSABlockLabelPos[:0] + + m.amodePool.Reset() + m.jmpTableTargetsNext = 0 + m.constSwizzleMaskConstIndex = -1 + m.constSqmulRoundSatIndex = -1 + m.constI8x16SHLMaskTableIndex = -1 + m.constI8x16LogicalSHRMaskTableIndex = -1 + m.constF64x2CvtFromIMaskIndex = -1 + m.constTwop52Index = -1 + m.constI32sMaxOnF64x2Index = -1 + m.constI32uMaxOnF64x2Index = -1 + m.constAllOnesI8x16Index = -1 + m.constAllOnesI16x8Index = -1 + m.constExtAddPairwiseI16x8uMask1Index = -1 + m.constExtAddPairwiseI16x8uMask2Index = -1 +} + +// StartLoweringFunction implements backend.Machine StartLoweringFunction. +func (m *machine) StartLoweringFunction(maxBlockID ssa.BasicBlockID) { + m.maxSSABlockID = label(maxBlockID) + m.nextLabel = label(maxBlockID) + 1 +} + +// LinkAdjacentBlocks implements backend.Machine. +func (m *machine) LinkAdjacentBlocks(prev, next ssa.BasicBlock) { + prevPos, nextPos := m.getOrAllocateSSABlockLabelPosition(prev), m.getOrAllocateSSABlockLabelPosition(next) + prevPos.end.next = nextPos.begin +} + +// StartBlock implements backend.Machine. +func (m *machine) StartBlock(blk ssa.BasicBlock) { + m.currentLabelPos = m.getOrAllocateSSABlockLabelPosition(blk) + labelPos := m.currentLabelPos + end := m.allocateNop() + m.perBlockHead, m.perBlockEnd = end, end + labelPos.begin, labelPos.end = end, end + m.orderedSSABlockLabelPos = append(m.orderedSSABlockLabelPos, labelPos) +} + +// EndBlock implements ExecutableContext. +func (m *machine) EndBlock() { + // Insert nop0 as the head of the block for convenience to simplify the logic of inserting instructions. + m.insertAtPerBlockHead(m.allocateNop()) + + m.currentLabelPos.begin = m.perBlockHead + + if m.currentLabelPos.sb.EntryBlock() { + m.rootInstr = m.perBlockHead + } +} + +func (m *machine) insertAtPerBlockHead(i *instruction) { + if m.perBlockHead == nil { + m.perBlockHead = i + m.perBlockEnd = i + return + } + + i.next = m.perBlockHead + m.perBlockHead.prev = i + m.perBlockHead = i +} + +// FlushPendingInstructions implements backend.Machine. +func (m *machine) FlushPendingInstructions() { + l := len(m.pendingInstructions) + if l == 0 { + return + } + for i := l - 1; i >= 0; i-- { // reverse because we lower instructions in reverse order. + m.insertAtPerBlockHead(m.pendingInstructions[i]) + } + m.pendingInstructions = m.pendingInstructions[:0] +} + +// DisableStackCheck implements backend.Machine. +func (m *machine) DisableStackCheck() { m.stackBoundsCheckDisabled = true } + +// SetCompiler implements backend.Machine. +func (m *machine) SetCompiler(c backend.Compiler) { + m.c = c + m.regAllocFn.ssaB = c.SSABuilder() +} + +// SetCurrentABI implements backend.Machine. +func (m *machine) SetCurrentABI(abi *backend.FunctionABI) { m.currentABI = abi } + +// RegAlloc implements backend.Machine. +func (m *machine) RegAlloc() { + rf := m.regAllocFn + m.regAllocStarted = true + m.regAlloc.DoAllocation(&rf) + // Now that we know the final spill slot size, we must align spillSlotSize to 16 bytes. + m.spillSlotSize = (m.spillSlotSize + 15) &^ 15 +} + +// InsertReturn implements backend.Machine. +func (m *machine) InsertReturn() { + i := m.allocateInstr().asRet() + m.insert(i) +} + +// LowerSingleBranch implements backend.Machine. +func (m *machine) LowerSingleBranch(b *ssa.Instruction) { + switch b.Opcode() { + case ssa.OpcodeJump: + _, _, targetBlkID := b.BranchData() + if b.IsFallthroughJump() { + return + } + jmp := m.allocateInstr() + target := ssaBlockLabel(m.c.SSABuilder().BasicBlock(targetBlkID)) + if target == labelReturn { + jmp.asRet() + } else { + jmp.asJmp(newOperandLabel(target)) + } + m.insert(jmp) + case ssa.OpcodeBrTable: + index, targetBlkIDs := b.BrTableData() + m.lowerBrTable(index, targetBlkIDs) + default: + panic("BUG: unexpected branch opcode" + b.Opcode().String()) + } +} + +func (m *machine) addJmpTableTarget(targets ssa.Values) (index int) { + if m.jmpTableTargetsNext == len(m.jmpTableTargets) { + m.jmpTableTargets = append(m.jmpTableTargets, make([]uint32, 0, len(targets.View()))) + } + + index = m.jmpTableTargetsNext + m.jmpTableTargetsNext++ + m.jmpTableTargets[index] = m.jmpTableTargets[index][:0] + for _, targetBlockID := range targets.View() { + target := m.c.SSABuilder().BasicBlock(ssa.BasicBlockID(targetBlockID)) + m.jmpTableTargets[index] = append(m.jmpTableTargets[index], uint32(ssaBlockLabel(target))) + } + return +} + +var condBranchMatches = [...]ssa.Opcode{ssa.OpcodeIcmp, ssa.OpcodeFcmp} + +func (m *machine) lowerBrTable(index ssa.Value, targets ssa.Values) { + _v := m.getOperand_Reg(m.c.ValueDefinition(index)) + v := m.copyToTmp(_v.reg()) + + targetCount := len(targets.View()) + + // First, we need to do the bounds check. + maxIndex := m.c.AllocateVReg(ssa.TypeI32) + m.lowerIconst(maxIndex, uint64(targetCount-1), false) + cmp := m.allocateInstr().asCmpRmiR(true, newOperandReg(maxIndex), v, false) + m.insert(cmp) + + // Then do the conditional move maxIndex to v if v > maxIndex. + cmov := m.allocateInstr().asCmove(condNB, newOperandReg(maxIndex), v, false) + m.insert(cmov) + + // Now that v has the correct index. Load the address of the jump table into the addr. + addr := m.c.AllocateVReg(ssa.TypeI64) + leaJmpTableAddr := m.allocateInstr() + m.insert(leaJmpTableAddr) + + // Then add the target's offset into jmpTableAddr. + loadTargetOffsetFromJmpTable := m.allocateInstr().asAluRmiR(aluRmiROpcodeAdd, + // Shift by 3 because each entry is 8 bytes. + newOperandMem(m.newAmodeRegRegShift(0, addr, v, 3)), addr, true) + m.insert(loadTargetOffsetFromJmpTable) + + // Now ready to jump. + jmp := m.allocateInstr().asJmp(newOperandReg(addr)) + m.insert(jmp) + + jmpTableBegin, jmpTableBeginLabel := m.allocateBrTarget() + m.insert(jmpTableBegin) + leaJmpTableAddr.asLEA(newOperandLabel(jmpTableBeginLabel), addr) + + jmpTable := m.allocateInstr() + targetSliceIndex := m.addJmpTableTarget(targets) + jmpTable.asJmpTableSequence(targetSliceIndex, targetCount) + m.insert(jmpTable) +} + +// LowerConditionalBranch implements backend.Machine. +func (m *machine) LowerConditionalBranch(b *ssa.Instruction) { + cval, args, targetBlkID := b.BranchData() + if len(args) > 0 { + panic(fmt.Sprintf( + "conditional branch shouldn't have args; likely a bug in critical edge splitting: from %s to %s", + m.currentLabelPos.sb, + targetBlkID, + )) + } + + target := ssaBlockLabel(m.c.SSABuilder().BasicBlock(targetBlkID)) + cvalDef := m.c.ValueDefinition(cval) + + switch m.c.MatchInstrOneOf(cvalDef, condBranchMatches[:]) { + case ssa.OpcodeIcmp: + cvalInstr := cvalDef.Instr + x, y, c := cvalInstr.IcmpData() + + cc := condFromSSAIntCmpCond(c) + if b.Opcode() == ssa.OpcodeBrz { + cc = cc.invert() + } + + // First, perform the comparison and set the flag. + xd, yd := m.c.ValueDefinition(x), m.c.ValueDefinition(y) + if !m.tryLowerBandToFlag(xd, yd) { + m.lowerIcmpToFlag(xd, yd, x.Type() == ssa.TypeI64) + } + + // Then perform the conditional branch. + m.insert(m.allocateInstr().asJmpIf(cc, newOperandLabel(target))) + cvalDef.Instr.MarkLowered() + case ssa.OpcodeFcmp: + cvalInstr := cvalDef.Instr + + f1, f2, and := m.lowerFcmpToFlags(cvalInstr) + isBrz := b.Opcode() == ssa.OpcodeBrz + if isBrz { + f1 = f1.invert() + } + if f2 == condInvalid { + m.insert(m.allocateInstr().asJmpIf(f1, newOperandLabel(target))) + } else { + if isBrz { + f2 = f2.invert() + and = !and + } + jmp1, jmp2 := m.allocateInstr(), m.allocateInstr() + m.insert(jmp1) + m.insert(jmp2) + notTaken, notTakenLabel := m.allocateBrTarget() + m.insert(notTaken) + if and { + jmp1.asJmpIf(f1.invert(), newOperandLabel(notTakenLabel)) + jmp2.asJmpIf(f2, newOperandLabel(target)) + } else { + jmp1.asJmpIf(f1, newOperandLabel(target)) + jmp2.asJmpIf(f2, newOperandLabel(target)) + } + } + + cvalDef.Instr.MarkLowered() + default: + v := m.getOperand_Reg(cvalDef) + + var cc cond + if b.Opcode() == ssa.OpcodeBrz { + cc = condZ + } else { + cc = condNZ + } + + // Perform test %v, %v to set the flag. + cmp := m.allocateInstr().asCmpRmiR(false, v, v.reg(), false) + m.insert(cmp) + m.insert(m.allocateInstr().asJmpIf(cc, newOperandLabel(target))) + } +} + +// LowerInstr implements backend.Machine. +func (m *machine) LowerInstr(instr *ssa.Instruction) { + if l := instr.SourceOffset(); l.Valid() { + info := m.allocateInstr().asEmitSourceOffsetInfo(l) + m.insert(info) + } + + switch op := instr.Opcode(); op { + case ssa.OpcodeBrz, ssa.OpcodeBrnz, ssa.OpcodeJump, ssa.OpcodeBrTable: + panic("BUG: branching instructions are handled by LowerBranches") + case ssa.OpcodeReturn: + panic("BUG: return must be handled by backend.Compiler") + case ssa.OpcodeIconst, ssa.OpcodeF32const, ssa.OpcodeF64const: // Constant instructions are inlined. + case ssa.OpcodeCall, ssa.OpcodeCallIndirect: + m.lowerCall(instr) + case ssa.OpcodeStore, ssa.OpcodeIstore8, ssa.OpcodeIstore16, ssa.OpcodeIstore32: + m.lowerStore(instr) + case ssa.OpcodeIadd: + m.lowerAluRmiROp(instr, aluRmiROpcodeAdd) + case ssa.OpcodeIsub: + m.lowerAluRmiROp(instr, aluRmiROpcodeSub) + case ssa.OpcodeImul: + m.lowerAluRmiROp(instr, aluRmiROpcodeMul) + case ssa.OpcodeSdiv, ssa.OpcodeUdiv, ssa.OpcodeSrem, ssa.OpcodeUrem: + isDiv := op == ssa.OpcodeSdiv || op == ssa.OpcodeUdiv + isSigned := op == ssa.OpcodeSdiv || op == ssa.OpcodeSrem + m.lowerIDivRem(instr, isDiv, isSigned) + case ssa.OpcodeBand: + m.lowerAluRmiROp(instr, aluRmiROpcodeAnd) + case ssa.OpcodeBor: + m.lowerAluRmiROp(instr, aluRmiROpcodeOr) + case ssa.OpcodeBxor: + m.lowerAluRmiROp(instr, aluRmiROpcodeXor) + case ssa.OpcodeIshl: + m.lowerShiftR(instr, shiftROpShiftLeft) + case ssa.OpcodeSshr: + m.lowerShiftR(instr, shiftROpShiftRightArithmetic) + case ssa.OpcodeUshr: + m.lowerShiftR(instr, shiftROpShiftRightLogical) + case ssa.OpcodeRotl: + m.lowerShiftR(instr, shiftROpRotateLeft) + case ssa.OpcodeRotr: + m.lowerShiftR(instr, shiftROpRotateRight) + case ssa.OpcodeClz: + m.lowerClz(instr) + case ssa.OpcodeCtz: + m.lowerCtz(instr) + case ssa.OpcodePopcnt: + m.lowerUnaryRmR(instr, unaryRmROpcodePopcnt) + case ssa.OpcodeFadd, ssa.OpcodeFsub, ssa.OpcodeFmul, ssa.OpcodeFdiv: + m.lowerXmmRmR(instr) + case ssa.OpcodeFabs: + m.lowerFabsFneg(instr) + case ssa.OpcodeFneg: + m.lowerFabsFneg(instr) + case ssa.OpcodeCeil: + m.lowerRound(instr, roundingModeUp) + case ssa.OpcodeFloor: + m.lowerRound(instr, roundingModeDown) + case ssa.OpcodeTrunc: + m.lowerRound(instr, roundingModeZero) + case ssa.OpcodeNearest: + m.lowerRound(instr, roundingModeNearest) + case ssa.OpcodeFmin, ssa.OpcodeFmax: + m.lowerFminFmax(instr) + case ssa.OpcodeFcopysign: + m.lowerFcopysign(instr) + case ssa.OpcodeBitcast: + m.lowerBitcast(instr) + case ssa.OpcodeSqrt: + m.lowerSqrt(instr) + case ssa.OpcodeFpromote: + v := instr.Arg() + rn := m.getOperand_Reg(m.c.ValueDefinition(v)) + rd := m.c.VRegOf(instr.Return()) + cnt := m.allocateInstr() + cnt.asXmmUnaryRmR(sseOpcodeCvtss2sd, rn, rd) + m.insert(cnt) + case ssa.OpcodeFdemote: + v := instr.Arg() + rn := m.getOperand_Reg(m.c.ValueDefinition(v)) + rd := m.c.VRegOf(instr.Return()) + cnt := m.allocateInstr() + cnt.asXmmUnaryRmR(sseOpcodeCvtsd2ss, rn, rd) + m.insert(cnt) + case ssa.OpcodeFcvtToSint, ssa.OpcodeFcvtToSintSat: + x, ctx := instr.Arg2() + rn := m.getOperand_Reg(m.c.ValueDefinition(x)) + rd := m.c.VRegOf(instr.Return()) + ctxVReg := m.c.VRegOf(ctx) + m.lowerFcvtToSint(ctxVReg, rn.reg(), rd, x.Type() == ssa.TypeF64, + instr.Return().Type().Bits() == 64, op == ssa.OpcodeFcvtToSintSat) + case ssa.OpcodeFcvtToUint, ssa.OpcodeFcvtToUintSat: + x, ctx := instr.Arg2() + rn := m.getOperand_Reg(m.c.ValueDefinition(x)) + rd := m.c.VRegOf(instr.Return()) + ctxVReg := m.c.VRegOf(ctx) + m.lowerFcvtToUint(ctxVReg, rn.reg(), rd, x.Type() == ssa.TypeF64, + instr.Return().Type().Bits() == 64, op == ssa.OpcodeFcvtToUintSat) + case ssa.OpcodeFcvtFromSint: + x := instr.Arg() + rn := m.getOperand_Reg(m.c.ValueDefinition(x)) + rd := newOperandReg(m.c.VRegOf(instr.Return())) + m.lowerFcvtFromSint(rn, rd, + x.Type() == ssa.TypeI64, instr.Return().Type().Bits() == 64) + case ssa.OpcodeFcvtFromUint: + x := instr.Arg() + rn := m.getOperand_Reg(m.c.ValueDefinition(x)) + rd := newOperandReg(m.c.VRegOf(instr.Return())) + m.lowerFcvtFromUint(rn, rd, x.Type() == ssa.TypeI64, + instr.Return().Type().Bits() == 64) + case ssa.OpcodeVanyTrue: + m.lowerVanyTrue(instr) + case ssa.OpcodeVallTrue: + m.lowerVallTrue(instr) + case ssa.OpcodeVhighBits: + m.lowerVhighBits(instr) + case ssa.OpcodeVbnot: + m.lowerVbnot(instr) + case ssa.OpcodeVband: + x, y := instr.Arg2() + m.lowerVbBinOp(sseOpcodePand, x, y, instr.Return()) + case ssa.OpcodeVbor: + x, y := instr.Arg2() + m.lowerVbBinOp(sseOpcodePor, x, y, instr.Return()) + case ssa.OpcodeVbxor: + x, y := instr.Arg2() + m.lowerVbBinOp(sseOpcodePxor, x, y, instr.Return()) + case ssa.OpcodeVbandnot: + m.lowerVbandnot(instr, sseOpcodePandn) + case ssa.OpcodeVbitselect: + m.lowerVbitselect(instr) + case ssa.OpcodeVIadd: + x, y, lane := instr.Arg2WithLane() + var vecOp sseOpcode + switch lane { + case ssa.VecLaneI8x16: + vecOp = sseOpcodePaddb + case ssa.VecLaneI16x8: + vecOp = sseOpcodePaddw + case ssa.VecLaneI32x4: + vecOp = sseOpcodePaddd + case ssa.VecLaneI64x2: + vecOp = sseOpcodePaddq + } + m.lowerVbBinOp(vecOp, x, y, instr.Return()) + + case ssa.OpcodeVSaddSat: + x, y, lane := instr.Arg2WithLane() + var vecOp sseOpcode + switch lane { + case ssa.VecLaneI8x16: + vecOp = sseOpcodePaddsb + case ssa.VecLaneI16x8: + vecOp = sseOpcodePaddsw + } + m.lowerVbBinOp(vecOp, x, y, instr.Return()) + + case ssa.OpcodeVUaddSat: + x, y, lane := instr.Arg2WithLane() + var vecOp sseOpcode + switch lane { + case ssa.VecLaneI8x16: + vecOp = sseOpcodePaddusb + case ssa.VecLaneI16x8: + vecOp = sseOpcodePaddusw + } + m.lowerVbBinOp(vecOp, x, y, instr.Return()) + + case ssa.OpcodeVIsub: + x, y, lane := instr.Arg2WithLane() + var vecOp sseOpcode + switch lane { + case ssa.VecLaneI8x16: + vecOp = sseOpcodePsubb + case ssa.VecLaneI16x8: + vecOp = sseOpcodePsubw + case ssa.VecLaneI32x4: + vecOp = sseOpcodePsubd + case ssa.VecLaneI64x2: + vecOp = sseOpcodePsubq + } + m.lowerVbBinOp(vecOp, x, y, instr.Return()) + + case ssa.OpcodeVSsubSat: + x, y, lane := instr.Arg2WithLane() + var vecOp sseOpcode + switch lane { + case ssa.VecLaneI8x16: + vecOp = sseOpcodePsubsb + case ssa.VecLaneI16x8: + vecOp = sseOpcodePsubsw + } + m.lowerVbBinOp(vecOp, x, y, instr.Return()) + + case ssa.OpcodeVUsubSat: + x, y, lane := instr.Arg2WithLane() + var vecOp sseOpcode + switch lane { + case ssa.VecLaneI8x16: + vecOp = sseOpcodePsubusb + case ssa.VecLaneI16x8: + vecOp = sseOpcodePsubusw + } + m.lowerVbBinOp(vecOp, x, y, instr.Return()) + + case ssa.OpcodeVImul: + m.lowerVImul(instr) + case ssa.OpcodeVIneg: + x, lane := instr.ArgWithLane() + rn := m.getOperand_Reg(m.c.ValueDefinition(x)) + rd := m.c.VRegOf(instr.Return()) + var vecOp sseOpcode + switch lane { + case ssa.VecLaneI8x16: + vecOp = sseOpcodePsubb + case ssa.VecLaneI16x8: + vecOp = sseOpcodePsubw + case ssa.VecLaneI32x4: + vecOp = sseOpcodePsubd + case ssa.VecLaneI64x2: + vecOp = sseOpcodePsubq + default: + panic("BUG") + } + + tmp := m.c.AllocateVReg(ssa.TypeV128) + m.insert(m.allocateInstr().asZeros(tmp)) + + i := m.allocateInstr() + i.asXmmRmR(vecOp, rn, tmp) + m.insert(i) + + m.copyTo(tmp, rd) + case ssa.OpcodeVFadd: + x, y, lane := instr.Arg2WithLane() + var vecOp sseOpcode + switch lane { + case ssa.VecLaneF32x4: + vecOp = sseOpcodeAddps + case ssa.VecLaneF64x2: + vecOp = sseOpcodeAddpd + } + m.lowerVbBinOp(vecOp, x, y, instr.Return()) + + case ssa.OpcodeVFsub: + x, y, lane := instr.Arg2WithLane() + var vecOp sseOpcode + switch lane { + case ssa.VecLaneF32x4: + vecOp = sseOpcodeSubps + case ssa.VecLaneF64x2: + vecOp = sseOpcodeSubpd + } + m.lowerVbBinOp(vecOp, x, y, instr.Return()) + + case ssa.OpcodeVFdiv: + x, y, lane := instr.Arg2WithLane() + var vecOp sseOpcode + switch lane { + case ssa.VecLaneF32x4: + vecOp = sseOpcodeDivps + case ssa.VecLaneF64x2: + vecOp = sseOpcodeDivpd + } + m.lowerVbBinOp(vecOp, x, y, instr.Return()) + + case ssa.OpcodeVFmul: + x, y, lane := instr.Arg2WithLane() + var vecOp sseOpcode + switch lane { + case ssa.VecLaneF32x4: + vecOp = sseOpcodeMulps + case ssa.VecLaneF64x2: + vecOp = sseOpcodeMulpd + } + m.lowerVbBinOp(vecOp, x, y, instr.Return()) + + case ssa.OpcodeVFneg: + x, lane := instr.ArgWithLane() + rn := m.getOperand_Reg(m.c.ValueDefinition(x)) + rd := m.c.VRegOf(instr.Return()) + + tmp := m.c.AllocateVReg(ssa.TypeV128) + + var shiftOp, xorOp sseOpcode + var shiftAmt uint32 + switch lane { + case ssa.VecLaneF32x4: + shiftOp, shiftAmt, xorOp = sseOpcodePslld, 31, sseOpcodeXorps + case ssa.VecLaneF64x2: + shiftOp, shiftAmt, xorOp = sseOpcodePsllq, 63, sseOpcodeXorpd + } + + zero := m.allocateInstr() + zero.asZeros(tmp) + m.insert(zero) + + // Set all bits on tmp by CMPPD with arg=0 (== pseudo CMPEQPD instruction). + // See https://www.felixcloutier.com/x86/cmpps + // + // Note: if we do not clear all the bits ^ with XORPS, this might end up not setting ones on some lane + // if the lane is NaN. + cmp := m.allocateInstr() + cmp.asXmmRmRImm(sseOpcodeCmppd, uint8(cmpPredEQ_UQ), newOperandReg(tmp), tmp) + m.insert(cmp) + + // Do the left shift on each lane to set only the most significant bit in each. + i := m.allocateInstr() + i.asXmmRmiReg(shiftOp, newOperandImm32(shiftAmt), tmp) + m.insert(i) + + // Get the negated result by XOR on each lane with tmp. + i = m.allocateInstr() + i.asXmmRmR(xorOp, rn, tmp) + m.insert(i) + + m.copyTo(tmp, rd) + + case ssa.OpcodeVSqrt: + x, lane := instr.ArgWithLane() + rn := m.getOperand_Reg(m.c.ValueDefinition(x)) + rd := m.c.VRegOf(instr.Return()) + + var vecOp sseOpcode + switch lane { + case ssa.VecLaneF32x4: + vecOp = sseOpcodeSqrtps + case ssa.VecLaneF64x2: + vecOp = sseOpcodeSqrtpd + } + i := m.allocateInstr() + i.asXmmUnaryRmR(vecOp, rn, rd) + m.insert(i) + + case ssa.OpcodeVImin: + x, y, lane := instr.Arg2WithLane() + var vecOp sseOpcode + switch lane { + case ssa.VecLaneI8x16: + vecOp = sseOpcodePminsb + case ssa.VecLaneI16x8: + vecOp = sseOpcodePminsw + case ssa.VecLaneI32x4: + vecOp = sseOpcodePminsd + } + m.lowerVbBinOp(vecOp, x, y, instr.Return()) + + case ssa.OpcodeVUmin: + x, y, lane := instr.Arg2WithLane() + var vecOp sseOpcode + switch lane { + case ssa.VecLaneI8x16: + vecOp = sseOpcodePminub + case ssa.VecLaneI16x8: + vecOp = sseOpcodePminuw + case ssa.VecLaneI32x4: + vecOp = sseOpcodePminud + } + m.lowerVbBinOp(vecOp, x, y, instr.Return()) + + case ssa.OpcodeVImax: + x, y, lane := instr.Arg2WithLane() + var vecOp sseOpcode + switch lane { + case ssa.VecLaneI8x16: + vecOp = sseOpcodePmaxsb + case ssa.VecLaneI16x8: + vecOp = sseOpcodePmaxsw + case ssa.VecLaneI32x4: + vecOp = sseOpcodePmaxsd + } + m.lowerVbBinOp(vecOp, x, y, instr.Return()) + + case ssa.OpcodeVUmax: + x, y, lane := instr.Arg2WithLane() + var vecOp sseOpcode + switch lane { + case ssa.VecLaneI8x16: + vecOp = sseOpcodePmaxub + case ssa.VecLaneI16x8: + vecOp = sseOpcodePmaxuw + case ssa.VecLaneI32x4: + vecOp = sseOpcodePmaxud + } + m.lowerVbBinOp(vecOp, x, y, instr.Return()) + + case ssa.OpcodeVAvgRound: + x, y, lane := instr.Arg2WithLane() + var vecOp sseOpcode + switch lane { + case ssa.VecLaneI8x16: + vecOp = sseOpcodePavgb + case ssa.VecLaneI16x8: + vecOp = sseOpcodePavgw + } + m.lowerVbBinOp(vecOp, x, y, instr.Return()) + + case ssa.OpcodeVIcmp: + x, y, c, lane := instr.VIcmpData() + m.lowerVIcmp(x, y, c, instr.Return(), lane) + + case ssa.OpcodeVFcmp: + x, y, c, lane := instr.VFcmpData() + m.lowerVFcmp(x, y, c, instr.Return(), lane) + + case ssa.OpcodeExtractlane: + x, index, signed, lane := instr.ExtractlaneData() + m.lowerExtractLane(x, index, signed, instr.Return(), lane) + + case ssa.OpcodeInsertlane: + x, y, index, lane := instr.InsertlaneData() + m.lowerInsertLane(x, y, index, instr.Return(), lane) + + case ssa.OpcodeSwizzle: + x, y, _ := instr.Arg2WithLane() + m.lowerSwizzle(x, y, instr.Return()) + + case ssa.OpcodeShuffle: + x, y, lo, hi := instr.ShuffleData() + m.lowerShuffle(x, y, lo, hi, instr.Return()) + + case ssa.OpcodeSplat: + x, lane := instr.ArgWithLane() + m.lowerSplat(x, instr.Return(), lane) + + case ssa.OpcodeSqmulRoundSat: + x, y := instr.Arg2() + m.lowerSqmulRoundSat(x, y, instr.Return()) + + case ssa.OpcodeVZeroExtLoad: + ptr, offset, typ := instr.VZeroExtLoadData() + var sseOp sseOpcode + // Both movss and movsd clears the higher bits of the destination register upt 128 bits. + // https://www.felixcloutier.com/x86/movss + // https://www.felixcloutier.com/x86/movsd + if typ == ssa.TypeF32 { + sseOp = sseOpcodeMovss + } else { + sseOp = sseOpcodeMovsd + } + mem := m.lowerToAddressMode(ptr, offset) + dst := m.c.VRegOf(instr.Return()) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOp, newOperandMem(mem), dst)) + + case ssa.OpcodeVMinPseudo: + x, y, lane := instr.Arg2WithLane() + var vecOp sseOpcode + switch lane { + case ssa.VecLaneF32x4: + vecOp = sseOpcodeMinps + case ssa.VecLaneF64x2: + vecOp = sseOpcodeMinpd + default: + panic("BUG: unexpected lane type") + } + m.lowerVbBinOpUnaligned(vecOp, y, x, instr.Return()) + + case ssa.OpcodeVMaxPseudo: + x, y, lane := instr.Arg2WithLane() + var vecOp sseOpcode + switch lane { + case ssa.VecLaneF32x4: + vecOp = sseOpcodeMaxps + case ssa.VecLaneF64x2: + vecOp = sseOpcodeMaxpd + default: + panic("BUG: unexpected lane type") + } + m.lowerVbBinOpUnaligned(vecOp, y, x, instr.Return()) + + case ssa.OpcodeVIshl: + x, y, lane := instr.Arg2WithLane() + m.lowerVIshl(x, y, instr.Return(), lane) + + case ssa.OpcodeVSshr: + x, y, lane := instr.Arg2WithLane() + m.lowerVSshr(x, y, instr.Return(), lane) + + case ssa.OpcodeVUshr: + x, y, lane := instr.Arg2WithLane() + m.lowerVUshr(x, y, instr.Return(), lane) + + case ssa.OpcodeVCeil: + x, lane := instr.ArgWithLane() + m.lowerVRound(x, instr.Return(), 0x2, lane == ssa.VecLaneF64x2) + + case ssa.OpcodeVFloor: + x, lane := instr.ArgWithLane() + m.lowerVRound(x, instr.Return(), 0x1, lane == ssa.VecLaneF64x2) + + case ssa.OpcodeVTrunc: + x, lane := instr.ArgWithLane() + m.lowerVRound(x, instr.Return(), 0x3, lane == ssa.VecLaneF64x2) + + case ssa.OpcodeVNearest: + x, lane := instr.ArgWithLane() + m.lowerVRound(x, instr.Return(), 0x0, lane == ssa.VecLaneF64x2) + + case ssa.OpcodeExtIaddPairwise: + x, lane, signed := instr.ExtIaddPairwiseData() + m.lowerExtIaddPairwise(x, instr.Return(), lane, signed) + + case ssa.OpcodeUwidenLow, ssa.OpcodeSwidenLow: + x, lane := instr.ArgWithLane() + m.lowerWidenLow(x, instr.Return(), lane, op == ssa.OpcodeSwidenLow) + + case ssa.OpcodeUwidenHigh, ssa.OpcodeSwidenHigh: + x, lane := instr.ArgWithLane() + m.lowerWidenHigh(x, instr.Return(), lane, op == ssa.OpcodeSwidenHigh) + + case ssa.OpcodeLoadSplat: + ptr, offset, lane := instr.LoadSplatData() + m.lowerLoadSplat(ptr, offset, instr.Return(), lane) + + case ssa.OpcodeVFcvtFromUint, ssa.OpcodeVFcvtFromSint: + x, lane := instr.ArgWithLane() + m.lowerVFcvtFromInt(x, instr.Return(), lane, op == ssa.OpcodeVFcvtFromSint) + + case ssa.OpcodeVFcvtToSintSat, ssa.OpcodeVFcvtToUintSat: + x, lane := instr.ArgWithLane() + m.lowerVFcvtToIntSat(x, instr.Return(), lane, op == ssa.OpcodeVFcvtToSintSat) + + case ssa.OpcodeSnarrow, ssa.OpcodeUnarrow: + x, y, lane := instr.Arg2WithLane() + m.lowerNarrow(x, y, instr.Return(), lane, op == ssa.OpcodeSnarrow) + + case ssa.OpcodeFvpromoteLow: + x := instr.Arg() + src := m.getOperand_Reg(m.c.ValueDefinition(x)) + dst := m.c.VRegOf(instr.Return()) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeCvtps2pd, src, dst)) + + case ssa.OpcodeFvdemote: + x := instr.Arg() + src := m.getOperand_Reg(m.c.ValueDefinition(x)) + dst := m.c.VRegOf(instr.Return()) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeCvtpd2ps, src, dst)) + + case ssa.OpcodeWideningPairwiseDotProductS: + x, y := instr.Arg2() + m.lowerWideningPairwiseDotProductS(x, y, instr.Return()) + + case ssa.OpcodeVIabs: + m.lowerVIabs(instr) + case ssa.OpcodeVIpopcnt: + m.lowerVIpopcnt(instr) + case ssa.OpcodeVFmin: + m.lowerVFmin(instr) + case ssa.OpcodeVFmax: + m.lowerVFmax(instr) + case ssa.OpcodeVFabs: + m.lowerVFabs(instr) + case ssa.OpcodeUndefined: + m.insert(m.allocateInstr().asUD2()) + case ssa.OpcodeExitWithCode: + execCtx, code := instr.ExitWithCodeData() + m.lowerExitWithCode(m.c.VRegOf(execCtx), code) + case ssa.OpcodeExitIfTrueWithCode: + execCtx, c, code := instr.ExitIfTrueWithCodeData() + m.lowerExitIfTrueWithCode(m.c.VRegOf(execCtx), c, code) + case ssa.OpcodeLoad: + ptr, offset, typ := instr.LoadData() + dst := m.c.VRegOf(instr.Return()) + m.lowerLoad(ptr, offset, typ, dst) + case ssa.OpcodeUload8, ssa.OpcodeUload16, ssa.OpcodeUload32, ssa.OpcodeSload8, ssa.OpcodeSload16, ssa.OpcodeSload32: + ptr, offset, _ := instr.LoadData() + ret := m.c.VRegOf(instr.Return()) + m.lowerExtLoad(op, ptr, offset, ret) + case ssa.OpcodeVconst: + result := m.c.VRegOf(instr.Return()) + lo, hi := instr.VconstData() + m.lowerVconst(result, lo, hi) + case ssa.OpcodeSExtend, ssa.OpcodeUExtend: + from, to, signed := instr.ExtendData() + m.lowerExtend(instr.Arg(), instr.Return(), from, to, signed) + case ssa.OpcodeIcmp: + m.lowerIcmp(instr) + case ssa.OpcodeFcmp: + m.lowerFcmp(instr) + case ssa.OpcodeSelect: + cval, x, y := instr.SelectData() + m.lowerSelect(x, y, cval, instr.Return()) + case ssa.OpcodeIreduce: + rn := m.getOperand_Mem_Reg(m.c.ValueDefinition(instr.Arg())) + retVal := instr.Return() + rd := m.c.VRegOf(retVal) + + if retVal.Type() != ssa.TypeI32 { + panic("TODO?: Ireduce to non-i32") + } + m.insert(m.allocateInstr().asMovzxRmR(extModeLQ, rn, rd)) + + case ssa.OpcodeAtomicLoad: + ptr := instr.Arg() + size := instr.AtomicTargetSize() + dst := m.c.VRegOf(instr.Return()) + + // At this point, the ptr is ensured to be aligned, so using a normal load is atomic. + // https://github.com/golang/go/blob/go1.24.0/src/internal/runtime/atomic/atomic_amd64.go#L29 + mem := newOperandMem(m.lowerToAddressMode(ptr, 0)) + load := m.allocateInstr() + switch size { + case 8: + load.asMov64MR(mem, dst) + case 4: + load.asMovzxRmR(extModeLQ, mem, dst) + case 2: + load.asMovzxRmR(extModeWQ, mem, dst) + case 1: + load.asMovzxRmR(extModeBQ, mem, dst) + default: + panic("BUG") + } + m.insert(load) + + case ssa.OpcodeFence: + m.insert(m.allocateInstr().asMFence()) + + case ssa.OpcodeAtomicStore: + ptr, _val := instr.Arg2() + size := instr.AtomicTargetSize() + + val := m.getOperand_Reg(m.c.ValueDefinition(_val)) + // The content on the val register will be overwritten by xchg, so we need to copy it to a temporary register. + copied := m.copyToTmp(val.reg()) + + mem := newOperandMem(m.lowerToAddressMode(ptr, 0)) + store := m.allocateInstr().asXCHG(copied, mem, byte(size)) + m.insert(store) + + case ssa.OpcodeAtomicCas: + addr, exp, repl := instr.Arg3() + size := instr.AtomicTargetSize() + m.lowerAtomicCas(addr, exp, repl, size, instr.Return()) + + case ssa.OpcodeAtomicRmw: + addr, val := instr.Arg2() + atomicOp, size := instr.AtomicRmwData() + m.lowerAtomicRmw(atomicOp, addr, val, size, instr.Return()) + + case ssa.OpcodeTailCallReturnCall, ssa.OpcodeTailCallReturnCallIndirect: + m.lowerTailCall(instr) + + default: + panic("TODO: lowering " + op.String()) + } +} + +func (m *machine) lowerAtomicRmw(op ssa.AtomicRmwOp, addr, val ssa.Value, size uint64, ret ssa.Value) { + mem := m.lowerToAddressMode(addr, 0) + _val := m.getOperand_Reg(m.c.ValueDefinition(val)) + + switch op { + case ssa.AtomicRmwOpAdd, ssa.AtomicRmwOpSub: + valCopied := m.copyToTmp(_val.reg()) + if op == ssa.AtomicRmwOpSub { + // Negate the value. + m.insert(m.allocateInstr().asNeg(newOperandReg(valCopied), true)) + } + m.insert(m.allocateInstr().asLockXAdd(valCopied, mem, byte(size))) + m.clearHigherBitsForAtomic(valCopied, size, ret.Type()) + m.copyTo(valCopied, m.c.VRegOf(ret)) + + case ssa.AtomicRmwOpAnd, ssa.AtomicRmwOpOr, ssa.AtomicRmwOpXor: + accumulator := raxVReg + // Reserve rax for the accumulator to make regalloc happy. + // Note: do this initialization before defining valCopied, because it might be the same register and + // if that happens, the unnecessary load/store will be performed inside the loop. + // This can be mitigated in any way once the register allocator is clever enough. + m.insert(m.allocateInstr().asDefineUninitializedReg(accumulator)) + + // Copy the value to a temporary register. + valCopied := m.copyToTmp(_val.reg()) + m.clearHigherBitsForAtomic(valCopied, size, ret.Type()) + + memOp := newOperandMem(mem) + tmp := m.c.AllocateVReg(ssa.TypeI64) + beginLoop, beginLoopLabel := m.allocateBrTarget() + { + m.insert(beginLoop) + // Reset the value on tmp by the original value. + m.copyTo(valCopied, tmp) + // Load the current value at the memory location into accumulator. + switch size { + case 1: + m.insert(m.allocateInstr().asMovzxRmR(extModeBQ, memOp, accumulator)) + case 2: + m.insert(m.allocateInstr().asMovzxRmR(extModeWQ, memOp, accumulator)) + case 4: + m.insert(m.allocateInstr().asMovzxRmR(extModeLQ, memOp, accumulator)) + case 8: + m.insert(m.allocateInstr().asMov64MR(memOp, accumulator)) + default: + panic("BUG") + } + // Then perform the logical operation on the accumulator and the value on tmp. + switch op { + case ssa.AtomicRmwOpAnd: + m.insert(m.allocateInstr().asAluRmiR(aluRmiROpcodeAnd, newOperandReg(accumulator), tmp, true)) + case ssa.AtomicRmwOpOr: + m.insert(m.allocateInstr().asAluRmiR(aluRmiROpcodeOr, newOperandReg(accumulator), tmp, true)) + case ssa.AtomicRmwOpXor: + m.insert(m.allocateInstr().asAluRmiR(aluRmiROpcodeXor, newOperandReg(accumulator), tmp, true)) + default: + panic("BUG") + } + // Finally, try compare-exchange the value at the memory location with the tmp. + m.insert(m.allocateInstr().asLockCmpXCHG(tmp, memOp.addressMode(), byte(size))) + // If it succeeds, ZF will be set, and we can break the loop. + m.insert(m.allocateInstr().asJmpIf(condNZ, newOperandLabel(beginLoopLabel))) + } + + // valCopied must be alive at the end of the loop. + m.insert(m.allocateInstr().asNopUseReg(valCopied)) + + // At this point, accumulator contains the result. + m.clearHigherBitsForAtomic(accumulator, size, ret.Type()) + m.copyTo(accumulator, m.c.VRegOf(ret)) + + case ssa.AtomicRmwOpXchg: + valCopied := m.copyToTmp(_val.reg()) + + m.insert(m.allocateInstr().asXCHG(valCopied, newOperandMem(mem), byte(size))) + m.clearHigherBitsForAtomic(valCopied, size, ret.Type()) + m.copyTo(valCopied, m.c.VRegOf(ret)) + + default: + panic("BUG") + } +} + +func (m *machine) lowerAtomicCas(addr, exp, repl ssa.Value, size uint64, ret ssa.Value) { + mem := m.lowerToAddressMode(addr, 0) + expOp := m.getOperand_Reg(m.c.ValueDefinition(exp)) + replOp := m.getOperand_Reg(m.c.ValueDefinition(repl)) + + accumulator := raxVReg + m.copyTo(expOp.reg(), accumulator) + m.insert(m.allocateInstr().asLockCmpXCHG(replOp.reg(), mem, byte(size))) + m.clearHigherBitsForAtomic(accumulator, size, ret.Type()) + m.copyTo(accumulator, m.c.VRegOf(ret)) +} + +func (m *machine) clearHigherBitsForAtomic(r regalloc.VReg, valSize uint64, resultType ssa.Type) { + switch resultType { + case ssa.TypeI32: + switch valSize { + case 1: + m.insert(m.allocateInstr().asMovzxRmR(extModeBL, newOperandReg(r), r)) + case 2: + m.insert(m.allocateInstr().asMovzxRmR(extModeWL, newOperandReg(r), r)) + } + case ssa.TypeI64: + switch valSize { + case 1: + m.insert(m.allocateInstr().asMovzxRmR(extModeBQ, newOperandReg(r), r)) + case 2: + m.insert(m.allocateInstr().asMovzxRmR(extModeWQ, newOperandReg(r), r)) + case 4: + m.insert(m.allocateInstr().asMovzxRmR(extModeLQ, newOperandReg(r), r)) + } + } +} + +func (m *machine) lowerFcmp(instr *ssa.Instruction) { + f1, f2, and := m.lowerFcmpToFlags(instr) + rd := m.c.VRegOf(instr.Return()) + if f2 == condInvalid { + tmp := m.c.AllocateVReg(ssa.TypeI32) + m.insert(m.allocateInstr().asSetcc(f1, tmp)) + // On amd64, setcc only sets the first byte of the register, so we need to zero extend it to match + // the semantics of Icmp that sets either 0 or 1. + m.insert(m.allocateInstr().asMovzxRmR(extModeBQ, newOperandReg(tmp), rd)) + } else { + tmp1, tmp2 := m.c.AllocateVReg(ssa.TypeI32), m.c.AllocateVReg(ssa.TypeI32) + m.insert(m.allocateInstr().asSetcc(f1, tmp1)) + m.insert(m.allocateInstr().asSetcc(f2, tmp2)) + var op aluRmiROpcode + if and { + op = aluRmiROpcodeAnd + } else { + op = aluRmiROpcodeOr + } + m.insert(m.allocateInstr().asAluRmiR(op, newOperandReg(tmp1), tmp2, false)) + m.insert(m.allocateInstr().asMovzxRmR(extModeBQ, newOperandReg(tmp2), rd)) + } +} + +func (m *machine) lowerIcmp(instr *ssa.Instruction) { + x, y, c := instr.IcmpData() + m.lowerIcmpToFlag(m.c.ValueDefinition(x), m.c.ValueDefinition(y), x.Type() == ssa.TypeI64) + rd := m.c.VRegOf(instr.Return()) + tmp := m.c.AllocateVReg(ssa.TypeI32) + m.insert(m.allocateInstr().asSetcc(condFromSSAIntCmpCond(c), tmp)) + // On amd64, setcc only sets the first byte of the register, so we need to zero extend it to match + // the semantics of Icmp that sets either 0 or 1. + m.insert(m.allocateInstr().asMovzxRmR(extModeBQ, newOperandReg(tmp), rd)) +} + +func (m *machine) lowerSelect(x, y, cval, ret ssa.Value) { + xo, yo := m.getOperand_Mem_Reg(m.c.ValueDefinition(x)), m.getOperand_Reg(m.c.ValueDefinition(y)) + rd := m.c.VRegOf(ret) + + var cond cond + cvalDef := m.c.ValueDefinition(cval) + switch m.c.MatchInstrOneOf(cvalDef, condBranchMatches[:]) { + case ssa.OpcodeIcmp: + icmp := cvalDef.Instr + xc, yc, cc := icmp.IcmpData() + m.lowerIcmpToFlag(m.c.ValueDefinition(xc), m.c.ValueDefinition(yc), xc.Type() == ssa.TypeI64) + cond = condFromSSAIntCmpCond(cc) + icmp.Lowered() + default: // TODO: match ssa.OpcodeFcmp for optimization, but seems a bit complex. + cv := m.getOperand_Reg(cvalDef) + test := m.allocateInstr().asCmpRmiR(false, cv, cv.reg(), false) + m.insert(test) + cond = condNZ + } + + if typ := x.Type(); typ.IsInt() { + _64 := typ.Bits() == 64 + mov := m.allocateInstr() + tmp := m.c.AllocateVReg(typ) + switch yo.kind { + case operandKindReg: + mov.asMovRR(yo.reg(), tmp, _64) + case operandKindMem: + if _64 { + mov.asMov64MR(yo, tmp) + } else { + mov.asMovzxRmR(extModeLQ, yo, tmp) + } + default: + panic("BUG") + } + m.insert(mov) + cmov := m.allocateInstr().asCmove(cond, xo, tmp, _64) + m.insert(cmov) + m.insert(m.allocateInstr().asMovRR(tmp, rd, _64)) + } else { + mov := m.allocateInstr() + tmp := m.c.AllocateVReg(typ) + switch typ { + case ssa.TypeF32: + mov.asXmmUnaryRmR(sseOpcodeMovss, yo, tmp) + case ssa.TypeF64: + mov.asXmmUnaryRmR(sseOpcodeMovsd, yo, tmp) + case ssa.TypeV128: + mov.asXmmUnaryRmR(sseOpcodeMovdqu, yo, tmp) + default: + panic("BUG") + } + m.insert(mov) + + cmov := m.allocateInstr().asXmmCMov(cond, xo, tmp, typ.Size()) + m.insert(cmov) + + m.copyTo(tmp, rd) + } +} + +func (m *machine) lowerXmmCmovAfterRegAlloc(i *instruction) { + x := i.op1 + rd := i.op2.reg() + cond := cond(i.u1) + + jcc := m.allocateInstr() + m.insert(jcc) + + mov := m.allocateInstr() + switch i.u2 { + case 4: + mov.asXmmUnaryRmR(sseOpcodeMovss, x, rd) + case 8: + mov.asXmmUnaryRmR(sseOpcodeMovsd, x, rd) + case 16: + mov.asXmmUnaryRmR(sseOpcodeMovdqu, x, rd) + default: + panic("BUG") + } + m.insert(mov) + + nop, end := m.allocateBrTarget() + m.insert(nop) + jcc.asJmpIf(cond.invert(), newOperandLabel(end)) +} + +func (m *machine) lowerExtend(_arg, ret ssa.Value, from, to byte, signed bool) { + rd0 := m.c.VRegOf(ret) + arg := m.getOperand_Mem_Reg(m.c.ValueDefinition(_arg)) + + rd := m.c.AllocateVReg(ret.Type()) + + ext := m.allocateInstr() + switch { + case from == 8 && to == 16 && signed: + ext.asMovsxRmR(extModeBQ, arg, rd) + case from == 8 && to == 16 && !signed: + ext.asMovzxRmR(extModeBL, arg, rd) + case from == 8 && to == 32 && signed: + ext.asMovsxRmR(extModeBL, arg, rd) + case from == 8 && to == 32 && !signed: + ext.asMovzxRmR(extModeBQ, arg, rd) + case from == 8 && to == 64 && signed: + ext.asMovsxRmR(extModeBQ, arg, rd) + case from == 8 && to == 64 && !signed: + ext.asMovzxRmR(extModeBQ, arg, rd) + case from == 16 && to == 32 && signed: + ext.asMovsxRmR(extModeWL, arg, rd) + case from == 16 && to == 32 && !signed: + ext.asMovzxRmR(extModeWL, arg, rd) + case from == 16 && to == 64 && signed: + ext.asMovsxRmR(extModeWQ, arg, rd) + case from == 16 && to == 64 && !signed: + ext.asMovzxRmR(extModeWQ, arg, rd) + case from == 32 && to == 64 && signed: + ext.asMovsxRmR(extModeLQ, arg, rd) + case from == 32 && to == 64 && !signed: + ext.asMovzxRmR(extModeLQ, arg, rd) + default: + panic(fmt.Sprintf("BUG: unhandled extend: from=%d, to=%d, signed=%t", from, to, signed)) + } + m.insert(ext) + + m.copyTo(rd, rd0) +} + +func (m *machine) lowerVconst(dst regalloc.VReg, lo, hi uint64) { + if lo == 0 && hi == 0 { + m.insert(m.allocateInstr().asZeros(dst)) + return + } + + load := m.allocateInstr() + l, pos := m.allocateLabel() + m.consts = append(m.consts, _const{label: l, labelPos: pos, lo: lo, hi: hi}) + load.asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(m.newAmodeRipRel(l)), dst) + m.insert(load) +} + +func (m *machine) lowerCtz(instr *ssa.Instruction) { + if m.cpuFeatures.Has(platform.CpuFeatureAmd64BMI1) { + m.lowerUnaryRmR(instr, unaryRmROpcodeTzcnt) + } else { + // On processors that do not support TZCNT, the BSF instruction is + // executed instead. The key difference between TZCNT and BSF + // instruction is that if source operand is zero, the content of + // destination operand is undefined. + // https://www.felixcloutier.com/x86/tzcnt.html + + x := instr.Arg() + if !x.Type().IsInt() { + panic("BUG?") + } + _64 := x.Type().Bits() == 64 + + xDef := m.c.ValueDefinition(x) + tmp := m.c.AllocateVReg(x.Type()) + rm := m.getOperand_Reg(xDef) + + // First, we have to check if the target is non-zero. + test := m.allocateInstr() + test.asCmpRmiR(false, rm, rm.reg(), _64) + m.insert(test) + + jmpNz := m.allocateInstr() + m.insert(jmpNz) + + // If the value is zero, we just push the const value. + m.lowerIconst(tmp, uint64(x.Type().Bits()), _64) + + // Now jump right after the non-zero case. + jmpAtEnd := m.allocateInstr() + m.insert(jmpAtEnd) + + // jmpNz target label is set here. + nop, nz := m.allocateBrTarget() + jmpNz.asJmpIf(condNZ, newOperandLabel(nz)) + m.insert(nop) + + // Emit the non-zero case. + bsr := m.allocateInstr() + bsr.asUnaryRmR(unaryRmROpcodeBsf, rm, tmp, _64) + m.insert(bsr) + + // jmpAtEnd target label is set here. + nopEnd, end := m.allocateBrTarget() + jmpAtEnd.asJmp(newOperandLabel(end)) + m.insert(nopEnd) + + m.copyTo(tmp, m.c.VRegOf(instr.Return())) + } +} + +func (m *machine) lowerClz(instr *ssa.Instruction) { + if m.cpuFeatures.Has(platform.CpuFeatureAmd64ABM) { + m.lowerUnaryRmR(instr, unaryRmROpcodeLzcnt) + } else { + // On processors that do not support LZCNT, we combine BSR (calculating + // most significant set bit) with XOR. This logic is described in + // "Replace Raw Assembly Code with Builtin Intrinsics" section in: + // https://developer.apple.com/documentation/apple-silicon/addressing-architectural-differences-in-your-macos-code. + + x := instr.Arg() + if !x.Type().IsInt() { + panic("BUG?") + } + _64 := x.Type().Bits() == 64 + + xDef := m.c.ValueDefinition(x) + rm := m.getOperand_Reg(xDef) + tmp := m.c.AllocateVReg(x.Type()) + + // First, we have to check if the rm is non-zero as BSR is undefined + // on zero. See https://www.felixcloutier.com/x86/bsr. + test := m.allocateInstr() + test.asCmpRmiR(false, rm, rm.reg(), _64) + m.insert(test) + + jmpNz := m.allocateInstr() + m.insert(jmpNz) + + // If the value is zero, we just push the const value. + m.lowerIconst(tmp, uint64(x.Type().Bits()), _64) + + // Now jump right after the non-zero case. + jmpAtEnd := m.allocateInstr() + m.insert(jmpAtEnd) + + // jmpNz target label is set here. + nop, nz := m.allocateBrTarget() + jmpNz.asJmpIf(condNZ, newOperandLabel(nz)) + m.insert(nop) + + // Emit the non-zero case. + bsr := m.allocateInstr() + bsr.asUnaryRmR(unaryRmROpcodeBsr, rm, tmp, _64) + m.insert(bsr) + + // Now we XOR the value with the bit length minus one. + xor := m.allocateInstr() + xor.asAluRmiR(aluRmiROpcodeXor, newOperandImm32(uint32(x.Type().Bits()-1)), tmp, _64) + m.insert(xor) + + // jmpAtEnd target label is set here. + nopEnd, end := m.allocateBrTarget() + jmpAtEnd.asJmp(newOperandLabel(end)) + m.insert(nopEnd) + + m.copyTo(tmp, m.c.VRegOf(instr.Return())) + } +} + +func (m *machine) lowerUnaryRmR(si *ssa.Instruction, op unaryRmROpcode) { + x := si.Arg() + if !x.Type().IsInt() { + panic("BUG?") + } + _64 := x.Type().Bits() == 64 + + xDef := m.c.ValueDefinition(x) + rm := m.getOperand_Mem_Reg(xDef) + rd := m.c.VRegOf(si.Return()) + + instr := m.allocateInstr() + instr.asUnaryRmR(op, rm, rd, _64) + m.insert(instr) +} + +func (m *machine) lowerLoad(ptr ssa.Value, offset uint32, typ ssa.Type, dst regalloc.VReg) { + mem := newOperandMem(m.lowerToAddressMode(ptr, offset)) + load := m.allocateInstr() + switch typ { + case ssa.TypeI32: + load.asMovzxRmR(extModeLQ, mem, dst) + case ssa.TypeI64: + load.asMov64MR(mem, dst) + case ssa.TypeF32: + load.asXmmUnaryRmR(sseOpcodeMovss, mem, dst) + case ssa.TypeF64: + load.asXmmUnaryRmR(sseOpcodeMovsd, mem, dst) + case ssa.TypeV128: + load.asXmmUnaryRmR(sseOpcodeMovdqu, mem, dst) + default: + panic("BUG") + } + m.insert(load) +} + +func (m *machine) lowerExtLoad(op ssa.Opcode, ptr ssa.Value, offset uint32, dst regalloc.VReg) { + mem := newOperandMem(m.lowerToAddressMode(ptr, offset)) + load := m.allocateInstr() + switch op { + case ssa.OpcodeUload8: + load.asMovzxRmR(extModeBQ, mem, dst) + case ssa.OpcodeUload16: + load.asMovzxRmR(extModeWQ, mem, dst) + case ssa.OpcodeUload32: + load.asMovzxRmR(extModeLQ, mem, dst) + case ssa.OpcodeSload8: + load.asMovsxRmR(extModeBQ, mem, dst) + case ssa.OpcodeSload16: + load.asMovsxRmR(extModeWQ, mem, dst) + case ssa.OpcodeSload32: + load.asMovsxRmR(extModeLQ, mem, dst) + default: + panic("BUG") + } + m.insert(load) +} + +func (m *machine) lowerExitIfTrueWithCode(execCtx regalloc.VReg, cond ssa.Value, code wazevoapi.ExitCode) { + condDef := m.c.ValueDefinition(cond) + if !m.c.MatchInstr(condDef, ssa.OpcodeIcmp) { + panic("TODO: ExitIfTrue must come after Icmp at the moment: " + condDef.Instr.Opcode().String()) + } + cvalInstr := condDef.Instr + cvalInstr.MarkLowered() + + // We need to copy the execution context to a temp register, because if it's spilled, + // it might end up being reloaded inside the exiting branch. + execCtxTmp := m.copyToTmp(execCtx) + + x, y, c := cvalInstr.IcmpData() + xx, yy := m.c.ValueDefinition(x), m.c.ValueDefinition(y) + if !m.tryLowerBandToFlag(xx, yy) { + m.lowerIcmpToFlag(xx, yy, x.Type() == ssa.TypeI64) + } + + jmpIf := m.allocateInstr() + m.insert(jmpIf) + l := m.lowerExitWithCode(execCtxTmp, code) + jmpIf.asJmpIf(condFromSSAIntCmpCond(c).invert(), newOperandLabel(l)) +} + +func (m *machine) tryLowerBandToFlag(x, y backend.SSAValueDefinition) (ok bool) { + var target backend.SSAValueDefinition + var got bool + if x.IsFromInstr() && x.Instr.Constant() && x.Instr.ConstantVal() == 0 { + if m.c.MatchInstr(y, ssa.OpcodeBand) { + target = y + got = true + } + } + + if y.IsFromInstr() && y.Instr.Constant() && y.Instr.ConstantVal() == 0 { + if m.c.MatchInstr(x, ssa.OpcodeBand) { + target = x + got = true + } + } + + if !got { + return false + } + + bandInstr := target.Instr + bandX, bandY := bandInstr.Arg2() + + xx := m.getOperand_Reg(m.c.ValueDefinition(bandX)) + yy := m.getOperand_Mem_Imm32_Reg(m.c.ValueDefinition(bandY)) + test := m.allocateInstr().asCmpRmiR(false, yy, xx.reg(), bandX.Type() == ssa.TypeI64) + m.insert(test) + bandInstr.MarkLowered() + return true +} + +func (m *machine) allocateExitInstructions(execCtx, exitCodeReg regalloc.VReg) (saveRsp, saveRbp, setExitCode *instruction) { + saveRsp = m.allocateInstr().asMovRM( + rspVReg, + newOperandMem(m.newAmodeImmReg(wazevoapi.ExecutionContextOffsetStackPointerBeforeGoCall.U32(), execCtx)), + 8, + ) + + saveRbp = m.allocateInstr().asMovRM( + rbpVReg, + newOperandMem(m.newAmodeImmReg(wazevoapi.ExecutionContextOffsetFramePointerBeforeGoCall.U32(), execCtx)), + 8, + ) + setExitCode = m.allocateInstr().asMovRM( + exitCodeReg, + newOperandMem(m.newAmodeImmReg(wazevoapi.ExecutionContextOffsetExitCodeOffset.U32(), execCtx)), + 4, + ) + return +} + +func (m *machine) lowerExitWithCode(execCtx regalloc.VReg, code wazevoapi.ExitCode) (afterLabel label) { + exitCodeReg := rbpVReg + saveRsp, saveRbp, setExitCode := m.allocateExitInstructions(execCtx, exitCodeReg) + + // Set save RSP, RBP, and write exit code. + m.insert(saveRsp) + m.insert(saveRbp) + m.lowerIconst(exitCodeReg, uint64(code), false) + m.insert(setExitCode) + + ripReg := rbpVReg + + // Next is to save the current address for stack unwinding. + nop, currentAddrLabel := m.allocateBrTarget() + m.insert(nop) + readRip := m.allocateInstr().asLEA(newOperandLabel(currentAddrLabel), ripReg) + m.insert(readRip) + saveRip := m.allocateInstr().asMovRM( + ripReg, + newOperandMem(m.newAmodeImmReg(wazevoapi.ExecutionContextOffsetGoCallReturnAddress.U32(), execCtx)), + 8, + ) + m.insert(saveRip) + + // Finally exit. + exitSq := m.allocateExitSeq(execCtx) + m.insert(exitSq) + + // Return the label for continuation. + continuation, afterLabel := m.allocateBrTarget() + m.insert(continuation) + return afterLabel +} + +func (m *machine) lowerAluRmiROp(si *ssa.Instruction, op aluRmiROpcode) { + x, y := si.Arg2() + if !x.Type().IsInt() { + panic("BUG?") + } + + _64 := x.Type().Bits() == 64 + + xDef, yDef := m.c.ValueDefinition(x), m.c.ValueDefinition(y) + + // TODO: commutative args can be swapped if one of them is an immediate. + rn := m.getOperand_Reg(xDef) + rm := m.getOperand_Mem_Imm32_Reg(yDef) + rd := m.c.VRegOf(si.Return()) + + // rn is being overwritten, so we first copy its value to a temp register, + // in case it is referenced again later. + tmp := m.copyToTmp(rn.reg()) + + alu := m.allocateInstr() + alu.asAluRmiR(op, rm, tmp, _64) + m.insert(alu) + + // tmp now contains the result, we copy it to the dest register. + m.copyTo(tmp, rd) +} + +func (m *machine) lowerShiftR(si *ssa.Instruction, op shiftROp) { + x, amt := si.Arg2() + if !x.Type().IsInt() { + panic("BUG?") + } + _64 := x.Type().Bits() == 64 + + xDef, amtDef := m.c.ValueDefinition(x), m.c.ValueDefinition(amt) + + opAmt := m.getOperand_Imm32_Reg(amtDef) + rx := m.getOperand_Reg(xDef) + rd := m.c.VRegOf(si.Return()) + + // rx is being overwritten, so we first copy its value to a temp register, + // in case it is referenced again later. + tmpDst := m.copyToTmp(rx.reg()) + + if opAmt.kind == operandKindReg { + // If opAmt is a register we must copy its value to rcx, + // because shiftR encoding mandates that the shift amount is in rcx. + m.copyTo(opAmt.reg(), rcxVReg) + + alu := m.allocateInstr() + alu.asShiftR(op, newOperandReg(rcxVReg), tmpDst, _64) + m.insert(alu) + + } else { + alu := m.allocateInstr() + alu.asShiftR(op, opAmt, tmpDst, _64) + m.insert(alu) + } + + // tmp now contains the result, we copy it to the dest register. + m.copyTo(tmpDst, rd) +} + +func (m *machine) lowerXmmRmR(instr *ssa.Instruction) { + x, y := instr.Arg2() + if !x.Type().IsFloat() { + panic("BUG?") + } + _64 := x.Type().Bits() == 64 + + var op sseOpcode + if _64 { + switch instr.Opcode() { + case ssa.OpcodeFadd: + op = sseOpcodeAddsd + case ssa.OpcodeFsub: + op = sseOpcodeSubsd + case ssa.OpcodeFmul: + op = sseOpcodeMulsd + case ssa.OpcodeFdiv: + op = sseOpcodeDivsd + default: + panic("BUG") + } + } else { + switch instr.Opcode() { + case ssa.OpcodeFadd: + op = sseOpcodeAddss + case ssa.OpcodeFsub: + op = sseOpcodeSubss + case ssa.OpcodeFmul: + op = sseOpcodeMulss + case ssa.OpcodeFdiv: + op = sseOpcodeDivss + default: + panic("BUG") + } + } + + xDef, yDef := m.c.ValueDefinition(x), m.c.ValueDefinition(y) + rn := m.getOperand_Reg(yDef) + rm := m.getOperand_Reg(xDef) + rd := m.c.VRegOf(instr.Return()) + + // rm is being overwritten, so we first copy its value to a temp register, + // in case it is referenced again later. + tmp := m.copyToTmp(rm.reg()) + + xmm := m.allocateInstr().asXmmRmR(op, rn, tmp) + m.insert(xmm) + + m.copyTo(tmp, rd) +} + +func (m *machine) lowerSqrt(instr *ssa.Instruction) { + x := instr.Arg() + if !x.Type().IsFloat() { + panic("BUG") + } + _64 := x.Type().Bits() == 64 + var op sseOpcode + if _64 { + op = sseOpcodeSqrtsd + } else { + op = sseOpcodeSqrtss + } + + xDef := m.c.ValueDefinition(x) + rm := m.getOperand_Mem_Reg(xDef) + rd := m.c.VRegOf(instr.Return()) + + xmm := m.allocateInstr().asXmmUnaryRmR(op, rm, rd) + m.insert(xmm) +} + +func (m *machine) lowerFabsFneg(instr *ssa.Instruction) { + x := instr.Arg() + if !x.Type().IsFloat() { + panic("BUG") + } + _64 := x.Type().Bits() == 64 + var op sseOpcode + var mask uint64 + if _64 { + switch instr.Opcode() { + case ssa.OpcodeFabs: + mask, op = 0x7fffffffffffffff, sseOpcodeAndpd + case ssa.OpcodeFneg: + mask, op = 0x8000000000000000, sseOpcodeXorpd + } + } else { + switch instr.Opcode() { + case ssa.OpcodeFabs: + mask, op = 0x7fffffff, sseOpcodeAndps + case ssa.OpcodeFneg: + mask, op = 0x80000000, sseOpcodeXorps + } + } + + tmp := m.c.AllocateVReg(x.Type()) + + xDef := m.c.ValueDefinition(x) + rm := m.getOperand_Reg(xDef) + rd := m.c.VRegOf(instr.Return()) + + m.lowerFconst(tmp, mask, _64) + + xmm := m.allocateInstr().asXmmRmR(op, rm, tmp) + m.insert(xmm) + + m.copyTo(tmp, rd) +} + +func (m *machine) lowerStore(si *ssa.Instruction) { + value, ptr, offset, storeSizeInBits := si.StoreData() + rm := m.getOperand_Reg(m.c.ValueDefinition(value)) + mem := newOperandMem(m.lowerToAddressMode(ptr, offset)) + + store := m.allocateInstr() + switch value.Type() { + case ssa.TypeI32: + store.asMovRM(rm.reg(), mem, storeSizeInBits/8) + case ssa.TypeI64: + store.asMovRM(rm.reg(), mem, storeSizeInBits/8) + case ssa.TypeF32: + store.asXmmMovRM(sseOpcodeMovss, rm.reg(), mem) + case ssa.TypeF64: + store.asXmmMovRM(sseOpcodeMovsd, rm.reg(), mem) + case ssa.TypeV128: + store.asXmmMovRM(sseOpcodeMovdqu, rm.reg(), mem) + default: + panic("BUG") + } + m.insert(store) +} + +func (m *machine) lowerCall(si *ssa.Instruction) { + isDirectCall := si.Opcode() == ssa.OpcodeCall + indirectCalleePtr, directCallee, isMemmove, calleeABI, stackSlotSize := m.prepareCall(si, isDirectCall) + + if isMemmove { + // Go's memmove *might* use all xmm0-xmm15, so we need to release them. + // https://github.com/golang/go/blob/go1.24.0/src/cmd/compile/abi-internal.md#architecture-specifics + // https://github.com/golang/go/blob/go1.24.0/src/runtime/memmove_amd64.s#L286-L301 + for i := regalloc.RealReg(0); i < 16; i++ { + m.insert(m.allocateInstr().asDefineUninitializedReg(regInfo.RealRegToVReg[xmm0+i])) + } + // Since Go 1.24 it may also use DX, which is not reserved for the function call's 3 args. + // https://github.com/golang/go/blob/go1.24.0/src/runtime/memmove_amd64.s#L123 + m.insert(m.allocateInstr().asDefineUninitializedReg(regInfo.RealRegToVReg[rdx])) + } + + if isDirectCall { + call := m.allocateInstr().asCall(directCallee, calleeABI) + m.insert(call) + } else { + ptrOp := m.getOperand_Mem_Reg(m.c.ValueDefinition(indirectCalleePtr)) + callInd := m.allocateInstr().asCallIndirect(ptrOp, calleeABI) + m.insert(callInd) + } + + if isMemmove { + for i := regalloc.RealReg(0); i < 16; i++ { + m.insert(m.allocateInstr().asNopUseReg(regInfo.RealRegToVReg[xmm0+i])) + } + m.insert(m.allocateInstr().asNopUseReg(regInfo.RealRegToVReg[rdx])) + } + + m.insertReturns(si, calleeABI, stackSlotSize) +} + +func (m *machine) prepareCall(si *ssa.Instruction, isDirectCall bool) (ssa.Value, ssa.FuncRef, bool, *backend.FunctionABI, int64) { + var indirectCalleePtr ssa.Value + var directCallee ssa.FuncRef + var sigID ssa.SignatureID + var args []ssa.Value + var isMemmove bool + if isDirectCall { + directCallee, sigID, args = si.CallData() + } else { + indirectCalleePtr, sigID, args, isMemmove = si.CallIndirectData() + } + calleeABI := m.c.GetFunctionABI(m.c.SSABuilder().ResolveSignature(sigID)) + + stackSlotSize := int64(calleeABI.AlignedArgResultStackSlotSize()) + if m.maxRequiredStackSizeForCalls < stackSlotSize+16 { + m.maxRequiredStackSizeForCalls = stackSlotSize + 16 // 16 == return address + RBP. + } + + // Note: See machine.SetupPrologue for the stack layout. + // The stack pointer decrease/increase will be inserted later in the compilation. + + for i, arg := range args { + reg := m.c.VRegOf(arg) + def := m.c.ValueDefinition(arg) + m.callerGenVRegToFunctionArg(calleeABI, i, reg, def, stackSlotSize) + } + return indirectCalleePtr, directCallee, isMemmove, calleeABI, stackSlotSize +} + +func (m *machine) insertReturns(si *ssa.Instruction, calleeABI *backend.FunctionABI, stackSlotSize int64) { + var index int + r1, rs := si.Returns() + if r1.Valid() { + m.callerGenFunctionReturnVReg(calleeABI, 0, m.c.VRegOf(r1), stackSlotSize) + index++ + } + + for _, r := range rs { + m.callerGenFunctionReturnVReg(calleeABI, index, m.c.VRegOf(r), stackSlotSize) + index++ + } +} + +func (m *machine) lowerTailCall(si *ssa.Instruction) { + isDirectCall := si.Opcode() == ssa.OpcodeTailCallReturnCall + indirectCalleePtr, directCallee, isMemmove, calleeABI, stackSlotSize := m.prepareCall(si, isDirectCall) + if isMemmove { + panic("memmove not supported in tail calls") + } + + isAllRegs := stackSlotSize == 0 + + switch { + case isDirectCall && isAllRegs: + call := m.allocateInstr().asTailCallReturnCall(directCallee, calleeABI) + m.insert(call) + case !isDirectCall && isAllRegs: + // In a tail call we insert the epilogue before the jump instruction, + // so an arbitrary register might be overwritten while restoring the stack. + // So, as compared to a regular indirect call, we ensure the pointer is stored + // in a caller-saved register (r11). + // For details, see internal/engine/RATIONALE.md + ptrOp := m.getOperand_Reg(m.c.ValueDefinition(indirectCalleePtr)) + tmpJmp := r11VReg + m.InsertMove(tmpJmp, ptrOp.reg(), ssa.TypeI64) + callInd := m.allocateInstr().asTailCallReturnCallIndirect(newOperandReg(tmpJmp), calleeABI) + m.insert(callInd) + case isDirectCall && !isAllRegs: + call := m.allocateInstr().asCall(directCallee, calleeABI) + m.insert(call) + case !isDirectCall && !isAllRegs: + ptrOp := m.getOperand_Mem_Reg(m.c.ValueDefinition(indirectCalleePtr)) + callInd := m.allocateInstr().asCallIndirect(ptrOp, calleeABI) + m.insert(callInd) + } + + // If this is a proper tail call, returns will be cleared in the postRegAlloc phase. + m.insertReturns(si, calleeABI, stackSlotSize) +} + +// callerGenVRegToFunctionArg is the opposite of GenFunctionArgToVReg, which is used to generate the +// caller side of the function call. +func (m *machine) callerGenVRegToFunctionArg(a *backend.FunctionABI, argIndex int, reg regalloc.VReg, def backend.SSAValueDefinition, stackSlotSize int64) { + arg := &a.Args[argIndex] + if def.IsFromInstr() { + // Constant instructions are inlined. + if inst := def.Instr; inst.Constant() { + m.insertLoadConstant(inst, reg) + } + } + if arg.Kind == backend.ABIArgKindReg { + m.InsertMove(arg.Reg, reg, arg.Type) + } else { + store := m.allocateInstr() + mem := newOperandMem(m.newAmodeImmReg( + // -stackSlotSize because the stack pointer is not yet decreased. + uint32(arg.Offset-stackSlotSize), rspVReg)) + switch arg.Type { + case ssa.TypeI32: + store.asMovRM(reg, mem, 4) + case ssa.TypeI64: + store.asMovRM(reg, mem, 8) + case ssa.TypeF32: + store.asXmmMovRM(sseOpcodeMovss, reg, mem) + case ssa.TypeF64: + store.asXmmMovRM(sseOpcodeMovsd, reg, mem) + case ssa.TypeV128: + store.asXmmMovRM(sseOpcodeMovdqu, reg, mem) + default: + panic("BUG") + } + m.insert(store) + } +} + +func (m *machine) callerGenFunctionReturnVReg(a *backend.FunctionABI, retIndex int, reg regalloc.VReg, stackSlotSize int64) { + r := &a.Rets[retIndex] + if r.Kind == backend.ABIArgKindReg { + m.InsertMove(reg, r.Reg, r.Type) + } else { + load := m.allocateInstr() + mem := newOperandMem(m.newAmodeImmReg( + // -stackSlotSize because the stack pointer is not yet decreased. + uint32(a.ArgStackSize+r.Offset-stackSlotSize), rspVReg)) + switch r.Type { + case ssa.TypeI32: + load.asMovzxRmR(extModeLQ, mem, reg) + case ssa.TypeI64: + load.asMov64MR(mem, reg) + case ssa.TypeF32: + load.asXmmUnaryRmR(sseOpcodeMovss, mem, reg) + case ssa.TypeF64: + load.asXmmUnaryRmR(sseOpcodeMovsd, mem, reg) + case ssa.TypeV128: + load.asXmmUnaryRmR(sseOpcodeMovdqu, mem, reg) + default: + panic("BUG") + } + m.insert(load) + } +} + +// InsertMove implements backend.Machine. +func (m *machine) InsertMove(dst, src regalloc.VReg, typ ssa.Type) { + switch typ { + case ssa.TypeI32, ssa.TypeI64: + i := m.allocateInstr().asMovRR(src, dst, typ.Bits() == 64) + m.insert(i) + case ssa.TypeF32, ssa.TypeF64, ssa.TypeV128: + var op sseOpcode + switch typ { + case ssa.TypeF32: + op = sseOpcodeMovss + case ssa.TypeF64: + op = sseOpcodeMovsd + case ssa.TypeV128: + op = sseOpcodeMovdqa + } + i := m.allocateInstr().asXmmUnaryRmR(op, newOperandReg(src), dst) + m.insert(i) + default: + panic("BUG") + } +} + +// Format implements backend.Machine. +func (m *machine) Format() string { + begins := map[*instruction]label{} + for l := label(0); l < m.nextLabel; l++ { + pos := m.labelPositionPool.Get(int(l)) + if pos != nil { + begins[pos.begin] = l + } + } + + var lines []string + for cur := m.rootInstr; cur != nil; cur = cur.next { + if l, ok := begins[cur]; ok { + var labelStr string + if l <= m.maxSSABlockID { + labelStr = fmt.Sprintf("%s (SSA Block: blk%d):", l, l) + } else { + labelStr = fmt.Sprintf("%s:", l) + } + lines = append(lines, labelStr) + } + if cur.kind == nop0 { + continue + } + lines = append(lines, "\t"+cur.String()) + } + for _, vc := range m.consts { + if vc._var == nil { + lines = append(lines, fmt.Sprintf("%s: const [%d %d]", vc.label, vc.lo, vc.hi)) + } else { + lines = append(lines, fmt.Sprintf("%s: const %#x", vc.label, vc._var)) + } + } + return "\n" + strings.Join(lines, "\n") + "\n" +} + +func (m *machine) encodeWithoutSSA(root *instruction) { + m.labelResolutionPends = m.labelResolutionPends[:0] + bufPtr := m.c.BufPtr() + for cur := root; cur != nil; cur = cur.next { + offset := int64(len(*bufPtr)) + if cur.kind == nop0 { + l := cur.nop0Label() + pos := m.labelPositionPool.Get(int(l)) + if pos != nil { + pos.binaryOffset = offset + } + } + + needLabelResolution := cur.encode(m.c) + if needLabelResolution { + m.labelResolutionPends = append(m.labelResolutionPends, + labelResolutionPend{instr: cur, imm32Offset: int64(len(*bufPtr)) - 4}, + ) + } + } + + for i := range m.labelResolutionPends { + p := &m.labelResolutionPends[i] + switch p.instr.kind { + case jmp, jmpIf, lea: + target := p.instr.jmpLabel() + targetOffset := m.labelPositionPool.Get(int(target)).binaryOffset + imm32Offset := p.imm32Offset + jmpOffset := int32(targetOffset - (p.imm32Offset + 4)) // +4 because RIP points to the next instruction. + binary.LittleEndian.PutUint32((*bufPtr)[imm32Offset:], uint32(jmpOffset)) + default: + panic("BUG") + } + } +} + +// Encode implements backend.Machine Encode. +func (m *machine) Encode(ctx context.Context) (err error) { + bufPtr := m.c.BufPtr() + + var fn string + var fnIndex int + var labelPosToLabel map[*labelPosition]label + if wazevoapi.PerfMapEnabled { + fn = wazevoapi.GetCurrentFunctionName(ctx) + labelPosToLabel = make(map[*labelPosition]label) + for i := 0; i <= m.labelPositionPool.MaxIDEncountered(); i++ { + pos := m.labelPositionPool.Get(i) + labelPosToLabel[pos] = label(i) + } + fnIndex = wazevoapi.GetCurrentFunctionIndex(ctx) + } + + m.labelResolutionPends = m.labelResolutionPends[:0] + for _, pos := range m.orderedSSABlockLabelPos { + offset := int64(len(*bufPtr)) + pos.binaryOffset = offset + for cur := pos.begin; cur != pos.end.next; cur = cur.next { + offset := int64(len(*bufPtr)) + + switch cur.kind { + case nop0: + l := cur.nop0Label() + if pos := m.labelPositionPool.Get(int(l)); pos != nil { + pos.binaryOffset = offset + } + case sourceOffsetInfo: + m.c.AddSourceOffsetInfo(offset, cur.sourceOffsetInfo()) + } + + needLabelResolution := cur.encode(m.c) + if needLabelResolution { + m.labelResolutionPends = append(m.labelResolutionPends, + labelResolutionPend{instr: cur, instrOffset: offset, imm32Offset: int64(len(*bufPtr)) - 4}, + ) + } + } + + if wazevoapi.PerfMapEnabled { + l := labelPosToLabel[pos] + size := int64(len(*bufPtr)) - offset + wazevoapi.PerfMap.AddModuleEntry(fnIndex, offset, uint64(size), fmt.Sprintf("%s:::::%s", fn, l)) + } + } + + for i := range m.consts { + offset := int64(len(*bufPtr)) + vc := &m.consts[i] + vc.labelPos.binaryOffset = offset + if vc._var == nil { + lo, hi := vc.lo, vc.hi + m.c.Emit8Bytes(lo) + m.c.Emit8Bytes(hi) + } else { + for _, b := range vc._var { + m.c.EmitByte(b) + } + } + } + + buf := *bufPtr + for i := range m.labelResolutionPends { + p := &m.labelResolutionPends[i] + switch p.instr.kind { + case jmp, jmpIf, lea, xmmUnaryRmR: + target := p.instr.jmpLabel() + targetOffset := m.labelPositionPool.Get(int(target)).binaryOffset + imm32Offset := p.imm32Offset + jmpOffset := int32(targetOffset - (p.imm32Offset + 4)) // +4 because RIP points to the next instruction. + binary.LittleEndian.PutUint32(buf[imm32Offset:], uint32(jmpOffset)) + case jmpTableIsland: + tableBegin := p.instrOffset + // Each entry is the offset from the beginning of the jmpTableIsland instruction in 8 bytes. + targets := m.jmpTableTargets[p.instr.u1] + for i, l := range targets { + targetOffset := m.labelPositionPool.Get(int(l)).binaryOffset + jmpOffset := targetOffset - tableBegin + binary.LittleEndian.PutUint64(buf[tableBegin+int64(i)*8:], uint64(jmpOffset)) + } + default: + panic("BUG") + } + } + return +} + +// ResolveRelocations implements backend.Machine. +func (m *machine) ResolveRelocations(refToBinaryOffset []int, _ int, binary []byte, relocations []backend.RelocationInfo, _ []int) { + for _, r := range relocations { + offset := r.Offset + calleeFnOffset := refToBinaryOffset[r.FuncRef] + // offset is the offset of the last 4 bytes of the call instruction. + callInstrOffsetBytes := binary[offset : offset+4] + diff := int64(calleeFnOffset) - (offset + 4) // +4 because we want the offset of the next instruction (In x64, RIP always points to the next instruction). + callInstrOffsetBytes[0] = byte(diff) + callInstrOffsetBytes[1] = byte(diff >> 8) + callInstrOffsetBytes[2] = byte(diff >> 16) + callInstrOffsetBytes[3] = byte(diff >> 24) + } +} + +// CallTrampolineIslandInfo implements backend.Machine CallTrampolineIslandInfo. +func (m *machine) CallTrampolineIslandInfo(_ int) (_, _ int, _ error) { return } + +func (m *machine) lowerIcmpToFlag(xd, yd backend.SSAValueDefinition, _64 bool) { + x := m.getOperand_Reg(xd) + y := m.getOperand_Mem_Imm32_Reg(yd) + cmp := m.allocateInstr().asCmpRmiR(true, y, x.reg(), _64) + m.insert(cmp) +} + +func (m *machine) lowerFcmpToFlags(instr *ssa.Instruction) (f1, f2 cond, and bool) { + x, y, c := instr.FcmpData() + switch c { + case ssa.FloatCmpCondEqual: + f1, f2 = condNP, condZ + and = true + case ssa.FloatCmpCondNotEqual: + f1, f2 = condP, condNZ + case ssa.FloatCmpCondLessThan: + f1 = condFromSSAFloatCmpCond(ssa.FloatCmpCondGreaterThan) + f2 = condInvalid + x, y = y, x + case ssa.FloatCmpCondLessThanOrEqual: + f1 = condFromSSAFloatCmpCond(ssa.FloatCmpCondGreaterThanOrEqual) + f2 = condInvalid + x, y = y, x + default: + f1 = condFromSSAFloatCmpCond(c) + f2 = condInvalid + } + + var opc sseOpcode + if x.Type() == ssa.TypeF32 { + opc = sseOpcodeUcomiss + } else { + opc = sseOpcodeUcomisd + } + + xr := m.getOperand_Reg(m.c.ValueDefinition(x)) + yr := m.getOperand_Mem_Reg(m.c.ValueDefinition(y)) + m.insert(m.allocateInstr().asXmmCmpRmR(opc, yr, xr.reg())) + return +} + +// allocateInstr allocates an instruction. +func (m *machine) allocateInstr() *instruction { + instr := m.instrPool.Allocate() + if !m.regAllocStarted { + instr.addedBeforeRegAlloc = true + } + return instr +} + +func (m *machine) allocateNop() *instruction { + instr := m.allocateInstr() + instr.kind = nop0 + return instr +} + +func (m *machine) insert(i *instruction) { + m.pendingInstructions = append(m.pendingInstructions, i) +} + +func (m *machine) allocateBrTarget() (nop *instruction, l label) { //nolint + l, pos := m.allocateLabel() + nop = m.allocateInstr() + nop.asNop0WithLabel(l) + pos.begin, pos.end = nop, nop + return +} + +func (m *machine) allocateLabel() (label, *labelPosition) { + l := m.nextLabel + pos := m.labelPositionPool.GetOrAllocate(int(l)) + m.nextLabel++ + return l, pos +} + +func (m *machine) getVRegSpillSlotOffsetFromSP(id regalloc.VRegID, size byte) int64 { + offset, ok := m.spillSlots[id] + if !ok { + offset = m.spillSlotSize + m.spillSlots[id] = offset + m.spillSlotSize += int64(size) + } + return offset +} + +func (m *machine) copyTo(src regalloc.VReg, dst regalloc.VReg) { + mov := m.allocateInstr() + if src.RegType() == regalloc.RegTypeInt { + mov.asMovRR(src, dst, true) + } else { + mov.asXmmUnaryRmR(sseOpcodeMovdqu, newOperandReg(src), dst) + } + m.insert(mov) +} + +func (m *machine) copyToTmp(v regalloc.VReg) regalloc.VReg { + typ := m.c.TypeOf(v) + tmp := m.c.AllocateVReg(typ) + m.copyTo(v, tmp) + return tmp +} + +func (m *machine) requiredStackSize() int64 { + return m.maxRequiredStackSizeForCalls + + m.frameSize() + + 16 + // Need for stack checking. + 16 // return address and the caller RBP. +} + +func (m *machine) frameSize() int64 { + s := m.clobberedRegSlotSize() + m.spillSlotSize + if s&0xf != 0 { + panic(fmt.Errorf("BUG: frame size %d is not 16-byte aligned", s)) + } + return s +} + +func (m *machine) clobberedRegSlotSize() int64 { + return int64(len(m.clobberedRegs) * 16) +} + +func (m *machine) lowerIDivRem(si *ssa.Instruction, isDiv bool, signed bool) { + x, y, execCtx := si.Arg3() + + dividend := m.getOperand_Reg(m.c.ValueDefinition(x)) + divisor := m.getOperand_Reg(m.c.ValueDefinition(y)) + ctxVReg := m.c.VRegOf(execCtx) + tmpGp := m.c.AllocateVReg(si.Return().Type()) + + m.copyTo(dividend.reg(), raxVReg) + m.insert(m.allocateInstr().asDefineUninitializedReg(rdxVReg)) + m.insert(m.allocateInstr().asDefineUninitializedReg(tmpGp)) + seq := m.allocateInstr().asIdivRemSequence(ctxVReg, divisor.reg(), tmpGp, isDiv, signed, x.Type().Bits() == 64) + m.insert(seq) + rd := m.c.VRegOf(si.Return()) + if isDiv { + m.copyTo(raxVReg, rd) + } else { + m.copyTo(rdxVReg, rd) + } +} + +func (m *machine) lowerIDivRemSequenceAfterRegAlloc(i *instruction) { + execCtx, divisor, tmpGp, isDiv, signed, _64 := i.idivRemSequenceData() + + dividend := raxVReg + + // Ensure yr is not zero. + test := m.allocateInstr() + test.asCmpRmiR(false, newOperandReg(divisor), divisor, _64) + m.insert(test) + + jnz := m.allocateInstr() + m.insert(jnz) + + nz := m.lowerExitWithCode(execCtx, wazevoapi.ExitCodeIntegerDivisionByZero) + + // If not zero, we can proceed with the division. + jnz.asJmpIf(condNZ, newOperandLabel(nz)) + + var ifRemNeg1 *instruction + if signed { + var neg1 uint64 + if _64 { + neg1 = 0xffffffffffffffff + } else { + neg1 = 0xffffffff + } + m.lowerIconst(tmpGp, neg1, _64) + + if isDiv { + // For signed division, we have to have branches for "math.MinInt{32,64} / -1" + // case which results in the floating point exception via division error as + // the resulting value exceeds the maximum of signed int. + + // First, we check if the divisor is -1. + cmp := m.allocateInstr() + cmp.asCmpRmiR(true, newOperandReg(tmpGp), divisor, _64) + m.insert(cmp) + + ifNotNeg1 := m.allocateInstr() + m.insert(ifNotNeg1) + + var minInt uint64 + if _64 { + minInt = 0x8000000000000000 + } else { + minInt = 0x80000000 + } + m.lowerIconst(tmpGp, minInt, _64) + + // Next we check if the quotient is the most negative value for the signed integer, i.e. + // if we are trying to do (math.MinInt32 / -1) or (math.MinInt64 / -1) respectively. + cmp2 := m.allocateInstr() + cmp2.asCmpRmiR(true, newOperandReg(tmpGp), dividend, _64) + m.insert(cmp2) + + ifNotMinInt := m.allocateInstr() + m.insert(ifNotMinInt) + + // Trap if we are trying to do (math.MinInt32 / -1) or (math.MinInt64 / -1), + // as that is the overflow in division as the result becomes 2^31 which is larger than + // the maximum of signed 32-bit int (2^31-1). + end := m.lowerExitWithCode(execCtx, wazevoapi.ExitCodeIntegerOverflow) + ifNotNeg1.asJmpIf(condNZ, newOperandLabel(end)) + ifNotMinInt.asJmpIf(condNZ, newOperandLabel(end)) + } else { + // If it is remainder, zeros DX register and compare the divisor to -1. + xor := m.allocateInstr().asZeros(rdxVReg) + m.insert(xor) + + // We check if the divisor is -1. + cmp := m.allocateInstr() + cmp.asCmpRmiR(true, newOperandReg(tmpGp), divisor, _64) + m.insert(cmp) + + ifRemNeg1 = m.allocateInstr() + m.insert(ifRemNeg1) + } + + // Sign-extend DX register to have 2*x.Type().Bits() dividend over DX and AX registers. + sed := m.allocateInstr() + sed.asSignExtendData(_64) + m.insert(sed) + } else { + // Zeros DX register to have 2*x.Type().Bits() dividend over DX and AX registers. + zeros := m.allocateInstr().asZeros(rdxVReg) + m.insert(zeros) + } + + div := m.allocateInstr() + div.asDiv(newOperandReg(divisor), signed, _64) + m.insert(div) + + nop, end := m.allocateBrTarget() + m.insert(nop) + // If we are compiling a Rem instruction, when the divisor is -1 we land at the end of the function. + if ifRemNeg1 != nil { + ifRemNeg1.asJmpIf(condZ, newOperandLabel(end)) + } +} + +func (m *machine) lowerRound(instr *ssa.Instruction, imm roundingMode) { + x := instr.Arg() + if !x.Type().IsFloat() { + panic("BUG?") + } + var op sseOpcode + if x.Type().Bits() == 64 { + op = sseOpcodeRoundsd + } else { + op = sseOpcodeRoundss + } + + xDef := m.c.ValueDefinition(x) + rm := m.getOperand_Mem_Reg(xDef) + rd := m.c.VRegOf(instr.Return()) + + xmm := m.allocateInstr().asXmmUnaryRmRImm(op, uint8(imm), rm, rd) + m.insert(xmm) +} + +func (m *machine) lowerFminFmax(instr *ssa.Instruction) { + x, y := instr.Arg2() + if !x.Type().IsFloat() { + panic("BUG?") + } + + _64 := x.Type().Bits() == 64 + isMin := instr.Opcode() == ssa.OpcodeFmin + var minMaxOp sseOpcode + + switch { + case _64 && isMin: + minMaxOp = sseOpcodeMinpd + case _64 && !isMin: + minMaxOp = sseOpcodeMaxpd + case !_64 && isMin: + minMaxOp = sseOpcodeMinps + case !_64 && !isMin: + minMaxOp = sseOpcodeMaxps + } + + xDef, yDef := m.c.ValueDefinition(x), m.c.ValueDefinition(y) + rm := m.getOperand_Reg(xDef) + // We cannot ensure that y is aligned to 16 bytes, so we have to use it on reg. + rn := m.getOperand_Reg(yDef) + rd := m.c.VRegOf(instr.Return()) + + tmp := m.copyToTmp(rm.reg()) + + // Check if this is (either x1 or x2 is NaN) or (x1 equals x2) case. + cmp := m.allocateInstr() + if _64 { + cmp.asXmmCmpRmR(sseOpcodeUcomisd, rn, tmp) + } else { + cmp.asXmmCmpRmR(sseOpcodeUcomiss, rn, tmp) + } + m.insert(cmp) + + // At this point, we have the three cases of conditional flags below + // (See https://www.felixcloutier.com/x86/ucomiss#operation for detail.) + // + // 1) Two values are NaN-free and different: All flags are cleared. + // 2) Two values are NaN-free and equal: Only ZF flags is set. + // 3) One of Two values is NaN: ZF, PF and CF flags are set. + + // Jump instruction to handle 1) case by checking the ZF flag + // as ZF is only set for 2) and 3) cases. + nanFreeOrDiffJump := m.allocateInstr() + m.insert(nanFreeOrDiffJump) + + // Start handling 2) and 3). + + // Jump if one of two values is NaN by checking the parity flag (PF). + ifIsNan := m.allocateInstr() + m.insert(ifIsNan) + + // Start handling 2) NaN-free and equal. + + // Before we exit this case, we have to ensure that positive zero (or negative zero for min instruction) is + // returned if two values are positive and negative zeros. + var op sseOpcode + switch { + case !_64 && isMin: + op = sseOpcodeOrps + case _64 && isMin: + op = sseOpcodeOrpd + case !_64 && !isMin: + op = sseOpcodeAndps + case _64 && !isMin: + op = sseOpcodeAndpd + } + orAnd := m.allocateInstr() + orAnd.asXmmRmR(op, rn, tmp) + m.insert(orAnd) + + // Done, jump to end. + sameExitJump := m.allocateInstr() + m.insert(sameExitJump) + + // Start handling 3) either is NaN. + isNanTarget, isNan := m.allocateBrTarget() + m.insert(isNanTarget) + ifIsNan.asJmpIf(condP, newOperandLabel(isNan)) + + // We emit the ADD instruction to produce the NaN in tmp. + add := m.allocateInstr() + if _64 { + add.asXmmRmR(sseOpcodeAddsd, rn, tmp) + } else { + add.asXmmRmR(sseOpcodeAddss, rn, tmp) + } + m.insert(add) + + // Exit from the NaN case branch. + nanExitJmp := m.allocateInstr() + m.insert(nanExitJmp) + + // Start handling 1). + doMinMaxTarget, doMinMax := m.allocateBrTarget() + m.insert(doMinMaxTarget) + nanFreeOrDiffJump.asJmpIf(condNZ, newOperandLabel(doMinMax)) + + // Now handle the NaN-free and different values case. + minMax := m.allocateInstr() + minMax.asXmmRmR(minMaxOp, rn, tmp) + m.insert(minMax) + + endNop, end := m.allocateBrTarget() + m.insert(endNop) + nanExitJmp.asJmp(newOperandLabel(end)) + sameExitJump.asJmp(newOperandLabel(end)) + + m.copyTo(tmp, rd) +} + +func (m *machine) lowerFcopysign(instr *ssa.Instruction) { + x, y := instr.Arg2() + if !x.Type().IsFloat() { + panic("BUG") + } + + _64 := x.Type().Bits() == 64 + + xDef, yDef := m.c.ValueDefinition(x), m.c.ValueDefinition(y) + rm := m.getOperand_Reg(xDef) + rn := m.getOperand_Reg(yDef) + rd := m.c.VRegOf(instr.Return()) + + // Clear the non-sign bits of src via AND with the mask. + var opAnd, opOr sseOpcode + var signMask uint64 + if _64 { + signMask, opAnd, opOr = 0x8000000000000000, sseOpcodeAndpd, sseOpcodeOrpd + } else { + signMask, opAnd, opOr = 0x80000000, sseOpcodeAndps, sseOpcodeOrps + } + + signBitReg := m.c.AllocateVReg(x.Type()) + m.lowerFconst(signBitReg, signMask, _64) + nonSignBitReg := m.c.AllocateVReg(x.Type()) + m.lowerFconst(nonSignBitReg, ^signMask, _64) + + // Extract the sign bits of rn. + and := m.allocateInstr().asXmmRmR(opAnd, rn, signBitReg) + m.insert(and) + + // Clear the sign bit of dst via AND with the non-sign bit mask. + xor := m.allocateInstr().asXmmRmR(opAnd, rm, nonSignBitReg) + m.insert(xor) + + // Copy the sign bits of src to dst via OR. + or := m.allocateInstr().asXmmRmR(opOr, newOperandReg(signBitReg), nonSignBitReg) + m.insert(or) + + m.copyTo(nonSignBitReg, rd) +} + +func (m *machine) lowerBitcast(instr *ssa.Instruction) { + x, dstTyp := instr.BitcastData() + srcTyp := x.Type() + rn := m.getOperand_Reg(m.c.ValueDefinition(x)) + rd := m.c.VRegOf(instr.Return()) + switch { + case srcTyp == ssa.TypeF32 && dstTyp == ssa.TypeI32: + cvt := m.allocateInstr().asXmmToGpr(sseOpcodeMovd, rn.reg(), rd, false) + m.insert(cvt) + case srcTyp == ssa.TypeI32 && dstTyp == ssa.TypeF32: + cvt := m.allocateInstr().asGprToXmm(sseOpcodeMovd, rn, rd, false) + m.insert(cvt) + case srcTyp == ssa.TypeF64 && dstTyp == ssa.TypeI64: + cvt := m.allocateInstr().asXmmToGpr(sseOpcodeMovq, rn.reg(), rd, true) + m.insert(cvt) + case srcTyp == ssa.TypeI64 && dstTyp == ssa.TypeF64: + cvt := m.allocateInstr().asGprToXmm(sseOpcodeMovq, rn, rd, true) + m.insert(cvt) + default: + panic(fmt.Sprintf("invalid bitcast from %s to %s", srcTyp, dstTyp)) + } +} + +func (m *machine) lowerFcvtToSint(ctxVReg, rn, rd regalloc.VReg, src64, dst64, sat bool) { + var tmpXmm regalloc.VReg + if dst64 { + tmpXmm = m.c.AllocateVReg(ssa.TypeF64) + } else { + tmpXmm = m.c.AllocateVReg(ssa.TypeF32) + } + + m.insert(m.allocateInstr().asDefineUninitializedReg(tmpXmm)) + tmpGp, tmpGp2 := m.c.AllocateVReg(ssa.TypeI64), m.c.AllocateVReg(ssa.TypeI64) + m.insert(m.allocateInstr().asDefineUninitializedReg(tmpGp)) + m.insert(m.allocateInstr().asDefineUninitializedReg(tmpGp2)) + + m.insert(m.allocateFcvtToSintSequence(ctxVReg, rn, tmpGp, tmpGp2, tmpXmm, src64, dst64, sat)) + m.copyTo(tmpGp, rd) +} + +func (m *machine) lowerFcvtToSintSequenceAfterRegalloc(i *instruction) { + execCtx, src, tmpGp, tmpGp2, tmpXmm, src64, dst64, sat := i.fcvtToSintSequenceData() + var cmpOp, truncOp sseOpcode + if src64 { + cmpOp, truncOp = sseOpcodeUcomisd, sseOpcodeCvttsd2si + } else { + cmpOp, truncOp = sseOpcodeUcomiss, sseOpcodeCvttss2si + } + + trunc := m.allocateInstr() + trunc.asXmmToGpr(truncOp, src, tmpGp, dst64) + m.insert(trunc) + + // Check if the dst operand was INT_MIN, by checking it against 1. + cmp1 := m.allocateInstr() + cmp1.asCmpRmiR(true, newOperandImm32(1), tmpGp, dst64) + m.insert(cmp1) + + // If no overflow, then we are done. + doneTarget, done := m.allocateBrTarget() + ifNoOverflow := m.allocateInstr() + ifNoOverflow.asJmpIf(condNO, newOperandLabel(done)) + m.insert(ifNoOverflow) + + // Now, check for NaN. + cmpNan := m.allocateInstr() + cmpNan.asXmmCmpRmR(cmpOp, newOperandReg(src), src) + m.insert(cmpNan) + + // We allocate the "non-nan target" here, but we will insert it later. + notNanTarget, notNaN := m.allocateBrTarget() + ifNotNan := m.allocateInstr() + ifNotNan.asJmpIf(condNP, newOperandLabel(notNaN)) + m.insert(ifNotNan) + + if sat { + // If NaN and saturating, return 0. + zeroDst := m.allocateInstr().asZeros(tmpGp) + m.insert(zeroDst) + + jmpEnd := m.allocateInstr() + jmpEnd.asJmp(newOperandLabel(done)) + m.insert(jmpEnd) + + // Otherwise: + m.insert(notNanTarget) + + // Zero-out the tmp register. + zero := m.allocateInstr().asZeros(tmpXmm) + m.insert(zero) + + cmpXmm := m.allocateInstr().asXmmCmpRmR(cmpOp, newOperandReg(tmpXmm), src) + m.insert(cmpXmm) + + // if >= jump to end. + jmpEnd2 := m.allocateInstr() + jmpEnd2.asJmpIf(condB, newOperandLabel(done)) + m.insert(jmpEnd2) + + // Otherwise, saturate to INT_MAX. + if dst64 { + m.lowerIconst(tmpGp, math.MaxInt64, dst64) + } else { + m.lowerIconst(tmpGp, math.MaxInt32, dst64) + } + + } else { + + // If non-sat, NaN, trap. + m.lowerExitWithCode(execCtx, wazevoapi.ExitCodeInvalidConversionToInteger) + + // Otherwise, we will jump here. + m.insert(notNanTarget) + + // jump over trap if src larger than threshold + condAboveThreshold := condNB + + // The magic constants are various combination of minInt for int[32|64] represented as float[32|64]. + var minInt uint64 + switch { + case src64 && dst64: + minInt = 0xc3e0000000000000 + case src64 && !dst64: + condAboveThreshold = condNBE + minInt = 0xC1E0_0000_0020_0000 + case !src64 && dst64: + minInt = 0xDF00_0000 + case !src64 && !dst64: + minInt = 0xCF00_0000 + } + + loadToGP := m.allocateInstr().asImm(tmpGp2, minInt, src64) + m.insert(loadToGP) + + movToXmm := m.allocateInstr().asGprToXmm(sseOpcodeMovq, newOperandReg(tmpGp2), tmpXmm, src64) + m.insert(movToXmm) + + cmpXmm := m.allocateInstr().asXmmCmpRmR(cmpOp, newOperandReg(tmpXmm), src) + m.insert(cmpXmm) + + jmpIfLarger := m.allocateInstr() + checkPositiveTarget, checkPositive := m.allocateBrTarget() + jmpIfLarger.asJmpIf(condAboveThreshold, newOperandLabel(checkPositive)) + m.insert(jmpIfLarger) + + m.lowerExitWithCode(execCtx, wazevoapi.ExitCodeIntegerOverflow) + + // If positive, it was a real overflow. + m.insert(checkPositiveTarget) + + // Zero out the temp register. + xorpd := m.allocateInstr() + xorpd.asXmmRmR(sseOpcodeXorpd, newOperandReg(tmpXmm), tmpXmm) + m.insert(xorpd) + + pos := m.allocateInstr() + pos.asXmmCmpRmR(cmpOp, newOperandReg(src), tmpXmm) + m.insert(pos) + + // If >= jump to end. + jmp := m.allocateInstr().asJmpIf(condNB, newOperandLabel(done)) + m.insert(jmp) + m.lowerExitWithCode(execCtx, wazevoapi.ExitCodeIntegerOverflow) + } + + m.insert(doneTarget) +} + +func (m *machine) lowerFcvtToUint(ctxVReg, rn, rd regalloc.VReg, src64, dst64, sat bool) { + tmpXmm, tmpXmm2 := m.c.AllocateVReg(ssa.TypeF64), m.c.AllocateVReg(ssa.TypeF64) + m.insert(m.allocateInstr().asDefineUninitializedReg(tmpXmm)) + m.insert(m.allocateInstr().asDefineUninitializedReg(tmpXmm2)) + tmpGp, tmpGp2 := m.c.AllocateVReg(ssa.TypeI64), m.c.AllocateVReg(ssa.TypeI64) + m.insert(m.allocateInstr().asDefineUninitializedReg(tmpGp)) + m.insert(m.allocateInstr().asDefineUninitializedReg(tmpGp2)) + + m.insert(m.allocateFcvtToUintSequence( + ctxVReg, rn, tmpGp, tmpGp2, tmpXmm, tmpXmm2, src64, dst64, sat, + )) + m.copyTo(tmpGp, rd) +} + +func (m *machine) lowerFcvtToUintSequenceAfterRegalloc(i *instruction) { + execCtx, src, tmpGp, tmpGp2, tmpXmm, tmpXmm2, src64, dst64, sat := i.fcvtToUintSequenceData() + + var subOp, cmpOp, truncOp sseOpcode + if src64 { + subOp, cmpOp, truncOp = sseOpcodeSubsd, sseOpcodeUcomisd, sseOpcodeCvttsd2si + } else { + subOp, cmpOp, truncOp = sseOpcodeSubss, sseOpcodeUcomiss, sseOpcodeCvttss2si + } + + doneTarget, done := m.allocateBrTarget() + + switch { + case src64 && dst64: + loadToGP := m.allocateInstr().asImm(tmpGp, 0x43e0000000000000, true) + m.insert(loadToGP) + movToXmm := m.allocateInstr().asGprToXmm(sseOpcodeMovq, newOperandReg(tmpGp), tmpXmm, true) + m.insert(movToXmm) + case src64 && !dst64: + loadToGP := m.allocateInstr().asImm(tmpGp, 0x41e0000000000000, true) + m.insert(loadToGP) + movToXmm := m.allocateInstr().asGprToXmm(sseOpcodeMovq, newOperandReg(tmpGp), tmpXmm, true) + m.insert(movToXmm) + case !src64 && dst64: + loadToGP := m.allocateInstr().asImm(tmpGp, 0x5f000000, false) + m.insert(loadToGP) + movToXmm := m.allocateInstr().asGprToXmm(sseOpcodeMovq, newOperandReg(tmpGp), tmpXmm, false) + m.insert(movToXmm) + case !src64 && !dst64: + loadToGP := m.allocateInstr().asImm(tmpGp, 0x4f000000, false) + m.insert(loadToGP) + movToXmm := m.allocateInstr().asGprToXmm(sseOpcodeMovq, newOperandReg(tmpGp), tmpXmm, false) + m.insert(movToXmm) + } + + cmp := m.allocateInstr() + cmp.asXmmCmpRmR(cmpOp, newOperandReg(tmpXmm), src) + m.insert(cmp) + + // If above `tmp` ("large threshold"), jump to `ifAboveThreshold` + ifAboveThresholdTarget, ifAboveThreshold := m.allocateBrTarget() + jmpIfAboveThreshold := m.allocateInstr() + jmpIfAboveThreshold.asJmpIf(condNB, newOperandLabel(ifAboveThreshold)) + m.insert(jmpIfAboveThreshold) + + ifNotNaNTarget, ifNotNaN := m.allocateBrTarget() + jmpIfNotNaN := m.allocateInstr() + jmpIfNotNaN.asJmpIf(condNP, newOperandLabel(ifNotNaN)) + m.insert(jmpIfNotNaN) + + // If NaN, handle the error condition. + if sat { + // On NaN, saturating, we just return 0. + zeros := m.allocateInstr().asZeros(tmpGp) + m.insert(zeros) + + jmpEnd := m.allocateInstr() + jmpEnd.asJmp(newOperandLabel(done)) + m.insert(jmpEnd) + } else { + // On NaN, non-saturating, we trap. + m.lowerExitWithCode(execCtx, wazevoapi.ExitCodeInvalidConversionToInteger) + } + + // If not NaN, land here. + m.insert(ifNotNaNTarget) + + // Truncation happens here. + + trunc := m.allocateInstr() + trunc.asXmmToGpr(truncOp, src, tmpGp, dst64) + m.insert(trunc) + + // Check if the result is negative. + cmpNeg := m.allocateInstr() + cmpNeg.asCmpRmiR(true, newOperandImm32(0), tmpGp, dst64) + m.insert(cmpNeg) + + // If non-neg, jump to end. + jmpIfNonNeg := m.allocateInstr() + jmpIfNonNeg.asJmpIf(condNL, newOperandLabel(done)) + m.insert(jmpIfNonNeg) + + if sat { + // If the input was "small" (< 2**(width -1)), the only way to get an integer + // overflow is because the input was too small: saturate to the min value, i.e. 0. + zeros := m.allocateInstr().asZeros(tmpGp) + m.insert(zeros) + + jmpEnd := m.allocateInstr() + jmpEnd.asJmp(newOperandLabel(done)) + m.insert(jmpEnd) + } else { + // If not saturating, trap. + m.lowerExitWithCode(execCtx, wazevoapi.ExitCodeIntegerOverflow) + } + + // If above the threshold, land here. + m.insert(ifAboveThresholdTarget) + + // tmpDiff := threshold - rn. + copySrc := m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandReg(src), tmpXmm2) + m.insert(copySrc) + + sub := m.allocateInstr() + sub.asXmmRmR(subOp, newOperandReg(tmpXmm), tmpXmm2) // must be -0x8000000000000000 + m.insert(sub) + + trunc2 := m.allocateInstr() + trunc2.asXmmToGpr(truncOp, tmpXmm2, tmpGp, dst64) + m.insert(trunc2) + + // Check if the result is negative. + cmpNeg2 := m.allocateInstr().asCmpRmiR(true, newOperandImm32(0), tmpGp, dst64) + m.insert(cmpNeg2) + + ifNextLargeTarget, ifNextLarge := m.allocateBrTarget() + jmpIfNextLarge := m.allocateInstr() + jmpIfNextLarge.asJmpIf(condNL, newOperandLabel(ifNextLarge)) + m.insert(jmpIfNextLarge) + + if sat { + // The input was "large" (>= maxInt), so the only way to get an integer + // overflow is because the input was too large: saturate to the max value. + var maxInt uint64 + if dst64 { + maxInt = math.MaxUint64 + } else { + maxInt = math.MaxUint32 + } + m.lowerIconst(tmpGp, maxInt, dst64) + + jmpToEnd := m.allocateInstr() + jmpToEnd.asJmp(newOperandLabel(done)) + m.insert(jmpToEnd) + } else { + // If not saturating, trap. + m.lowerExitWithCode(execCtx, wazevoapi.ExitCodeIntegerOverflow) + } + + m.insert(ifNextLargeTarget) + + var op operand + if dst64 { + m.lowerIconst(tmpGp2, 0x8000000000000000, true) + op = newOperandReg(tmpGp2) + } else { + op = newOperandImm32(0x80000000) + } + + add := m.allocateInstr() + add.asAluRmiR(aluRmiROpcodeAdd, op, tmpGp, dst64) + m.insert(add) + + m.insert(doneTarget) +} + +func (m *machine) lowerFcvtFromSint(rn, rd operand, src64, dst64 bool) { + var op sseOpcode + if dst64 { + op = sseOpcodeCvtsi2sd + } else { + op = sseOpcodeCvtsi2ss + } + + trunc := m.allocateInstr() + trunc.asGprToXmm(op, rn, rd.reg(), src64) + m.insert(trunc) +} + +func (m *machine) lowerFcvtFromUint(rn, rd operand, src64, dst64 bool) { + var op sseOpcode + if dst64 { + op = sseOpcodeCvtsi2sd + } else { + op = sseOpcodeCvtsi2ss + } + + // Src is 32 bit, then we just perform the conversion with 64 bit width. + // + // See the following link for why we use 64bit conversion for unsigned 32bit integer sources: + // https://stackoverflow.com/questions/41495498/fpu-operations-generated-by-gcc-during-casting-integer-to-float. + // + // Here's the summary: + // >> CVTSI2SS is indeed designed for converting a signed integer to a scalar single-precision float, + // >> not an unsigned integer like you have here. So what gives? Well, a 64-bit processor has 64-bit wide + // >> registers available, so the unsigned 32-bit input values can be stored as signed 64-bit intermediate values, + // >> which allows CVTSI2SS to be used after all. + // + if !src64 { + // Before we convert, we have to clear the higher 32-bits of the 64-bit register + // to get the correct result. + tmp := m.c.AllocateVReg(ssa.TypeI32) + m.insert(m.allocateInstr().asMovzxRmR(extModeLQ, rn, tmp)) + m.insert(m.allocateInstr().asGprToXmm(op, newOperandReg(tmp), rd.reg(), true)) + return + } + + // If uint64, we have to do a bit more work. + endTarget, end := m.allocateBrTarget() + + var tmpXmm regalloc.VReg + if dst64 { + tmpXmm = m.c.AllocateVReg(ssa.TypeF64) + } else { + tmpXmm = m.c.AllocateVReg(ssa.TypeF32) + } + + // Check if the most significant bit (sign bit) is set. + test := m.allocateInstr() + test.asCmpRmiR(false, rn, rn.reg(), src64) + m.insert(test) + + // Jump if the sign bit is set. + ifSignTarget, ifSign := m.allocateBrTarget() + jmpIfNeg := m.allocateInstr() + jmpIfNeg.asJmpIf(condS, newOperandLabel(ifSign)) + m.insert(jmpIfNeg) + + // If the sign bit is not set, we could fit the unsigned int into float32/float64. + // So, we convert it to float and emit jump instruction to exit from this branch. + cvt := m.allocateInstr() + cvt.asGprToXmm(op, rn, tmpXmm, src64) + m.insert(cvt) + + // We are done, jump to end. + jmpEnd := m.allocateInstr() + jmpEnd.asJmp(newOperandLabel(end)) + m.insert(jmpEnd) + + // Now handling the case where sign-bit is set. + // We emit the following sequences: + // mov %rn, %tmp + // shr 1, %tmp + // mov %rn, %tmp2 + // and 1, %tmp2 + // or %tmp2, %tmp + // cvtsi2ss %tmp, %xmm0 + // addsd %xmm0, %xmm0 + m.insert(ifSignTarget) + + tmp := m.copyToTmp(rn.reg()) + shr := m.allocateInstr() + shr.asShiftR(shiftROpShiftRightLogical, newOperandImm32(1), tmp, src64) + m.insert(shr) + + tmp2 := m.copyToTmp(rn.reg()) + and := m.allocateInstr() + and.asAluRmiR(aluRmiROpcodeAnd, newOperandImm32(1), tmp2, src64) + m.insert(and) + + or := m.allocateInstr() + or.asAluRmiR(aluRmiROpcodeOr, newOperandReg(tmp2), tmp, src64) + m.insert(or) + + cvt2 := m.allocateInstr() + cvt2.asGprToXmm(op, newOperandReg(tmp), tmpXmm, src64) + m.insert(cvt2) + + addsd := m.allocateInstr() + if dst64 { + addsd.asXmmRmR(sseOpcodeAddsd, newOperandReg(tmpXmm), tmpXmm) + } else { + addsd.asXmmRmR(sseOpcodeAddss, newOperandReg(tmpXmm), tmpXmm) + } + m.insert(addsd) + + m.insert(endTarget) + m.copyTo(tmpXmm, rd.reg()) +} + +func (m *machine) lowerVanyTrue(instr *ssa.Instruction) { + x := instr.Arg() + rm := m.getOperand_Reg(m.c.ValueDefinition(x)) + rd := m.c.VRegOf(instr.Return()) + + tmp := m.c.AllocateVReg(ssa.TypeI32) + + cmp := m.allocateInstr() + cmp.asXmmCmpRmR(sseOpcodePtest, rm, rm.reg()) + m.insert(cmp) + + setcc := m.allocateInstr() + setcc.asSetcc(condNZ, tmp) + m.insert(setcc) + + // Clear the irrelevant bits. + and := m.allocateInstr() + and.asAluRmiR(aluRmiROpcodeAnd, newOperandImm32(1), tmp, false) + m.insert(and) + + m.copyTo(tmp, rd) +} + +func (m *machine) lowerVallTrue(instr *ssa.Instruction) { + x, lane := instr.ArgWithLane() + var op sseOpcode + switch lane { + case ssa.VecLaneI8x16: + op = sseOpcodePcmpeqb + case ssa.VecLaneI16x8: + op = sseOpcodePcmpeqw + case ssa.VecLaneI32x4: + op = sseOpcodePcmpeqd + case ssa.VecLaneI64x2: + op = sseOpcodePcmpeqq + } + rm := m.getOperand_Reg(m.c.ValueDefinition(x)) + rd := m.c.VRegOf(instr.Return()) + + tmp := m.c.AllocateVReg(ssa.TypeV128) + + zeros := m.allocateInstr() + zeros.asZeros(tmp) + m.insert(zeros) + + pcmp := m.allocateInstr() + pcmp.asXmmRmR(op, rm, tmp) + m.insert(pcmp) + + test := m.allocateInstr() + test.asXmmCmpRmR(sseOpcodePtest, newOperandReg(tmp), tmp) + m.insert(test) + + tmp2 := m.c.AllocateVReg(ssa.TypeI32) + + setcc := m.allocateInstr() + setcc.asSetcc(condZ, tmp2) + m.insert(setcc) + + // Clear the irrelevant bits. + and := m.allocateInstr() + and.asAluRmiR(aluRmiROpcodeAnd, newOperandImm32(1), tmp2, false) + m.insert(and) + + m.copyTo(tmp2, rd) +} + +func (m *machine) lowerVhighBits(instr *ssa.Instruction) { + x, lane := instr.ArgWithLane() + rm := m.getOperand_Reg(m.c.ValueDefinition(x)) + rd := m.c.VRegOf(instr.Return()) + switch lane { + case ssa.VecLaneI8x16: + mov := m.allocateInstr() + mov.asXmmToGpr(sseOpcodePmovmskb, rm.reg(), rd, false) + m.insert(mov) + + case ssa.VecLaneI16x8: + // When we have: + // R1 = [R1(w1), R1(w2), R1(w3), R1(w4), R1(w5), R1(w6), R1(w7), R1(v8)] + // R2 = [R2(w1), R2(w2), R2(w3), R2(v4), R2(w5), R2(w6), R2(w7), R2(v8)] + // where RX(wn) is n-th signed word (16-bit) of RX register, + // + // "PACKSSWB R1, R2" produces + // R1 = [ + // byte_sat(R1(w1)), byte_sat(R1(w2)), byte_sat(R1(w3)), byte_sat(R1(w4)), + // byte_sat(R1(w5)), byte_sat(R1(w6)), byte_sat(R1(w7)), byte_sat(R1(w8)), + // byte_sat(R2(w1)), byte_sat(R2(w2)), byte_sat(R2(w3)), byte_sat(R2(w4)), + // byte_sat(R2(w5)), byte_sat(R2(w6)), byte_sat(R2(w7)), byte_sat(R2(w8)), + // ] + // where R1 is the destination register, and + // byte_sat(w) = int8(w) if w fits as signed 8-bit, + // 0x80 if w is less than 0x80 + // 0x7F if w is greater than 0x7f + // + // See https://www.felixcloutier.com/x86/packsswb:packssdw for detail. + // + // Therefore, v.register ends up having i-th and (i+8)-th bit set if i-th lane is negative (for i in 0..8). + tmp := m.copyToTmp(rm.reg()) + res := m.c.AllocateVReg(ssa.TypeI32) + + pak := m.allocateInstr() + pak.asXmmRmR(sseOpcodePacksswb, rm, tmp) + m.insert(pak) + + mov := m.allocateInstr() + mov.asXmmToGpr(sseOpcodePmovmskb, tmp, res, false) + m.insert(mov) + + // Clear the higher bits than 8. + shr := m.allocateInstr() + shr.asShiftR(shiftROpShiftRightLogical, newOperandImm32(8), res, false) + m.insert(shr) + + m.copyTo(res, rd) + + case ssa.VecLaneI32x4: + mov := m.allocateInstr() + mov.asXmmToGpr(sseOpcodeMovmskps, rm.reg(), rd, true) + m.insert(mov) + + case ssa.VecLaneI64x2: + mov := m.allocateInstr() + mov.asXmmToGpr(sseOpcodeMovmskpd, rm.reg(), rd, true) + m.insert(mov) + } +} + +func (m *machine) lowerVbnot(instr *ssa.Instruction) { + x := instr.Arg() + xDef := m.c.ValueDefinition(x) + rm := m.getOperand_Reg(xDef) + rd := m.c.VRegOf(instr.Return()) + + tmp := m.copyToTmp(rm.reg()) + tmp2 := m.c.AllocateVReg(ssa.TypeV128) + + // Ensure tmp2 is considered defined by regalloc. + m.insert(m.allocateInstr().asDefineUninitializedReg(tmp2)) + + // Set all bits on tmp register. + pak := m.allocateInstr() + pak.asXmmRmR(sseOpcodePcmpeqd, newOperandReg(tmp2), tmp2) + m.insert(pak) + + // Then XOR with tmp to reverse all bits on v.register. + xor := m.allocateInstr() + xor.asXmmRmR(sseOpcodePxor, newOperandReg(tmp2), tmp) + m.insert(xor) + + m.copyTo(tmp, rd) +} + +func (m *machine) lowerSplat(x, ret ssa.Value, lane ssa.VecLane) { + tmpDst := m.c.AllocateVReg(ssa.TypeV128) + m.insert(m.allocateInstr().asDefineUninitializedReg(tmpDst)) + + switch lane { + case ssa.VecLaneI8x16: + tmp := m.c.AllocateVReg(ssa.TypeV128) + m.insert(m.allocateInstr().asDefineUninitializedReg(tmp)) + xx := m.getOperand_Mem_Reg(m.c.ValueDefinition(x)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePinsrb, 0, xx, tmpDst)) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePxor, newOperandReg(tmp), tmp)) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePshufb, newOperandReg(tmp), tmpDst)) + case ssa.VecLaneI16x8: + xx := m.getOperand_Reg(m.c.ValueDefinition(x)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePinsrw, 0, xx, tmpDst)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePinsrw, 1, xx, tmpDst)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePshufd, 0, newOperandReg(tmpDst), tmpDst)) + case ssa.VecLaneI32x4: + xx := m.getOperand_Mem_Reg(m.c.ValueDefinition(x)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePinsrd, 0, xx, tmpDst)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePshufd, 0, newOperandReg(tmpDst), tmpDst)) + case ssa.VecLaneI64x2: + xx := m.getOperand_Reg(m.c.ValueDefinition(x)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePinsrq, 0, xx, tmpDst)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePinsrq, 1, xx, tmpDst)) + case ssa.VecLaneF32x4: + xx := m.getOperand_Mem_Reg(m.c.ValueDefinition(x)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodeInsertps, 0, xx, tmpDst)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePshufd, 0, newOperandReg(tmpDst), tmpDst)) + case ssa.VecLaneF64x2: + xx := m.getOperand_Reg(m.c.ValueDefinition(x)) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovsd, xx, tmpDst)) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodeMovlhps, xx, tmpDst)) + default: + panic(fmt.Sprintf("invalid lane type: %s", lane)) + } + + m.copyTo(tmpDst, m.c.VRegOf(ret)) +} + +func (m *machine) lowerShuffle(x, y ssa.Value, lo, hi uint64, ret ssa.Value) { + var xMask, yMask [2]uint64 + for i := 0; i < 8; i++ { + loLane := byte(lo >> (i * 8)) + if loLane < 16 { + xMask[0] |= uint64(loLane) << (i * 8) + yMask[0] |= uint64(0x80) << (i * 8) + } else { + xMask[0] |= uint64(0x80) << (i * 8) + yMask[0] |= uint64(loLane-16) << (i * 8) + } + hiLane := byte(hi >> (i * 8)) + if hiLane < 16 { + xMask[1] |= uint64(hiLane) << (i * 8) + yMask[1] |= uint64(0x80) << (i * 8) + } else { + xMask[1] |= uint64(0x80) << (i * 8) + yMask[1] |= uint64(hiLane-16) << (i * 8) + } + } + + xl, xmaskPos := m.allocateLabel() + m.consts = append(m.consts, _const{lo: xMask[0], hi: xMask[1], label: xl, labelPos: xmaskPos}) + yl, ymaskPos := m.allocateLabel() + m.consts = append(m.consts, _const{lo: yMask[0], hi: yMask[1], label: yl, labelPos: ymaskPos}) + + xx, yy := m.getOperand_Reg(m.c.ValueDefinition(x)), m.getOperand_Reg(m.c.ValueDefinition(y)) + tmpX, tmpY := m.copyToTmp(xx.reg()), m.copyToTmp(yy.reg()) + + // Apply mask to X. + tmp := m.c.AllocateVReg(ssa.TypeV128) + loadMaskLo := m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(m.newAmodeRipRel(xl)), tmp) + m.insert(loadMaskLo) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePshufb, newOperandReg(tmp), tmpX)) + + // Apply mask to Y. + loadMaskHi := m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(m.newAmodeRipRel(yl)), tmp) + m.insert(loadMaskHi) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePshufb, newOperandReg(tmp), tmpY)) + + // Combine the results. + m.insert(m.allocateInstr().asXmmRmR(sseOpcodeOrps, newOperandReg(tmpX), tmpY)) + + m.copyTo(tmpY, m.c.VRegOf(ret)) +} + +func (m *machine) lowerVbBinOpUnaligned(op sseOpcode, x, y, ret ssa.Value) { + rn := m.getOperand_Reg(m.c.ValueDefinition(x)) + rm := m.getOperand_Reg(m.c.ValueDefinition(y)) + rd := m.c.VRegOf(ret) + + tmp := m.copyToTmp(rn.reg()) + + binOp := m.allocateInstr() + binOp.asXmmRmR(op, rm, tmp) + m.insert(binOp) + + m.copyTo(tmp, rd) +} + +func (m *machine) lowerVbBinOp(op sseOpcode, x, y, ret ssa.Value) { + rn := m.getOperand_Reg(m.c.ValueDefinition(x)) + rm := m.getOperand_Mem_Reg(m.c.ValueDefinition(y)) + rd := m.c.VRegOf(ret) + + tmp := m.copyToTmp(rn.reg()) + + binOp := m.allocateInstr() + binOp.asXmmRmR(op, rm, tmp) + m.insert(binOp) + + m.copyTo(tmp, rd) +} + +func (m *machine) lowerVFcmp(x, y ssa.Value, c ssa.FloatCmpCond, ret ssa.Value, lane ssa.VecLane) { + var cmpOp sseOpcode + switch lane { + case ssa.VecLaneF32x4: + cmpOp = sseOpcodeCmpps + case ssa.VecLaneF64x2: + cmpOp = sseOpcodeCmppd + default: + panic(fmt.Sprintf("invalid lane type: %s", lane)) + } + + xx, yy := m.c.ValueDefinition(x), m.c.ValueDefinition(y) + var cmpImm cmpPred + switch c { + case ssa.FloatCmpCondGreaterThan: + yy, xx = xx, yy + cmpImm = cmpPredLT_OS + case ssa.FloatCmpCondGreaterThanOrEqual: + yy, xx = xx, yy + cmpImm = cmpPredLE_OS + case ssa.FloatCmpCondEqual: + cmpImm = cmpPredEQ_OQ + case ssa.FloatCmpCondNotEqual: + cmpImm = cmpPredNEQ_UQ + case ssa.FloatCmpCondLessThan: + cmpImm = cmpPredLT_OS + case ssa.FloatCmpCondLessThanOrEqual: + cmpImm = cmpPredLE_OS + default: + panic(fmt.Sprintf("invalid float comparison condition: %s", c)) + } + + tmp := m.c.AllocateVReg(ssa.TypeV128) + xxx := m.getOperand_Mem_Reg(xx) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, xxx, tmp)) + + rm := m.getOperand_Mem_Reg(yy) + m.insert(m.allocateInstr().asXmmRmRImm(cmpOp, byte(cmpImm), rm, tmp)) + + m.copyTo(tmp, m.c.VRegOf(ret)) +} + +func (m *machine) lowerVIcmp(x, y ssa.Value, c ssa.IntegerCmpCond, ret ssa.Value, lane ssa.VecLane) { + var eq, gt, maxu, minu, mins sseOpcode + switch lane { + case ssa.VecLaneI8x16: + eq, gt, maxu, minu, mins = sseOpcodePcmpeqb, sseOpcodePcmpgtb, sseOpcodePmaxub, sseOpcodePminub, sseOpcodePminsb + case ssa.VecLaneI16x8: + eq, gt, maxu, minu, mins = sseOpcodePcmpeqw, sseOpcodePcmpgtw, sseOpcodePmaxuw, sseOpcodePminuw, sseOpcodePminsw + case ssa.VecLaneI32x4: + eq, gt, maxu, minu, mins = sseOpcodePcmpeqd, sseOpcodePcmpgtd, sseOpcodePmaxud, sseOpcodePminud, sseOpcodePminsd + case ssa.VecLaneI64x2: + eq, gt = sseOpcodePcmpeqq, sseOpcodePcmpgtq + default: + panic(fmt.Sprintf("invalid lane type: %s", lane)) + } + + tmp := m.c.AllocateVReg(ssa.TypeV128) + var op operand + switch c { + case ssa.IntegerCmpCondSignedLessThanOrEqual: + if lane == ssa.VecLaneI64x2 { + x := m.getOperand_Mem_Reg(m.c.ValueDefinition(x)) + // Copy x to tmp. + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, x, tmp)) + op = m.getOperand_Mem_Reg(m.c.ValueDefinition(y)) + } else { + y := m.getOperand_Mem_Reg(m.c.ValueDefinition(y)) + // Copy y to tmp. + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, y, tmp)) + op = m.getOperand_Mem_Reg(m.c.ValueDefinition(x)) + } + case ssa.IntegerCmpCondSignedGreaterThanOrEqual: + if lane == ssa.VecLaneI64x2 { + y := m.getOperand_Mem_Reg(m.c.ValueDefinition(y)) + // Copy y to tmp. + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, y, tmp)) + op = m.getOperand_Mem_Reg(m.c.ValueDefinition(x)) + } else { + x := m.getOperand_Mem_Reg(m.c.ValueDefinition(x)) + // Copy x to tmp. + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, x, tmp)) + op = m.getOperand_Mem_Reg(m.c.ValueDefinition(y)) + } + case ssa.IntegerCmpCondSignedLessThan, ssa.IntegerCmpCondUnsignedLessThan, ssa.IntegerCmpCondUnsignedLessThanOrEqual: + y := m.getOperand_Mem_Reg(m.c.ValueDefinition(y)) + // Copy y to tmp. + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, y, tmp)) + op = m.getOperand_Mem_Reg(m.c.ValueDefinition(x)) + default: + x := m.getOperand_Mem_Reg(m.c.ValueDefinition(x)) + // Copy x to tmp. + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, x, tmp)) + op = m.getOperand_Mem_Reg(m.c.ValueDefinition(y)) + } + + switch c { + case ssa.IntegerCmpCondEqual: + m.insert(m.allocateInstr().asXmmRmR(eq, op, tmp)) + case ssa.IntegerCmpCondNotEqual: + // First we compare for equality. + m.insert(m.allocateInstr().asXmmRmR(eq, op, tmp)) + // Then flip the bits. To do so, we set all bits on tmp2. + tmp2 := m.c.AllocateVReg(ssa.TypeV128) + m.insert(m.allocateInstr().asDefineUninitializedReg(tmp2)) + m.insert(m.allocateInstr().asXmmRmR(eq, newOperandReg(tmp2), tmp2)) + // And then xor with tmp. + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePxor, newOperandReg(tmp2), tmp)) + case ssa.IntegerCmpCondSignedGreaterThan, ssa.IntegerCmpCondSignedLessThan: + m.insert(m.allocateInstr().asXmmRmR(gt, op, tmp)) + case ssa.IntegerCmpCondSignedGreaterThanOrEqual, ssa.IntegerCmpCondSignedLessThanOrEqual: + if lane == ssa.VecLaneI64x2 { + m.insert(m.allocateInstr().asXmmRmR(gt, op, tmp)) + // Then flip the bits. To do so, we set all bits on tmp2. + tmp2 := m.c.AllocateVReg(ssa.TypeV128) + m.insert(m.allocateInstr().asDefineUninitializedReg(tmp2)) + m.insert(m.allocateInstr().asXmmRmR(eq, newOperandReg(tmp2), tmp2)) + // And then xor with tmp. + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePxor, newOperandReg(tmp2), tmp)) + } else { + // First take min of x and y. + m.insert(m.allocateInstr().asXmmRmR(mins, op, tmp)) + // Then compare for equality. + m.insert(m.allocateInstr().asXmmRmR(eq, op, tmp)) + } + case ssa.IntegerCmpCondUnsignedGreaterThan, ssa.IntegerCmpCondUnsignedLessThan: + // First maxu of x and y. + m.insert(m.allocateInstr().asXmmRmR(maxu, op, tmp)) + // Then compare for equality. + m.insert(m.allocateInstr().asXmmRmR(eq, op, tmp)) + // Then flip the bits. To do so, we set all bits on tmp2. + tmp2 := m.c.AllocateVReg(ssa.TypeV128) + m.insert(m.allocateInstr().asDefineUninitializedReg(tmp2)) + m.insert(m.allocateInstr().asXmmRmR(eq, newOperandReg(tmp2), tmp2)) + // And then xor with tmp. + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePxor, newOperandReg(tmp2), tmp)) + case ssa.IntegerCmpCondUnsignedGreaterThanOrEqual, ssa.IntegerCmpCondUnsignedLessThanOrEqual: + m.insert(m.allocateInstr().asXmmRmR(minu, op, tmp)) + m.insert(m.allocateInstr().asXmmRmR(eq, op, tmp)) + default: + panic("BUG") + } + + m.copyTo(tmp, m.c.VRegOf(ret)) +} + +func (m *machine) lowerVbandnot(instr *ssa.Instruction, op sseOpcode) { + x, y := instr.Arg2() + xDef := m.c.ValueDefinition(x) + yDef := m.c.ValueDefinition(y) + rm, rn := m.getOperand_Reg(xDef), m.getOperand_Reg(yDef) + rd := m.c.VRegOf(instr.Return()) + + tmp := m.copyToTmp(rn.reg()) + + // pandn between rn, rm. + pand := m.allocateInstr() + pand.asXmmRmR(sseOpcodePandn, rm, tmp) + m.insert(pand) + + m.copyTo(tmp, rd) +} + +func (m *machine) lowerVbitselect(instr *ssa.Instruction) { + c, x, y := instr.SelectData() + xDef := m.c.ValueDefinition(x) + yDef := m.c.ValueDefinition(y) + rm, rn := m.getOperand_Reg(xDef), m.getOperand_Reg(yDef) + creg := m.getOperand_Reg(m.c.ValueDefinition(c)) + rd := m.c.VRegOf(instr.Return()) + + tmpC := m.copyToTmp(creg.reg()) + tmpX := m.copyToTmp(rm.reg()) + + // And between c, x (overwrites x). + pand := m.allocateInstr() + pand.asXmmRmR(sseOpcodePand, creg, tmpX) + m.insert(pand) + + // Andn between y, c (overwrites c). + pandn := m.allocateInstr() + pandn.asXmmRmR(sseOpcodePandn, rn, tmpC) + m.insert(pandn) + + por := m.allocateInstr() + por.asXmmRmR(sseOpcodePor, newOperandReg(tmpC), tmpX) + m.insert(por) + + m.copyTo(tmpX, rd) +} + +func (m *machine) lowerVFmin(instr *ssa.Instruction) { + x, y, lane := instr.Arg2WithLane() + rn := m.getOperand_Reg(m.c.ValueDefinition(x)) + rm := m.getOperand_Reg(m.c.ValueDefinition(y)) + rd := m.c.VRegOf(instr.Return()) + + var min, cmp, andn, or, srl /* shift right logical */ sseOpcode + var shiftNumToInverseNaN uint32 + if lane == ssa.VecLaneF32x4 { + min, cmp, andn, or, srl, shiftNumToInverseNaN = sseOpcodeMinps, sseOpcodeCmpps, sseOpcodeAndnps, sseOpcodeOrps, sseOpcodePsrld, 0xa + } else { + min, cmp, andn, or, srl, shiftNumToInverseNaN = sseOpcodeMinpd, sseOpcodeCmppd, sseOpcodeAndnpd, sseOpcodeOrpd, sseOpcodePsrlq, 0xd + } + + tmp1 := m.copyToTmp(rn.reg()) + tmp2 := m.copyToTmp(rm.reg()) + + // tmp1=min(rn, rm) + minIns1 := m.allocateInstr() + minIns1.asXmmRmR(min, rn, tmp2) + m.insert(minIns1) + + // tmp2=min(rm, rn) + minIns2 := m.allocateInstr() + minIns2.asXmmRmR(min, rm, tmp1) + m.insert(minIns2) + + // tmp3:=tmp1=min(rn, rm) + tmp3 := m.copyToTmp(tmp1) + + // tmp1 = -0 if (rn == -0 || rm == -0) && rn != NaN && rm !=NaN + // NaN if rn == NaN || rm == NaN + // min(rm, rm) otherwise + orIns := m.allocateInstr() + orIns.asXmmRmR(or, newOperandReg(tmp2), tmp1) + m.insert(orIns) + + // tmp3 is originally min(rn,rm). + // tmp3 = 0^ (set all bits) if rn == NaN || rm == NaN + // 0 otherwise + cmpIns := m.allocateInstr() + cmpIns.asXmmRmRImm(cmp, uint8(cmpPredUNORD_Q), newOperandReg(tmp2), tmp3) + m.insert(cmpIns) + + // tmp1 = -0 if (rn == -0 || rm == -0) && rn != NaN && rm !=NaN + // ^0 if rn == NaN || rm == NaN + // min(v1, v2) otherwise + orIns2 := m.allocateInstr() + orIns2.asXmmRmR(or, newOperandReg(tmp3), tmp1) + m.insert(orIns2) + + // tmp3 = set all bits on the mantissa bits + // 0 otherwise + shift := m.allocateInstr() + shift.asXmmRmiReg(srl, newOperandImm32(shiftNumToInverseNaN), tmp3) + m.insert(shift) + + // tmp3 = tmp1 and !tmp3 + // = -0 if (rn == -0 || rm == -0) && rn != NaN && rm !=NaN + // set all bits on exponential and sign bit (== NaN) if rn == NaN || rm == NaN + // min(rn, rm) otherwise + andnIns := m.allocateInstr() + andnIns.asXmmRmR(andn, newOperandReg(tmp1), tmp3) + m.insert(andnIns) + + m.copyTo(tmp3, rd) +} + +func (m *machine) lowerVFmax(instr *ssa.Instruction) { + x, y, lane := instr.Arg2WithLane() + rn := m.getOperand_Reg(m.c.ValueDefinition(x)) + rm := m.getOperand_Reg(m.c.ValueDefinition(y)) + rd := m.c.VRegOf(instr.Return()) + + var max, cmp, andn, or, xor, sub, srl /* shift right logical */ sseOpcode + var shiftNumToInverseNaN uint32 + if lane == ssa.VecLaneF32x4 { + max, cmp, andn, or, xor, sub, srl, shiftNumToInverseNaN = sseOpcodeMaxps, sseOpcodeCmpps, sseOpcodeAndnps, sseOpcodeOrps, sseOpcodeXorps, sseOpcodeSubps, sseOpcodePsrld, 0xa + } else { + max, cmp, andn, or, xor, sub, srl, shiftNumToInverseNaN = sseOpcodeMaxpd, sseOpcodeCmppd, sseOpcodeAndnpd, sseOpcodeOrpd, sseOpcodeXorpd, sseOpcodeSubpd, sseOpcodePsrlq, 0xd + } + + tmp0 := m.copyToTmp(rm.reg()) + tmp1 := m.copyToTmp(rn.reg()) + + // tmp0=max(rn, rm) + maxIns1 := m.allocateInstr() + maxIns1.asXmmRmR(max, rn, tmp0) + m.insert(maxIns1) + + // tmp1=max(rm, rn) + maxIns2 := m.allocateInstr() + maxIns2.asXmmRmR(max, rm, tmp1) + m.insert(maxIns2) + + // tmp2=max(rm, rn) + tmp2 := m.copyToTmp(tmp1) + + // tmp2 = -0 if (rn == -0 && rm == 0) || (rn == 0 && rm == -0) + // 0 if (rn == 0 && rm == 0) + // -0 if (rn == -0 && rm == -0) + // v1^v2 if rn == NaN || rm == NaN + // 0 otherwise + xorInstr := m.allocateInstr() + xorInstr.asXmmRmR(xor, newOperandReg(tmp0), tmp2) + m.insert(xorInstr) + // tmp1 = -0 if (rn == -0 && rm == 0) || (rn == 0 && rm == -0) + // 0 if (rn == 0 && rm == 0) + // -0 if (rn == -0 && rm == -0) + // NaN if rn == NaN || rm == NaN + // max(v1, v2) otherwise + orInstr := m.allocateInstr() + orInstr.asXmmRmR(or, newOperandReg(tmp2), tmp1) + m.insert(orInstr) + + tmp3 := m.copyToTmp(tmp1) + + // tmp3 = 0 if (rn == -0 && rm == 0) || (rn == 0 && rm == -0) || (rn == 0 && rm == 0) + // -0 if (rn == -0 && rm == -0) + // NaN if rn == NaN || rm == NaN + // max(v1, v2) otherwise + // + // Note: -0 - (-0) = 0 (!= -0) in floating point operation. + subIns := m.allocateInstr() + subIns.asXmmRmR(sub, newOperandReg(tmp2), tmp3) + m.insert(subIns) + + // tmp1 = 0^ if rn == NaN || rm == NaN + cmpIns := m.allocateInstr() + cmpIns.asXmmRmRImm(cmp, uint8(cmpPredUNORD_Q), newOperandReg(tmp1), tmp1) + m.insert(cmpIns) + + // tmp1 = set all bits on the mantissa bits + // 0 otherwise + shift := m.allocateInstr() + shift.asXmmRmiReg(srl, newOperandImm32(shiftNumToInverseNaN), tmp1) + m.insert(shift) + + andnIns := m.allocateInstr() + andnIns.asXmmRmR(andn, newOperandReg(tmp3), tmp1) + m.insert(andnIns) + + m.copyTo(tmp1, rd) +} + +func (m *machine) lowerVFabs(instr *ssa.Instruction) { + x, lane := instr.ArgWithLane() + rm := m.getOperand_Mem_Reg(m.c.ValueDefinition(x)) + rd := m.c.VRegOf(instr.Return()) + + tmp := m.c.AllocateVReg(ssa.TypeV128) + + def := m.allocateInstr() + def.asDefineUninitializedReg(tmp) + m.insert(def) + + // Set all bits on tmp. + pcmp := m.allocateInstr() + pcmp.asXmmRmR(sseOpcodePcmpeqd, newOperandReg(tmp), tmp) + m.insert(pcmp) + + switch lane { + case ssa.VecLaneF32x4: + // Shift right packed single floats by 1 to clear the sign bits. + shift := m.allocateInstr() + shift.asXmmRmiReg(sseOpcodePsrld, newOperandImm32(1), tmp) + m.insert(shift) + // Clear the sign bit of rm. + andp := m.allocateInstr() + andp.asXmmRmR(sseOpcodeAndpd, rm, tmp) + m.insert(andp) + case ssa.VecLaneF64x2: + // Shift right packed single floats by 1 to clear the sign bits. + shift := m.allocateInstr() + shift.asXmmRmiReg(sseOpcodePsrlq, newOperandImm32(1), tmp) + m.insert(shift) + // Clear the sign bit of rm. + andp := m.allocateInstr() + andp.asXmmRmR(sseOpcodeAndps, rm, tmp) + m.insert(andp) + } + + m.copyTo(tmp, rd) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine_pro_epi_logue.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine_pro_epi_logue.go new file mode 100644 index 00000000000..fa3ca58a6a3 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine_pro_epi_logue.go @@ -0,0 +1,334 @@ +package amd64 + +import ( + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" +) + +// PostRegAlloc implements backend.Machine. +func (m *machine) PostRegAlloc() { + m.setupPrologue() + m.postRegAlloc() +} + +func (m *machine) setupPrologue() { + cur := m.rootInstr + prevInitInst := cur.next + + // At this point, we have the stack layout as follows: + // + // (high address) + // +-----------------+ <----- RBP (somewhere in the middle of the stack) + // | ....... | + // | ret Y | + // | ....... | + // | ret 0 | + // | arg X | + // | ....... | + // | arg 1 | + // | arg 0 | + // | Return Addr | + // RSP ----> +-----------------+ + // (low address) + + // First, we push the RBP, and update the RBP to the current RSP. + // + // (high address) (high address) + // RBP ----> +-----------------+ +-----------------+ + // | ....... | | ....... | + // | ret Y | | ret Y | + // | ....... | | ....... | + // | ret 0 | | ret 0 | + // | arg X | | arg X | + // | ....... | ====> | ....... | + // | arg 1 | | arg 1 | + // | arg 0 | | arg 0 | + // | Return Addr | | Return Addr | + // RSP ----> +-----------------+ | Caller_RBP | + // (low address) +-----------------+ <----- RSP, RBP + // + cur = m.setupRBPRSP(cur) + + if !m.stackBoundsCheckDisabled { + cur = m.insertStackBoundsCheck(m.requiredStackSize(), cur) + } + + // + // (high address) + // +-----------------+ +-----------------+ + // | ....... | | ....... | + // | ret Y | | ret Y | + // | ....... | | ....... | + // | ret 0 | | ret 0 | + // | arg X | | arg X | + // | ....... | | ....... | + // | arg 1 | | arg 1 | + // | arg 0 | | arg 0 | + // | xxxxx | | xxxxx | + // | Return Addr | | Return Addr | + // | Caller_RBP | ====> | Caller_RBP | + // RBP,RSP->+-----------------+ +-----------------+ <----- RBP + // (low address) | clobbered M | + // | clobbered 1 | + // | ........... | + // | clobbered 0 | + // +-----------------+ <----- RSP + // + if regs := m.clobberedRegs; len(regs) > 0 { + for i := range regs { + r := regs[len(regs)-1-i] // Reverse order. + if r.RegType() == regalloc.RegTypeInt { + cur = linkInstr(cur, m.allocateInstr().asPush64(newOperandReg(r))) + } else { + // Push the XMM register is not supported by the PUSH instruction. + cur = m.addRSP(-16, cur) + push := m.allocateInstr().asXmmMovRM( + sseOpcodeMovdqu, r, newOperandMem(m.newAmodeImmReg(0, rspVReg)), + ) + cur = linkInstr(cur, push) + } + } + } + + if size := m.spillSlotSize; size > 0 { + // Simply decrease the RSP to allocate the spill slots. + // sub $size, %rsp + cur = linkInstr(cur, m.allocateInstr().asAluRmiR(aluRmiROpcodeSub, newOperandImm32(uint32(size)), rspVReg, true)) + + // At this point, we have the stack layout as follows: + // + // (high address) + // +-----------------+ + // | ....... | + // | ret Y | + // | ....... | + // | ret 0 | + // | arg X | + // | ....... | + // | arg 1 | + // | arg 0 | + // | ReturnAddress | + // | Caller_RBP | + // +-----------------+ <--- RBP + // | clobbered M | + // | ............ | + // | clobbered 1 | + // | clobbered 0 | + // | spill slot N | + // | ............ | + // | spill slot 0 | + // +-----------------+ <--- RSP + // (low address) + } + + linkInstr(cur, prevInitInst) +} + +// postRegAlloc does multiple things while walking through the instructions: +// 1. Inserts the epilogue code. +// 2. Removes the redundant copy instruction. +// 3. Inserts the dec/inc RSP instruction right before/after the call instruction. +// 4. Lowering that is supposed to be done after regalloc. +func (m *machine) postRegAlloc() { + for cur := m.rootInstr; cur != nil; cur = cur.next { + switch k := cur.kind; k { + case ret: + m.setupEpilogueAfter(cur.prev) + continue + case fcvtToSintSequence, fcvtToUintSequence: + m.pendingInstructions = m.pendingInstructions[:0] + if k == fcvtToSintSequence { + m.lowerFcvtToSintSequenceAfterRegalloc(cur) + } else { + m.lowerFcvtToUintSequenceAfterRegalloc(cur) + } + prev := cur.prev + next := cur.next + cur := prev + for _, instr := range m.pendingInstructions { + cur = linkInstr(cur, instr) + } + linkInstr(cur, next) + continue + case xmmCMov: + m.pendingInstructions = m.pendingInstructions[:0] + m.lowerXmmCmovAfterRegAlloc(cur) + prev := cur.prev + next := cur.next + cur := prev + for _, instr := range m.pendingInstructions { + cur = linkInstr(cur, instr) + } + linkInstr(cur, next) + continue + case idivRemSequence: + m.pendingInstructions = m.pendingInstructions[:0] + m.lowerIDivRemSequenceAfterRegAlloc(cur) + prev := cur.prev + next := cur.next + cur := prev + for _, instr := range m.pendingInstructions { + cur = linkInstr(cur, instr) + } + linkInstr(cur, next) + continue + case call, callIndirect: + // At this point, reg alloc is done, therefore we can safely insert dec/inc RPS instruction + // right before/after the call instruction. If this is done before reg alloc, the stack slot + // can point to the wrong location and therefore results in a wrong value. + call := cur + next := call.next + _, _, _, _, size := backend.ABIInfoFromUint64(call.u2) + if size > 0 { + dec := m.allocateInstr().asAluRmiR(aluRmiROpcodeSub, newOperandImm32(size), rspVReg, true) + linkInstr(call.prev, dec) + linkInstr(dec, call) + inc := m.allocateInstr().asAluRmiR(aluRmiROpcodeAdd, newOperandImm32(size), rspVReg, true) + linkInstr(call, inc) + linkInstr(inc, next) + } + continue + case tailCall, tailCallIndirect: + // At this point, reg alloc is done, therefore we can safely insert dec RPS instruction + // right before the tail call (jump) instruction. If this is done before reg alloc, the stack slot + // can point to the wrong location and therefore results in a wrong value. + tailCall := cur + _, _, _, _, size := backend.ABIInfoFromUint64(tailCall.u2) + if size > 0 { + dec := m.allocateInstr().asAluRmiR(aluRmiROpcodeSub, newOperandImm32(size), rspVReg, true) + linkInstr(tailCall.prev, dec) + linkInstr(dec, tailCall) + } + // In a tail call, we insert the epilogue before the jump instruction. + m.setupEpilogueAfter(tailCall.prev) + // If this has been encoded as a proper tail call, we can remove the trailing instructions + // For details, see internal/engine/RATIONALE.md + m.removeUntilRet(cur.next) + continue + } + + // Removes the redundant copy instruction. + if cur.IsCopy() && cur.op1.reg().RealReg() == cur.op2.reg().RealReg() { + prev, next := cur.prev, cur.next + // Remove the copy instruction. + prev.next = next + if next != nil { + next.prev = prev + } + } + } +} + +func (m *machine) setupEpilogueAfter(cur *instruction) { + prevNext := cur.next + + // At this point, we have the stack layout as follows: + // + // (high address) + // +-----------------+ + // | ....... | + // | ret Y | + // | ....... | + // | ret 0 | + // | arg X | + // | ....... | + // | arg 1 | + // | arg 0 | + // | ReturnAddress | + // | Caller_RBP | + // +-----------------+ <--- RBP + // | clobbered M | + // | ............ | + // | clobbered 1 | + // | clobbered 0 | + // | spill slot N | + // | ............ | + // | spill slot 0 | + // +-----------------+ <--- RSP + // (low address) + + if size := m.spillSlotSize; size > 0 { + // Simply increase the RSP to free the spill slots. + // add $size, %rsp + cur = linkInstr(cur, m.allocateInstr().asAluRmiR(aluRmiROpcodeAdd, newOperandImm32(uint32(size)), rspVReg, true)) + } + + // + // (high address) + // +-----------------+ +-----------------+ + // | ....... | | ....... | + // | ret Y | | ret Y | + // | ....... | | ....... | + // | ret 0 | | ret 0 | + // | arg X | | arg X | + // | ....... | | ....... | + // | arg 1 | | arg 1 | + // | arg 0 | | arg 0 | + // | ReturnAddress | | ReturnAddress | + // | Caller_RBP | | Caller_RBP | + // RBP ---> +-----------------+ ========> +-----------------+ <---- RSP, RBP + // | clobbered M | + // | ............ | + // | clobbered 1 | + // | clobbered 0 | + // RSP ---> +-----------------+ + // (low address) + // + if regs := m.clobberedRegs; len(regs) > 0 { + for _, r := range regs { + if r.RegType() == regalloc.RegTypeInt { + cur = linkInstr(cur, m.allocateInstr().asPop64(r)) + } else { + // Pop the XMM register is not supported by the POP instruction. + pop := m.allocateInstr().asXmmUnaryRmR( + sseOpcodeMovdqu, newOperandMem(m.newAmodeImmReg(0, rspVReg)), r, + ) + cur = linkInstr(cur, pop) + cur = m.addRSP(16, cur) + } + } + } + + // Now roll back the RSP to RBP, and pop the caller's RBP. + cur = m.revertRBPRSP(cur) + + linkInstr(cur, prevNext) +} + +// removeUntilRet removes the instructions starting from `cur` until the first `ret` instruction. +func (m *machine) removeUntilRet(cur *instruction) { + for ; cur != nil; cur = cur.next { + prev, next := cur.prev, cur.next + prev.next = next + if next != nil { + next.prev = prev + } + if cur.kind == ret { + return + } + } +} + +func (m *machine) addRSP(offset int32, cur *instruction) *instruction { + if offset == 0 { + return cur + } + opcode := aluRmiROpcodeAdd + if offset < 0 { + opcode = aluRmiROpcodeSub + offset = -offset + } + return linkInstr(cur, m.allocateInstr().asAluRmiR(opcode, newOperandImm32(uint32(offset)), rspVReg, true)) +} + +func (m *machine) setupRBPRSP(cur *instruction) *instruction { + cur = linkInstr(cur, m.allocateInstr().asPush64(newOperandReg(rbpVReg))) + cur = linkInstr(cur, m.allocateInstr().asMovRR(rspVReg, rbpVReg, true)) + return cur +} + +func (m *machine) revertRBPRSP(cur *instruction) *instruction { + cur = linkInstr(cur, m.allocateInstr().asMovRR(rbpVReg, rspVReg, true)) + cur = linkInstr(cur, m.allocateInstr().asPop64(rbpVReg)) + return cur +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go new file mode 100644 index 00000000000..de9dcc9444f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go @@ -0,0 +1,352 @@ +package amd64 + +import ( + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" +) + +// regAllocFn implements regalloc.Function. +type regAllocFn struct { + ssaB ssa.Builder + m *machine + loopNestingForestRoots []ssa.BasicBlock + blockIter int +} + +// PostOrderBlockIteratorBegin implements regalloc.Function. +func (f *regAllocFn) PostOrderBlockIteratorBegin() *labelPosition { + f.blockIter = len(f.m.orderedSSABlockLabelPos) - 1 + return f.PostOrderBlockIteratorNext() +} + +// PostOrderBlockIteratorNext implements regalloc.Function. +func (f *regAllocFn) PostOrderBlockIteratorNext() *labelPosition { + if f.blockIter < 0 { + return nil + } + b := f.m.orderedSSABlockLabelPos[f.blockIter] + f.blockIter-- + return b +} + +// ReversePostOrderBlockIteratorBegin implements regalloc.Function. +func (f *regAllocFn) ReversePostOrderBlockIteratorBegin() *labelPosition { + f.blockIter = 0 + return f.ReversePostOrderBlockIteratorNext() +} + +// ReversePostOrderBlockIteratorNext implements regalloc.Function. +func (f *regAllocFn) ReversePostOrderBlockIteratorNext() *labelPosition { + if f.blockIter >= len(f.m.orderedSSABlockLabelPos) { + return nil + } + b := f.m.orderedSSABlockLabelPos[f.blockIter] + f.blockIter++ + return b +} + +// ClobberedRegisters implements regalloc.Function. +func (f *regAllocFn) ClobberedRegisters(regs []regalloc.VReg) { + f.m.clobberedRegs = append(f.m.clobberedRegs[:0], regs...) +} + +// LoopNestingForestRoots implements regalloc.Function. +func (f *regAllocFn) LoopNestingForestRoots() int { + f.loopNestingForestRoots = f.ssaB.LoopNestingForestRoots() + return len(f.loopNestingForestRoots) +} + +// LoopNestingForestRoot implements regalloc.Function. +func (f *regAllocFn) LoopNestingForestRoot(i int) *labelPosition { + root := f.loopNestingForestRoots[i] + pos := f.m.getOrAllocateSSABlockLabelPosition(root) + return pos +} + +// LowestCommonAncestor implements regalloc.Function. +func (f *regAllocFn) LowestCommonAncestor(blk1, blk2 *labelPosition) *labelPosition { + sb := f.ssaB.LowestCommonAncestor(blk1.sb, blk2.sb) + pos := f.m.getOrAllocateSSABlockLabelPosition(sb) + return pos +} + +// Idom implements regalloc.Function. +func (f *regAllocFn) Idom(blk *labelPosition) *labelPosition { + sb := f.ssaB.Idom(blk.sb) + pos := f.m.getOrAllocateSSABlockLabelPosition(sb) + return pos +} + +// SwapBefore implements regalloc.Function. +func (f *regAllocFn) SwapBefore(x1, x2, tmp regalloc.VReg, instr *instruction) { + f.m.swap(instr.prev, x1, x2, tmp) +} + +// StoreRegisterBefore implements regalloc.Function. +func (f *regAllocFn) StoreRegisterBefore(v regalloc.VReg, instr *instruction) { + m := f.m + m.insertStoreRegisterAt(v, instr, false) +} + +// StoreRegisterAfter implements regalloc.Function. +func (f *regAllocFn) StoreRegisterAfter(v regalloc.VReg, instr *instruction) { + m := f.m + m.insertStoreRegisterAt(v, instr, true) +} + +// ReloadRegisterBefore implements regalloc.Function. +func (f *regAllocFn) ReloadRegisterBefore(v regalloc.VReg, instr *instruction) { + m := f.m + m.insertReloadRegisterAt(v, instr, false) +} + +// ReloadRegisterAfter implements regalloc.Function. +func (f *regAllocFn) ReloadRegisterAfter(v regalloc.VReg, instr *instruction) { + m := f.m + m.insertReloadRegisterAt(v, instr, true) +} + +// InsertMoveBefore implements regalloc.Function. +func (f *regAllocFn) InsertMoveBefore(dst, src regalloc.VReg, instr *instruction) { + f.m.insertMoveBefore(dst, src, instr) +} + +// LoopNestingForestChild implements regalloc.Function. +func (f *regAllocFn) LoopNestingForestChild(pos *labelPosition, i int) *labelPosition { + childSB := pos.sb.LoopNestingForestChildren()[i] + return f.m.getOrAllocateSSABlockLabelPosition(childSB) +} + +// Succ implements regalloc.Block. +func (f *regAllocFn) Succ(pos *labelPosition, i int) *labelPosition { + succSB := pos.sb.Succ(i) + if succSB.ReturnBlock() { + return nil + } + return f.m.getOrAllocateSSABlockLabelPosition(succSB) +} + +// Pred implements regalloc.Block. +func (f *regAllocFn) Pred(pos *labelPosition, i int) *labelPosition { + predSB := pos.sb.Pred(i) + return f.m.getOrAllocateSSABlockLabelPosition(predSB) +} + +// BlockParams implements regalloc.Function. +func (f *regAllocFn) BlockParams(pos *labelPosition, regs *[]regalloc.VReg) []regalloc.VReg { + c := f.m.c + *regs = (*regs)[:0] + for i := 0; i < pos.sb.Params(); i++ { + v := c.VRegOf(pos.sb.Param(i)) + *regs = append(*regs, v) + } + return *regs +} + +// ID implements regalloc.Block. +func (pos *labelPosition) ID() int32 { + return int32(pos.sb.ID()) +} + +// InstrIteratorBegin implements regalloc.Block. +func (pos *labelPosition) InstrIteratorBegin() *instruction { + ret := pos.begin + pos.cur = ret + return ret +} + +// InstrIteratorNext implements regalloc.Block. +func (pos *labelPosition) InstrIteratorNext() *instruction { + for { + if pos.cur == pos.end { + return nil + } + instr := pos.cur.next + pos.cur = instr + if instr == nil { + return nil + } else if instr.addedBeforeRegAlloc { + // Only concerned about the instruction added before regalloc. + return instr + } + } +} + +// InstrRevIteratorBegin implements regalloc.Block. +func (pos *labelPosition) InstrRevIteratorBegin() *instruction { + pos.cur = pos.end + return pos.cur +} + +// InstrRevIteratorNext implements regalloc.Block. +func (pos *labelPosition) InstrRevIteratorNext() *instruction { + for { + if pos.cur == pos.begin { + return nil + } + instr := pos.cur.prev + pos.cur = instr + if instr == nil { + return nil + } else if instr.addedBeforeRegAlloc { + // Only concerned about the instruction added before regalloc. + return instr + } + } +} + +// FirstInstr implements regalloc.Block. +func (pos *labelPosition) FirstInstr() *instruction { return pos.begin } + +// LastInstrForInsertion implements regalloc.Block. +func (pos *labelPosition) LastInstrForInsertion() *instruction { + return lastInstrForInsertion(pos.begin, pos.end) +} + +// Preds implements regalloc.Block. +func (pos *labelPosition) Preds() int { return pos.sb.Preds() } + +// Entry implements regalloc.Block. +func (pos *labelPosition) Entry() bool { return pos.sb.EntryBlock() } + +// Succs implements regalloc.Block. +func (pos *labelPosition) Succs() int { return pos.sb.Succs() } + +// LoopHeader implements regalloc.Block. +func (pos *labelPosition) LoopHeader() bool { return pos.sb.LoopHeader() } + +// LoopNestingForestChildren implements regalloc.Block. +func (pos *labelPosition) LoopNestingForestChildren() int { + return len(pos.sb.LoopNestingForestChildren()) +} + +func (m *machine) insertMoveBefore(dst, src regalloc.VReg, instr *instruction) { + typ := src.RegType() + if typ != dst.RegType() { + panic("BUG: src and dst must have the same type") + } + + mov := m.allocateInstr() + if typ == regalloc.RegTypeInt { + mov.asMovRR(src, dst, true) + } else { + mov.asXmmUnaryRmR(sseOpcodeMovdqu, newOperandReg(src), dst) + } + + cur := instr.prev + prevNext := cur.next + cur = linkInstr(cur, mov) + linkInstr(cur, prevNext) +} + +func (m *machine) insertStoreRegisterAt(v regalloc.VReg, instr *instruction, after bool) *instruction { + if !v.IsRealReg() { + panic("BUG: VReg must be backed by real reg to be stored") + } + + typ := m.c.TypeOf(v) + + var prevNext, cur *instruction + if after { + cur, prevNext = instr, instr.next + } else { + cur, prevNext = instr.prev, instr + } + + offsetFromSP := m.getVRegSpillSlotOffsetFromSP(v.ID(), typ.Size()) + store := m.allocateInstr() + mem := newOperandMem(m.newAmodeImmReg(uint32(offsetFromSP), rspVReg)) + switch typ { + case ssa.TypeI32: + store.asMovRM(v, mem, 4) + case ssa.TypeI64: + store.asMovRM(v, mem, 8) + case ssa.TypeF32: + store.asXmmMovRM(sseOpcodeMovss, v, mem) + case ssa.TypeF64: + store.asXmmMovRM(sseOpcodeMovsd, v, mem) + case ssa.TypeV128: + store.asXmmMovRM(sseOpcodeMovdqu, v, mem) + } + + cur = linkInstr(cur, store) + return linkInstr(cur, prevNext) +} + +func (m *machine) insertReloadRegisterAt(v regalloc.VReg, instr *instruction, after bool) *instruction { + if !v.IsRealReg() { + panic("BUG: VReg must be backed by real reg to be stored") + } + + typ := m.c.TypeOf(v) + var prevNext, cur *instruction + if after { + cur, prevNext = instr, instr.next + } else { + cur, prevNext = instr.prev, instr + } + + // Load the value to the temporary. + load := m.allocateInstr() + offsetFromSP := m.getVRegSpillSlotOffsetFromSP(v.ID(), typ.Size()) + a := newOperandMem(m.newAmodeImmReg(uint32(offsetFromSP), rspVReg)) + switch typ { + case ssa.TypeI32: + load.asMovzxRmR(extModeLQ, a, v) + case ssa.TypeI64: + load.asMov64MR(a, v) + case ssa.TypeF32: + load.asXmmUnaryRmR(sseOpcodeMovss, a, v) + case ssa.TypeF64: + load.asXmmUnaryRmR(sseOpcodeMovsd, a, v) + case ssa.TypeV128: + load.asXmmUnaryRmR(sseOpcodeMovdqu, a, v) + default: + panic("BUG") + } + + cur = linkInstr(cur, load) + return linkInstr(cur, prevNext) +} + +func (m *machine) swap(cur *instruction, x1, x2, tmp regalloc.VReg) { + if x1.RegType() == regalloc.RegTypeInt { + prevNext := cur.next + xc := m.allocateInstr().asXCHG(x1, newOperandReg(x2), 8) + cur = linkInstr(cur, xc) + linkInstr(cur, prevNext) + } else { + if tmp.Valid() { + prevNext := cur.next + m.insertMoveBefore(tmp, x1, prevNext) + m.insertMoveBefore(x1, x2, prevNext) + m.insertMoveBefore(x2, tmp, prevNext) + } else { + prevNext := cur.next + r2 := x2.RealReg() + // Temporarily spill x1 to stack. + cur = m.insertStoreRegisterAt(x1, cur, true).prev + // Then move x2 to x1. + cur = linkInstr(cur, m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqa, newOperandReg(x2), x1)) + linkInstr(cur, prevNext) + // Then reload the original value on x1 from stack to r2. + m.insertReloadRegisterAt(x1.SetRealReg(r2), cur, true) + } + } +} + +func lastInstrForInsertion(begin, end *instruction) *instruction { + cur := end + for cur.kind == nop0 { + cur = cur.prev + if cur == begin { + return end + } + } + switch cur.kind { + case jmp: + return cur + default: + return end + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine_vec.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine_vec.go new file mode 100644 index 00000000000..8d514d8576d --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine_vec.go @@ -0,0 +1,992 @@ +package amd64 + +import ( + "fmt" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" +) + +var swizzleMask = [16]byte{ + 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, + 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, +} + +func (m *machine) lowerSwizzle(x, y ssa.Value, ret ssa.Value) { + masklabel := m.getOrAllocateConstLabel(&m.constSwizzleMaskConstIndex, swizzleMask[:]) + + // Load mask to maskReg. + maskReg := m.c.AllocateVReg(ssa.TypeV128) + loadMask := m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(m.newAmodeRipRel(masklabel)), maskReg) + m.insert(loadMask) + + // Copy x and y to tmp registers. + xx := m.getOperand_Reg(m.c.ValueDefinition(x)) + tmpDst := m.copyToTmp(xx.reg()) + yy := m.getOperand_Reg(m.c.ValueDefinition(y)) + tmpX := m.copyToTmp(yy.reg()) + + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePaddusb, newOperandReg(maskReg), tmpX)) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePshufb, newOperandReg(tmpX), tmpDst)) + + // Copy the result to the destination register. + m.copyTo(tmpDst, m.c.VRegOf(ret)) +} + +func (m *machine) lowerInsertLane(x, y ssa.Value, index byte, ret ssa.Value, lane ssa.VecLane) { + // Copy x to tmp. + tmpDst := m.c.AllocateVReg(ssa.TypeV128) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, m.getOperand_Mem_Reg(m.c.ValueDefinition(x)), tmpDst)) + + yy := m.getOperand_Reg(m.c.ValueDefinition(y)) + switch lane { + case ssa.VecLaneI8x16: + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePinsrb, index, yy, tmpDst)) + case ssa.VecLaneI16x8: + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePinsrw, index, yy, tmpDst)) + case ssa.VecLaneI32x4: + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePinsrd, index, yy, tmpDst)) + case ssa.VecLaneI64x2: + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePinsrq, index, yy, tmpDst)) + case ssa.VecLaneF32x4: + // In INSERTPS instruction, the destination index is encoded at 4 and 5 bits of the argument. + // See https://www.felixcloutier.com/x86/insertps + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodeInsertps, index<<4, yy, tmpDst)) + case ssa.VecLaneF64x2: + if index == 0 { + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovsd, yy, tmpDst)) + } else { + m.insert(m.allocateInstr().asXmmRmR(sseOpcodeMovlhps, yy, tmpDst)) + } + default: + panic(fmt.Sprintf("invalid lane type: %s", lane)) + } + + m.copyTo(tmpDst, m.c.VRegOf(ret)) +} + +func (m *machine) lowerExtractLane(x ssa.Value, index byte, signed bool, ret ssa.Value, lane ssa.VecLane) { + // Pextr variants are used to extract a lane from a vector register. + xx := m.getOperand_Reg(m.c.ValueDefinition(x)) + + tmpDst := m.c.AllocateVReg(ret.Type()) + m.insert(m.allocateInstr().asDefineUninitializedReg(tmpDst)) + switch lane { + case ssa.VecLaneI8x16: + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePextrb, index, xx, tmpDst)) + if signed { + m.insert(m.allocateInstr().asMovsxRmR(extModeBL, newOperandReg(tmpDst), tmpDst)) + } else { + m.insert(m.allocateInstr().asMovzxRmR(extModeBL, newOperandReg(tmpDst), tmpDst)) + } + case ssa.VecLaneI16x8: + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePextrw, index, xx, tmpDst)) + if signed { + m.insert(m.allocateInstr().asMovsxRmR(extModeWL, newOperandReg(tmpDst), tmpDst)) + } else { + m.insert(m.allocateInstr().asMovzxRmR(extModeWL, newOperandReg(tmpDst), tmpDst)) + } + case ssa.VecLaneI32x4: + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePextrd, index, xx, tmpDst)) + case ssa.VecLaneI64x2: + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePextrq, index, xx, tmpDst)) + case ssa.VecLaneF32x4: + if index == 0 { + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovss, xx, tmpDst)) + } else { + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePshufd, index, xx, tmpDst)) + } + case ssa.VecLaneF64x2: + if index == 0 { + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovsd, xx, tmpDst)) + } else { + m.copyTo(xx.reg(), tmpDst) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePshufd, 0b00_00_11_10, newOperandReg(tmpDst), tmpDst)) + } + default: + panic(fmt.Sprintf("invalid lane type: %s", lane)) + } + + m.copyTo(tmpDst, m.c.VRegOf(ret)) +} + +var sqmulRoundSat = [16]byte{ + 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, + 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, +} + +func (m *machine) lowerSqmulRoundSat(x, y, ret ssa.Value) { + // See https://github.com/WebAssembly/simd/pull/365 for the following logic. + maskLabel := m.getOrAllocateConstLabel(&m.constSqmulRoundSatIndex, sqmulRoundSat[:]) + + tmp := m.c.AllocateVReg(ssa.TypeV128) + loadMask := m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(m.newAmodeRipRel(maskLabel)), tmp) + m.insert(loadMask) + + xx, yy := m.getOperand_Reg(m.c.ValueDefinition(x)), m.getOperand_Mem_Reg(m.c.ValueDefinition(y)) + tmpX := m.copyToTmp(xx.reg()) + + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePmulhrsw, yy, tmpX)) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePcmpeqw, newOperandReg(tmpX), tmp)) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePxor, newOperandReg(tmp), tmpX)) + + m.copyTo(tmpX, m.c.VRegOf(ret)) +} + +func (m *machine) lowerVUshr(x, y, ret ssa.Value, lane ssa.VecLane) { + switch lane { + case ssa.VecLaneI8x16: + m.lowerVUshri8x16(x, y, ret) + case ssa.VecLaneI16x8, ssa.VecLaneI32x4, ssa.VecLaneI64x2: + m.lowerShr(x, y, ret, lane, false) + default: + panic(fmt.Sprintf("invalid lane type: %s", lane)) + } +} + +// i8x16LogicalSHRMaskTable is necessary for emulating non-existent packed bytes logical right shifts on amd64. +// The mask is applied after performing packed word shifts on the value to clear out the unnecessary bits. +var i8x16LogicalSHRMaskTable = [8 * 16]byte{ // (the number of possible shift amount 0, 1, ..., 7.) * 16 bytes. + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // for 0 shift + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, // for 1 shift + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, // for 2 shift + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, // for 3 shift + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, // for 4 shift + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, // for 5 shift + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, // for 6 shift + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // for 7 shift +} + +func (m *machine) lowerVUshri8x16(x, y, ret ssa.Value) { + tmpGpReg := m.c.AllocateVReg(ssa.TypeI32) + // Load the modulo 8 mask to tmpReg. + m.lowerIconst(tmpGpReg, 0x7, false) + // Take the modulo 8 of the shift amount. + shiftAmt := m.getOperand_Mem_Imm32_Reg(m.c.ValueDefinition(y)) + m.insert(m.allocateInstr().asAluRmiR(aluRmiROpcodeAnd, shiftAmt, tmpGpReg, false)) + + _xx := m.getOperand_Reg(m.c.ValueDefinition(x)) + xx := m.copyToTmp(_xx.reg()) + + vecTmp := m.c.AllocateVReg(ssa.TypeV128) + m.insert(m.allocateInstr().asGprToXmm(sseOpcodeMovd, newOperandReg(tmpGpReg), vecTmp, false)) + m.insert(m.allocateInstr().asXmmRmiReg(sseOpcodePsrlw, newOperandReg(vecTmp), xx)) + + maskTableLabel := m.getOrAllocateConstLabel(&m.constI8x16LogicalSHRMaskTableIndex, i8x16LogicalSHRMaskTable[:]) + base := m.c.AllocateVReg(ssa.TypeI64) + lea := m.allocateInstr().asLEA(newOperandLabel(maskTableLabel), base) + m.insert(lea) + + // Shift tmpGpReg by 4 to multiply the shift amount by 16. + m.insert(m.allocateInstr().asShiftR(shiftROpShiftLeft, newOperandImm32(4), tmpGpReg, false)) + + mem := m.newAmodeRegRegShift(0, base, tmpGpReg, 0) + loadMask := m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(mem), vecTmp) + m.insert(loadMask) + + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePand, newOperandReg(vecTmp), xx)) + m.copyTo(xx, m.c.VRegOf(ret)) +} + +func (m *machine) lowerVSshr(x, y, ret ssa.Value, lane ssa.VecLane) { + switch lane { + case ssa.VecLaneI8x16: + m.lowerVSshri8x16(x, y, ret) + case ssa.VecLaneI16x8, ssa.VecLaneI32x4: + m.lowerShr(x, y, ret, lane, true) + case ssa.VecLaneI64x2: + m.lowerVSshri64x2(x, y, ret) + default: + panic(fmt.Sprintf("invalid lane type: %s", lane)) + } +} + +func (m *machine) lowerVSshri8x16(x, y, ret ssa.Value) { + shiftAmtReg := m.c.AllocateVReg(ssa.TypeI32) + // Load the modulo 8 mask to tmpReg. + m.lowerIconst(shiftAmtReg, 0x7, false) + // Take the modulo 8 of the shift amount. + shiftAmt := m.getOperand_Mem_Imm32_Reg(m.c.ValueDefinition(y)) + m.insert(m.allocateInstr().asAluRmiR(aluRmiROpcodeAnd, shiftAmt, shiftAmtReg, false)) + + // Copy the x value to two temporary registers. + _xx := m.getOperand_Reg(m.c.ValueDefinition(x)) + xx := m.copyToTmp(_xx.reg()) + vecTmp := m.c.AllocateVReg(ssa.TypeV128) + m.copyTo(xx, vecTmp) + + // Assuming that we have + // xx = [b1, ..., b16] + // vecTmp = [b1, ..., b16] + // at this point, then we use PUNPCKLBW and PUNPCKHBW to produce: + // xx = [b1, b1, b2, b2, ..., b8, b8] + // vecTmp = [b9, b9, b10, b10, ..., b16, b16] + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePunpcklbw, newOperandReg(xx), xx)) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePunpckhbw, newOperandReg(vecTmp), vecTmp)) + + // Adding 8 to the shift amount, and then move the amount to vecTmp2. + vecTmp2 := m.c.AllocateVReg(ssa.TypeV128) + m.insert(m.allocateInstr().asAluRmiR(aluRmiROpcodeAdd, newOperandImm32(8), shiftAmtReg, false)) + m.insert(m.allocateInstr().asGprToXmm(sseOpcodeMovd, newOperandReg(shiftAmtReg), vecTmp2, false)) + + // Perform the word packed arithmetic right shifts on vreg and vecTmp. + // This changes these two registers as: + // xx = [xxx, b1 >> s, xxx, b2 >> s, ..., xxx, b8 >> s] + // vecTmp = [xxx, b9 >> s, xxx, b10 >> s, ..., xxx, b16 >> s] + // where xxx is 1 or 0 depending on each byte's sign, and ">>" is the arithmetic shift on a byte. + m.insert(m.allocateInstr().asXmmRmiReg(sseOpcodePsraw, newOperandReg(vecTmp2), xx)) + m.insert(m.allocateInstr().asXmmRmiReg(sseOpcodePsraw, newOperandReg(vecTmp2), vecTmp)) + + // Finally, we can get the result by packing these two word vectors. + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePacksswb, newOperandReg(vecTmp), xx)) + + m.copyTo(xx, m.c.VRegOf(ret)) +} + +func (m *machine) lowerVSshri64x2(x, y, ret ssa.Value) { + // Load the shift amount to RCX. + shiftAmt := m.getOperand_Mem_Reg(m.c.ValueDefinition(y)) + m.insert(m.allocateInstr().asMovzxRmR(extModeBQ, shiftAmt, rcxVReg)) + + tmpGp := m.c.AllocateVReg(ssa.TypeI64) + + _xx := m.getOperand_Reg(m.c.ValueDefinition(x)) + xxReg := m.copyToTmp(_xx.reg()) + + m.insert(m.allocateInstr().asDefineUninitializedReg(tmpGp)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePextrq, 0, newOperandReg(xxReg), tmpGp)) + m.insert(m.allocateInstr().asShiftR(shiftROpShiftRightArithmetic, newOperandReg(rcxVReg), tmpGp, true)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePinsrq, 0, newOperandReg(tmpGp), xxReg)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePextrq, 1, newOperandReg(xxReg), tmpGp)) + m.insert(m.allocateInstr().asShiftR(shiftROpShiftRightArithmetic, newOperandReg(rcxVReg), tmpGp, true)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePinsrq, 1, newOperandReg(tmpGp), xxReg)) + + m.copyTo(xxReg, m.c.VRegOf(ret)) +} + +func (m *machine) lowerShr(x, y, ret ssa.Value, lane ssa.VecLane, signed bool) { + var modulo uint64 + var shiftOp sseOpcode + switch lane { + case ssa.VecLaneI16x8: + modulo = 0xf + if signed { + shiftOp = sseOpcodePsraw + } else { + shiftOp = sseOpcodePsrlw + } + case ssa.VecLaneI32x4: + modulo = 0x1f + if signed { + shiftOp = sseOpcodePsrad + } else { + shiftOp = sseOpcodePsrld + } + case ssa.VecLaneI64x2: + modulo = 0x3f + if signed { + panic("BUG") + } + shiftOp = sseOpcodePsrlq + default: + panic(fmt.Sprintf("invalid lane type: %s", lane)) + } + + _xx := m.getOperand_Reg(m.c.ValueDefinition(x)) + xx := m.copyToTmp(_xx.reg()) + + tmpGpReg := m.c.AllocateVReg(ssa.TypeI32) + // Load the modulo 8 mask to tmpReg. + m.lowerIconst(tmpGpReg, modulo, false) + // Take the modulo 8 of the shift amount. + m.insert(m.allocateInstr().asAluRmiR(aluRmiROpcodeAnd, + m.getOperand_Mem_Imm32_Reg(m.c.ValueDefinition(y)), tmpGpReg, false)) + // And move it to a xmm register. + tmpVec := m.c.AllocateVReg(ssa.TypeV128) + m.insert(m.allocateInstr().asGprToXmm(sseOpcodeMovd, newOperandReg(tmpGpReg), tmpVec, false)) + + // Then do the actual shift. + m.insert(m.allocateInstr().asXmmRmiReg(shiftOp, newOperandReg(tmpVec), xx)) + + m.copyTo(xx, m.c.VRegOf(ret)) +} + +func (m *machine) lowerVIshl(x, y, ret ssa.Value, lane ssa.VecLane) { + var modulo uint64 + var shiftOp sseOpcode + var isI8x16 bool + switch lane { + case ssa.VecLaneI8x16: + isI8x16 = true + modulo = 0x7 + shiftOp = sseOpcodePsllw + case ssa.VecLaneI16x8: + modulo = 0xf + shiftOp = sseOpcodePsllw + case ssa.VecLaneI32x4: + modulo = 0x1f + shiftOp = sseOpcodePslld + case ssa.VecLaneI64x2: + modulo = 0x3f + shiftOp = sseOpcodePsllq + default: + panic(fmt.Sprintf("invalid lane type: %s", lane)) + } + + _xx := m.getOperand_Reg(m.c.ValueDefinition(x)) + xx := m.copyToTmp(_xx.reg()) + + tmpGpReg := m.c.AllocateVReg(ssa.TypeI32) + // Load the modulo 8 mask to tmpReg. + m.lowerIconst(tmpGpReg, modulo, false) + // Take the modulo 8 of the shift amount. + m.insert(m.allocateInstr().asAluRmiR(aluRmiROpcodeAnd, + m.getOperand_Mem_Imm32_Reg(m.c.ValueDefinition(y)), tmpGpReg, false)) + // And move it to a xmm register. + tmpVec := m.c.AllocateVReg(ssa.TypeV128) + m.insert(m.allocateInstr().asGprToXmm(sseOpcodeMovd, newOperandReg(tmpGpReg), tmpVec, false)) + + // Then do the actual shift. + m.insert(m.allocateInstr().asXmmRmiReg(shiftOp, newOperandReg(tmpVec), xx)) + + if isI8x16 { + maskTableLabel := m.getOrAllocateConstLabel(&m.constI8x16SHLMaskTableIndex, i8x16SHLMaskTable[:]) + base := m.c.AllocateVReg(ssa.TypeI64) + lea := m.allocateInstr().asLEA(newOperandLabel(maskTableLabel), base) + m.insert(lea) + + // Shift tmpGpReg by 4 to multiply the shift amount by 16. + m.insert(m.allocateInstr().asShiftR(shiftROpShiftLeft, newOperandImm32(4), tmpGpReg, false)) + + mem := m.newAmodeRegRegShift(0, base, tmpGpReg, 0) + loadMask := m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(mem), tmpVec) + m.insert(loadMask) + + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePand, newOperandReg(tmpVec), xx)) + } + + m.copyTo(xx, m.c.VRegOf(ret)) +} + +// i8x16SHLMaskTable is necessary for emulating non-existent packed bytes left shifts on amd64. +// The mask is applied after performing packed word shifts on the value to clear out the unnecessary bits. +var i8x16SHLMaskTable = [8 * 16]byte{ // (the number of possible shift amount 0, 1, ..., 7.) * 16 bytes. + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // for 0 shift + 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, // for 1 shift + 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, // for 2 shift + 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, // for 3 shift + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // for 4 shift + 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, // for 5 shift + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, // for 6 shift + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // for 7 shift +} + +func (m *machine) lowerVRound(x, ret ssa.Value, imm byte, _64 bool) { + xx := m.getOperand_Mem_Reg(m.c.ValueDefinition(x)) + var round sseOpcode + if _64 { + round = sseOpcodeRoundpd + } else { + round = sseOpcodeRoundps + } + m.insert(m.allocateInstr().asXmmUnaryRmRImm(round, imm, xx, m.c.VRegOf(ret))) +} + +var ( + allOnesI8x16 = [16]byte{0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1} + allOnesI16x8 = [16]byte{0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0} + extAddPairwiseI16x8uMask1 = [16]byte{0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80} + extAddPairwiseI16x8uMask2 = [16]byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00} +) + +func (m *machine) lowerExtIaddPairwise(x, ret ssa.Value, srcLane ssa.VecLane, signed bool) { + _xx := m.getOperand_Reg(m.c.ValueDefinition(x)) + xx := m.copyToTmp(_xx.reg()) + switch srcLane { + case ssa.VecLaneI8x16: + allOneReg := m.c.AllocateVReg(ssa.TypeV128) + mask := m.getOrAllocateConstLabel(&m.constAllOnesI8x16Index, allOnesI8x16[:]) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(m.newAmodeRipRel(mask)), allOneReg)) + + var resultReg regalloc.VReg + if signed { + resultReg = allOneReg + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePmaddubsw, newOperandReg(xx), resultReg)) + } else { + // Interpreter tmp (all ones) as signed byte meaning that all the multiply-add is unsigned. + resultReg = xx + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePmaddubsw, newOperandReg(allOneReg), resultReg)) + } + m.copyTo(resultReg, m.c.VRegOf(ret)) + + case ssa.VecLaneI16x8: + if signed { + allOnesReg := m.c.AllocateVReg(ssa.TypeV128) + mask := m.getOrAllocateConstLabel(&m.constAllOnesI16x8Index, allOnesI16x8[:]) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(m.newAmodeRipRel(mask)), allOnesReg)) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePmaddwd, newOperandReg(allOnesReg), xx)) + m.copyTo(xx, m.c.VRegOf(ret)) + } else { + maskReg := m.c.AllocateVReg(ssa.TypeV128) + mask := m.getOrAllocateConstLabel(&m.constExtAddPairwiseI16x8uMask1Index, extAddPairwiseI16x8uMask1[:]) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(m.newAmodeRipRel(mask)), maskReg)) + + // Flip the sign bits on xx. + // + // Assuming that xx = [w1, ..., w8], now we have, + // xx[i] = int8(-w1) for i = 0...8 + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePxor, newOperandReg(maskReg), xx)) + + mask = m.getOrAllocateConstLabel(&m.constAllOnesI16x8Index, allOnesI16x8[:]) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(m.newAmodeRipRel(mask)), maskReg)) + + // For i = 0,..4 (as this results in i32x4 lanes), now we have + // xx[i] = int32(-wn + -w(n+1)) = int32(-(wn + w(n+1))) + // c.assembler.CompileRegisterToRegister(amd64.PMADDWD, tmp, vr) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePmaddwd, newOperandReg(maskReg), xx)) + + mask = m.getOrAllocateConstLabel(&m.constExtAddPairwiseI16x8uMask2Index, extAddPairwiseI16x8uMask2[:]) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(m.newAmodeRipRel(mask)), maskReg)) + + // vr[i] = int32(-(wn + w(n+1))) + int32(math.MaxInt16+1) = int32((wn + w(n+1))) = uint32(wn + w(n+1)). + // c.assembler.CompileRegisterToRegister(amd64.PADDD, tmp, vr) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePaddd, newOperandReg(maskReg), xx)) + + m.copyTo(xx, m.c.VRegOf(ret)) + } + default: + panic(fmt.Sprintf("invalid lane type: %s", srcLane)) + } +} + +func (m *machine) lowerWidenLow(x, ret ssa.Value, lane ssa.VecLane, signed bool) { + var sseOp sseOpcode + switch lane { + case ssa.VecLaneI8x16: + if signed { + sseOp = sseOpcodePmovsxbw + } else { + sseOp = sseOpcodePmovzxbw + } + case ssa.VecLaneI16x8: + if signed { + sseOp = sseOpcodePmovsxwd + } else { + sseOp = sseOpcodePmovzxwd + } + case ssa.VecLaneI32x4: + if signed { + sseOp = sseOpcodePmovsxdq + } else { + sseOp = sseOpcodePmovzxdq + } + default: + panic(fmt.Sprintf("invalid lane type: %s", lane)) + } + + xx := m.getOperand_Mem_Reg(m.c.ValueDefinition(x)) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOp, xx, m.c.VRegOf(ret))) +} + +func (m *machine) lowerWidenHigh(x, ret ssa.Value, lane ssa.VecLane, signed bool) { + tmp := m.c.AllocateVReg(ssa.TypeV128) + xx := m.getOperand_Reg(m.c.ValueDefinition(x)) + m.copyTo(xx.reg(), tmp) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePalignr, 8, newOperandReg(tmp), tmp)) + + var sseOp sseOpcode + switch lane { + case ssa.VecLaneI8x16: + if signed { + sseOp = sseOpcodePmovsxbw + } else { + sseOp = sseOpcodePmovzxbw + } + case ssa.VecLaneI16x8: + if signed { + sseOp = sseOpcodePmovsxwd + } else { + sseOp = sseOpcodePmovzxwd + } + case ssa.VecLaneI32x4: + if signed { + sseOp = sseOpcodePmovsxdq + } else { + sseOp = sseOpcodePmovzxdq + } + default: + panic(fmt.Sprintf("invalid lane type: %s", lane)) + } + + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOp, newOperandReg(tmp), m.c.VRegOf(ret))) +} + +func (m *machine) lowerLoadSplat(ptr ssa.Value, offset uint32, ret ssa.Value, lane ssa.VecLane) { + tmpDst, tmpGp := m.c.AllocateVReg(ssa.TypeV128), m.c.AllocateVReg(ssa.TypeI64) + am := newOperandMem(m.lowerToAddressMode(ptr, offset)) + + m.insert(m.allocateInstr().asDefineUninitializedReg(tmpDst)) + switch lane { + case ssa.VecLaneI8x16: + m.insert(m.allocateInstr().asMovzxRmR(extModeBQ, am, tmpGp)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePinsrb, 0, newOperandReg(tmpGp), tmpDst)) + tmpZeroVec := m.c.AllocateVReg(ssa.TypeV128) + m.insert(m.allocateInstr().asZeros(tmpZeroVec)) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePshufb, newOperandReg(tmpZeroVec), tmpDst)) + case ssa.VecLaneI16x8: + m.insert(m.allocateInstr().asMovzxRmR(extModeWQ, am, tmpGp)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePinsrw, 0, newOperandReg(tmpGp), tmpDst)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePinsrw, 1, newOperandReg(tmpGp), tmpDst)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePshufd, 0, newOperandReg(tmpDst), tmpDst)) + case ssa.VecLaneI32x4: + m.insert(m.allocateInstr().asMovzxRmR(extModeLQ, am, tmpGp)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePinsrd, 0, newOperandReg(tmpGp), tmpDst)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePshufd, 0, newOperandReg(tmpDst), tmpDst)) + case ssa.VecLaneI64x2: + m.insert(m.allocateInstr().asMov64MR(am, tmpGp)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePinsrq, 0, newOperandReg(tmpGp), tmpDst)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodePinsrq, 1, newOperandReg(tmpGp), tmpDst)) + default: + panic(fmt.Sprintf("invalid lane type: %s", lane)) + } + + m.copyTo(tmpDst, m.c.VRegOf(ret)) +} + +var f64x2CvtFromIMask = [16]byte{ + 0x00, 0x00, 0x30, 0x43, 0x00, 0x00, 0x30, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +} + +func (m *machine) lowerVFcvtFromInt(x, ret ssa.Value, lane ssa.VecLane, signed bool) { + switch lane { + case ssa.VecLaneF32x4: + if signed { + xx := m.getOperand_Reg(m.c.ValueDefinition(x)) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeCvtdq2ps, xx, m.c.VRegOf(ret))) + } else { + xx := m.getOperand_Reg(m.c.ValueDefinition(x)) + // Copy the value to two temporary registers. + tmp := m.copyToTmp(xx.reg()) + tmp2 := m.copyToTmp(xx.reg()) + + // Clear the higher 16 bits of each 32-bit element. + m.insert(m.allocateInstr().asXmmRmiReg(sseOpcodePslld, newOperandImm32(0xa), tmp)) + m.insert(m.allocateInstr().asXmmRmiReg(sseOpcodePsrld, newOperandImm32(0xa), tmp)) + + // Subtract the higher 16-bits from tmp2: clear the lower 16-bits of tmp2. + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePsubd, newOperandReg(tmp), tmp2)) + + // Convert the lower 16-bits in tmp. + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeCvtdq2ps, newOperandReg(tmp), tmp)) + + // Left shift by one and convert tmp2, meaning that halved conversion result of higher 16-bits in tmp2. + m.insert(m.allocateInstr().asXmmRmiReg(sseOpcodePsrld, newOperandImm32(1), tmp2)) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeCvtdq2ps, newOperandReg(tmp2), tmp2)) + + // Double the converted halved higher 16bits. + m.insert(m.allocateInstr().asXmmRmR(sseOpcodeAddps, newOperandReg(tmp2), tmp2)) + + // Get the conversion result by add tmp (holding lower 16-bit conversion) into tmp2. + m.insert(m.allocateInstr().asXmmRmR(sseOpcodeAddps, newOperandReg(tmp), tmp2)) + + m.copyTo(tmp2, m.c.VRegOf(ret)) + } + case ssa.VecLaneF64x2: + if signed { + xx := m.getOperand_Mem_Reg(m.c.ValueDefinition(x)) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeCvtdq2pd, xx, m.c.VRegOf(ret))) + } else { + maskReg := m.c.AllocateVReg(ssa.TypeV128) + maskLabel := m.getOrAllocateConstLabel(&m.constF64x2CvtFromIMaskIndex, f64x2CvtFromIMask[:]) + // maskReg = [0x00, 0x00, 0x30, 0x43, 0x00, 0x00, 0x30, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(m.newAmodeRipRel(maskLabel)), maskReg)) + + _xx := m.getOperand_Reg(m.c.ValueDefinition(x)) + xx := m.copyToTmp(_xx.reg()) + + // Given that we have xx = [d1, d2, d3, d4], this results in + // xx = [d1, [0x00, 0x00, 0x30, 0x43], d2, [0x00, 0x00, 0x30, 0x43]] + // = [float64(uint32(d1)) + 0x1.0p52, float64(uint32(d2)) + 0x1.0p52] + // ^See https://stackoverflow.com/questions/13269523/can-all-32-bit-ints-be-exactly-represented-as-a-double + m.insert(m.allocateInstr().asXmmRmR(sseOpcodeUnpcklps, newOperandReg(maskReg), xx)) + + // maskReg = [float64(0x1.0p52), float64(0x1.0p52)] + maskLabel = m.getOrAllocateConstLabel(&m.constTwop52Index, twop52[:]) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(m.newAmodeRipRel(maskLabel)), maskReg)) + + // Now, we get the result as + // xx = [float64(uint32(d1)), float64(uint32(d2))] + // because the following equality always satisfies: + // float64(0x1.0p52 + float64(uint32(x))) - float64(0x1.0p52 + float64(uint32(y))) = float64(uint32(x)) - float64(uint32(y)) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodeSubpd, newOperandReg(maskReg), xx)) + + m.copyTo(xx, m.c.VRegOf(ret)) + } + default: + panic(fmt.Sprintf("invalid lane type: %s", lane)) + } +} + +var ( + // i32sMaxOnF64x2 holds math.MaxInt32(=2147483647.0) on two f64 lanes. + i32sMaxOnF64x2 = [16]byte{ + 0x00, 0x00, 0xc0, 0xff, 0xff, 0xff, 0xdf, 0x41, // float64(2147483647.0) + 0x00, 0x00, 0xc0, 0xff, 0xff, 0xff, 0xdf, 0x41, // float64(2147483647.0) + } + + // i32sMaxOnF64x2 holds math.MaxUint32(=4294967295.0) on two f64 lanes. + i32uMaxOnF64x2 = [16]byte{ + 0x00, 0x00, 0xe0, 0xff, 0xff, 0xff, 0xef, 0x41, // float64(4294967295.0) + 0x00, 0x00, 0xe0, 0xff, 0xff, 0xff, 0xef, 0x41, // float64(4294967295.0) + } + + // twop52 holds two float64(0x1.0p52) on two f64 lanes. 0x1.0p52 is special in the sense that + // with this exponent, the mantissa represents a corresponding uint32 number, and arithmetics, + // like addition or subtraction, the resulted floating point holds exactly the same + // bit representations in 32-bit integer on its mantissa. + // + // Note: the name twop52 is common across various compiler ecosystem. + // E.g. https://github.com/llvm/llvm-project/blob/92ab024f81e5b64e258b7c3baaf213c7c26fcf40/compiler-rt/lib/builtins/floatdidf.c#L28 + // E.g. https://opensource.apple.com/source/clang/clang-425.0.24/src/projects/compiler-rt/lib/floatdidf.c.auto.html + twop52 = [16]byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x43, // float64(0x1.0p52) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x43, // float64(0x1.0p52) + } +) + +func (m *machine) lowerVFcvtToIntSat(x, ret ssa.Value, lane ssa.VecLane, signed bool) { + _xx := m.getOperand_Reg(m.c.ValueDefinition(x)) + xx := m.copyToTmp(_xx.reg()) + + switch lane { + case ssa.VecLaneF32x4: + if signed { + tmp := m.copyToTmp(xx) + + // Assuming we have xx = [v1, v2, v3, v4]. + // + // Set all bits if lane is not NaN on tmp. + // tmp[i] = 0xffffffff if vi != NaN + // = 0 if vi == NaN + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodeCmpps, uint8(cmpPredEQ_OQ), newOperandReg(tmp), tmp)) + + // Clear NaN lanes on xx, meaning that + // xx[i] = vi if vi != NaN + // 0 if vi == NaN + m.insert(m.allocateInstr().asXmmRmR(sseOpcodeAndps, newOperandReg(tmp), xx)) + + // tmp[i] = ^vi if vi != NaN + // = 0xffffffff if vi == NaN + // which means that tmp[i] & 0x80000000 != 0 if and only if vi is negative. + m.insert(m.allocateInstr().asXmmRmR(sseOpcodeXorps, newOperandReg(xx), tmp)) + + // xx[i] = int32(vi) if vi != NaN and xx is not overflowing. + // = 0x80000000 if vi != NaN and xx is overflowing (See https://www.felixcloutier.com/x86/cvttps2dq) + // = 0 if vi == NaN + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeCvttps2dq, newOperandReg(xx), xx)) + + // Below, we have to convert 0x80000000 into 0x7FFFFFFF for positive overflowing lane. + // + // tmp[i] = 0x80000000 if vi is positive + // = any satisfying any&0x80000000 = 0 if vi is negative or zero. + m.insert(m.allocateInstr().asXmmRmR(sseOpcodeAndps, newOperandReg(xx), tmp)) + + // Arithmetic right shifting tmp by 31, meaning that we have + // tmp[i] = 0xffffffff if vi is positive, 0 otherwise. + m.insert(m.allocateInstr().asXmmRmiReg(sseOpcodePsrad, newOperandImm32(0x1f), tmp)) + + // Flipping 0x80000000 if vi is positive, otherwise keep intact. + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePxor, newOperandReg(tmp), xx)) + } else { + tmp := m.c.AllocateVReg(ssa.TypeV128) + m.insert(m.allocateInstr().asZeros(tmp)) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodeMaxps, newOperandReg(tmp), xx)) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePcmpeqd, newOperandReg(tmp), tmp)) + m.insert(m.allocateInstr().asXmmRmiReg(sseOpcodePsrld, newOperandImm32(0x1), tmp)) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeCvtdq2ps, newOperandReg(tmp), tmp)) + tmp2 := m.copyToTmp(xx) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeCvttps2dq, newOperandReg(xx), xx)) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodeSubps, newOperandReg(tmp), tmp2)) + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodeCmpps, uint8(cmpPredLE_OS), newOperandReg(tmp2), tmp)) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeCvttps2dq, newOperandReg(tmp2), tmp2)) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePxor, newOperandReg(tmp), tmp2)) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePxor, newOperandReg(tmp), tmp)) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePmaxsd, newOperandReg(tmp), tmp2)) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePaddd, newOperandReg(tmp2), xx)) + } + + case ssa.VecLaneF64x2: + tmp2 := m.c.AllocateVReg(ssa.TypeV128) + if signed { + tmp := m.copyToTmp(xx) + + // Set all bits for non-NaN lanes, zeros otherwise. + // I.e. tmp[i] = 0xffffffff_ffffffff if vi != NaN, 0 otherwise. + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodeCmppd, uint8(cmpPredEQ_OQ), newOperandReg(tmp), tmp)) + + maskLabel := m.getOrAllocateConstLabel(&m.constI32sMaxOnF64x2Index, i32sMaxOnF64x2[:]) + // Load the 2147483647 into tmp2's each lane. + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(m.newAmodeRipRel(maskLabel)), tmp2)) + + // tmp[i] = 2147483647 if vi != NaN, 0 otherwise. + m.insert(m.allocateInstr().asXmmRmR(sseOpcodeAndps, newOperandReg(tmp2), tmp)) + + // MINPD returns the source register's value as-is, so we have + // xx[i] = vi if vi != NaN + // = 0 if vi == NaN + m.insert(m.allocateInstr().asXmmRmR(sseOpcodeMinpd, newOperandReg(tmp), xx)) + + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeCvttpd2dq, newOperandReg(xx), xx)) + } else { + tmp := m.c.AllocateVReg(ssa.TypeV128) + m.insert(m.allocateInstr().asZeros(tmp)) + + // xx[i] = vi if vi != NaN && vi > 0 + // = 0 if vi == NaN || vi <= 0 + m.insert(m.allocateInstr().asXmmRmR(sseOpcodeMaxpd, newOperandReg(tmp), xx)) + + // tmp2[i] = float64(math.MaxUint32) = math.MaxUint32 + maskIndex := m.getOrAllocateConstLabel(&m.constI32uMaxOnF64x2Index, i32uMaxOnF64x2[:]) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(m.newAmodeRipRel(maskIndex)), tmp2)) + + // xx[i] = vi if vi != NaN && vi > 0 && vi <= math.MaxUint32 + // = 0 otherwise + m.insert(m.allocateInstr().asXmmRmR(sseOpcodeMinpd, newOperandReg(tmp2), xx)) + + // Round the floating points into integer. + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodeRoundpd, 0x3, newOperandReg(xx), xx)) + + // tmp2[i] = float64(0x1.0p52) + maskIndex = m.getOrAllocateConstLabel(&m.constTwop52Index, twop52[:]) + m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(m.newAmodeRipRel(maskIndex)), tmp2)) + + // xx[i] = float64(0x1.0p52) + float64(uint32(vi)) if vi != NaN && vi > 0 && vi <= math.MaxUint32 + // = 0 otherwise + // + // This means that xx[i] holds exactly the same bit of uint32(vi) in its lower 32-bits. + m.insert(m.allocateInstr().asXmmRmR(sseOpcodeAddpd, newOperandReg(tmp2), xx)) + + // At this point, we have + // xx = [uint32(v0), float64(0x1.0p52), uint32(v1), float64(0x1.0p52)] + // tmp = [0, 0, 0, 0] + // as 32x4 lanes. Therefore, SHUFPS with 0b00_00_10_00 results in + // xx = [xx[00], xx[10], tmp[00], tmp[00]] = [xx[00], xx[10], 0, 0] + // meaning that for i = 0 and 1, we have + // xx[i] = uint32(vi) if vi != NaN && vi > 0 && vi <= math.MaxUint32 + // = 0 otherwise. + m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodeShufps, 0b00_00_10_00, newOperandReg(tmp), xx)) + } + default: + panic(fmt.Sprintf("invalid lane type: %s", lane)) + } + + m.copyTo(xx, m.c.VRegOf(ret)) +} + +func (m *machine) lowerNarrow(x, y, ret ssa.Value, lane ssa.VecLane, signed bool) { + _xx := m.getOperand_Reg(m.c.ValueDefinition(x)) + xx := m.copyToTmp(_xx.reg()) + yy := m.getOperand_Mem_Reg(m.c.ValueDefinition(y)) + + var sseOp sseOpcode + switch lane { + case ssa.VecLaneI16x8: + if signed { + sseOp = sseOpcodePacksswb + } else { + sseOp = sseOpcodePackuswb + } + case ssa.VecLaneI32x4: + if signed { + sseOp = sseOpcodePackssdw + } else { + sseOp = sseOpcodePackusdw + } + default: + panic(fmt.Sprintf("invalid lane type: %s", lane)) + } + m.insert(m.allocateInstr().asXmmRmR(sseOp, yy, xx)) + m.copyTo(xx, m.c.VRegOf(ret)) +} + +func (m *machine) lowerWideningPairwiseDotProductS(x, y, ret ssa.Value) { + _xx := m.getOperand_Reg(m.c.ValueDefinition(x)) + xx := m.copyToTmp(_xx.reg()) + yy := m.getOperand_Mem_Reg(m.c.ValueDefinition(y)) + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePmaddwd, yy, xx)) + m.copyTo(xx, m.c.VRegOf(ret)) +} + +func (m *machine) lowerVIabs(instr *ssa.Instruction) { + x, lane := instr.ArgWithLane() + rd := m.c.VRegOf(instr.Return()) + + if lane == ssa.VecLaneI64x2 { + _xx := m.getOperand_Reg(m.c.ValueDefinition(x)) + + blendReg := xmm0VReg + m.insert(m.allocateInstr().asDefineUninitializedReg(blendReg)) + + tmp := m.copyToTmp(_xx.reg()) + xx := m.copyToTmp(_xx.reg()) + + // Clear all bits on blendReg. + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePxor, newOperandReg(blendReg), blendReg)) + // Subtract xx from blendMaskReg. + m.insert(m.allocateInstr().asXmmRmR(sseOpcodePsubq, newOperandReg(xx), blendReg)) + // Copy the subtracted value ^^ back into tmp. + m.copyTo(blendReg, xx) + + m.insert(m.allocateInstr().asBlendvpd(newOperandReg(tmp), xx)) + + m.copyTo(xx, rd) + } else { + var vecOp sseOpcode + switch lane { + case ssa.VecLaneI8x16: + vecOp = sseOpcodePabsb + case ssa.VecLaneI16x8: + vecOp = sseOpcodePabsw + case ssa.VecLaneI32x4: + vecOp = sseOpcodePabsd + } + rn := m.getOperand_Reg(m.c.ValueDefinition(x)) + + i := m.allocateInstr() + i.asXmmUnaryRmR(vecOp, rn, rd) + m.insert(i) + } +} + +func (m *machine) lowerVIpopcnt(instr *ssa.Instruction) { + x := instr.Arg() + rn := m.getOperand_Reg(m.c.ValueDefinition(x)) + rd := m.c.VRegOf(instr.Return()) + + tmp1 := m.c.AllocateVReg(ssa.TypeV128) + m.lowerVconst(tmp1, 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f) + + // Copy input into tmp2. + tmp2 := m.copyToTmp(rn.reg()) + + // Given that we have: + // rm = [b1, ..., b16] where bn = hn:ln and hn and ln are higher and lower 4-bits of bn. + // + // Take PAND on tmp1 and tmp2, so that we mask out all the higher bits. + // tmp2 = [l1, ..., l16]. + pand := m.allocateInstr() + pand.asXmmRmR(sseOpcodePand, newOperandReg(tmp1), tmp2) + m.insert(pand) + + // Do logical (packed word) right shift by 4 on rm and PAND against the mask (tmp1); meaning that we have + // tmp3 = [h1, ...., h16]. + tmp3 := m.copyToTmp(rn.reg()) + psrlw := m.allocateInstr() + psrlw.asXmmRmiReg(sseOpcodePsrlw, newOperandImm32(4), tmp3) + m.insert(psrlw) + + pand2 := m.allocateInstr() + pand2.asXmmRmR(sseOpcodePand, newOperandReg(tmp1), tmp3) + m.insert(pand2) + + // Read the popcntTable into tmp4, and we have + // tmp4 = [0x00, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x03, 0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04] + tmp4 := m.c.AllocateVReg(ssa.TypeV128) + m.lowerVconst(tmp4, 0x03_02_02_01_02_01_01_00, 0x04_03_03_02_03_02_02_01) + + // Make a copy for later. + tmp5 := m.copyToTmp(tmp4) + + // tmp4 = [popcnt(l1), ..., popcnt(l16)]. + pshufb := m.allocateInstr() + pshufb.asXmmRmR(sseOpcodePshufb, newOperandReg(tmp2), tmp4) + m.insert(pshufb) + + pshufb2 := m.allocateInstr() + pshufb2.asXmmRmR(sseOpcodePshufb, newOperandReg(tmp3), tmp5) + m.insert(pshufb2) + + // tmp4 + tmp5 is the result. + paddb := m.allocateInstr() + paddb.asXmmRmR(sseOpcodePaddb, newOperandReg(tmp4), tmp5) + m.insert(paddb) + + m.copyTo(tmp5, rd) +} + +func (m *machine) lowerVImul(instr *ssa.Instruction) { + x, y, lane := instr.Arg2WithLane() + rd := m.c.VRegOf(instr.Return()) + if lane == ssa.VecLaneI64x2 { + rn := m.getOperand_Reg(m.c.ValueDefinition(x)) + rm := m.getOperand_Reg(m.c.ValueDefinition(y)) + // Assuming that we have + // rm = [p1, p2] = [p1_lo, p1_hi, p2_lo, p2_high] + // rn = [q1, q2] = [q1_lo, q1_hi, q2_lo, q2_high] + // where pN and qN are 64-bit (quad word) lane, and pN_lo, pN_hi, qN_lo and qN_hi are 32-bit (double word) lane. + + // Copy rn into tmp1. + tmp1 := m.copyToTmp(rn.reg()) + + // And do the logical right shift by 32-bit on tmp1, which makes tmp1 = [0, p1_high, 0, p2_high] + shift := m.allocateInstr() + shift.asXmmRmiReg(sseOpcodePsrlq, newOperandImm32(32), tmp1) + m.insert(shift) + + // Execute "pmuludq rm,tmp1", which makes tmp1 = [p1_high*q1_lo, p2_high*q2_lo] where each lane is 64-bit. + mul := m.allocateInstr() + mul.asXmmRmR(sseOpcodePmuludq, rm, tmp1) + m.insert(mul) + + // Copy rm value into tmp2. + tmp2 := m.copyToTmp(rm.reg()) + + // And do the logical right shift by 32-bit on tmp2, which makes tmp2 = [0, q1_high, 0, q2_high] + shift2 := m.allocateInstr() + shift2.asXmmRmiReg(sseOpcodePsrlq, newOperandImm32(32), tmp2) + m.insert(shift2) + + // Execute "pmuludq rm,tmp2", which makes tmp2 = [p1_lo*q1_high, p2_lo*q2_high] where each lane is 64-bit. + mul2 := m.allocateInstr() + mul2.asXmmRmR(sseOpcodePmuludq, rn, tmp2) + m.insert(mul2) + + // Adds tmp1 and tmp2 and do the logical left shift by 32-bit, + // which makes tmp1 = [(p1_lo*q1_high+p1_high*q1_lo)<<32, (p2_lo*q2_high+p2_high*q2_lo)<<32] + add := m.allocateInstr() + add.asXmmRmR(sseOpcodePaddq, newOperandReg(tmp2), tmp1) + m.insert(add) + + shift3 := m.allocateInstr() + shift3.asXmmRmiReg(sseOpcodePsllq, newOperandImm32(32), tmp1) + m.insert(shift3) + + // Copy rm value into tmp3. + tmp3 := m.copyToTmp(rm.reg()) + + // "pmuludq rm,tmp3" makes tmp3 = [p1_lo*q1_lo, p2_lo*q2_lo] where each lane is 64-bit. + mul3 := m.allocateInstr() + mul3.asXmmRmR(sseOpcodePmuludq, rn, tmp3) + m.insert(mul3) + + // Finally, we get the result by computing tmp1 + tmp3, + // which makes tmp1 = [(p1_lo*q1_high+p1_high*q1_lo)<<32+p1_lo*q1_lo, (p2_lo*q2_high+p2_high*q2_lo)<<32+p2_lo*q2_lo] + add2 := m.allocateInstr() + add2.asXmmRmR(sseOpcodePaddq, newOperandReg(tmp3), tmp1) + m.insert(add2) + + m.copyTo(tmp1, rd) + + } else { + var vecOp sseOpcode + switch lane { + case ssa.VecLaneI16x8: + vecOp = sseOpcodePmullw + case ssa.VecLaneI32x4: + vecOp = sseOpcodePmulld + default: + panic("unsupported: " + lane.String()) + } + m.lowerVbBinOp(vecOp, x, y, instr.Return()) + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/operands.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/operands.go new file mode 100644 index 00000000000..78797568332 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/operands.go @@ -0,0 +1,336 @@ +package amd64 + +import ( + "fmt" + "unsafe" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +type operand struct { + kind operandKind + data uint64 +} + +type operandKind byte + +const ( + // operandKindReg is an operand which is an integer Register. + operandKindReg operandKind = iota + 1 + + // operandKindMem is a value in Memory. + // 32, 64, or 128 bit value. + operandKindMem + + // operandKindImm32 is a signed-32-bit integer immediate value. + operandKindImm32 + + // operandKindLabel is a label. + operandKindLabel +) + +// String implements fmt.Stringer. +func (o operandKind) String() string { + switch o { + case operandKindReg: + return "reg" + case operandKindMem: + return "mem" + case operandKindImm32: + return "imm32" + case operandKindLabel: + return "label" + default: + panic("BUG: invalid operand kind") + } +} + +// format returns the string representation of the operand. +// _64 is only for the case where the operand is a register, and it's integer. +func (o *operand) format(_64 bool) string { + switch o.kind { + case operandKindReg: + return formatVRegSized(o.reg(), _64) + case operandKindMem: + return o.addressMode().String() + case operandKindImm32: + return fmt.Sprintf("$%d", int32(o.imm32())) + case operandKindLabel: + return label(o.imm32()).String() + default: + panic(fmt.Sprintf("BUG: invalid operand: %s", o.kind)) + } +} + +//go:inline +func (o *operand) reg() regalloc.VReg { + return regalloc.VReg(o.data) +} + +//go:inline +func (o *operand) setReg(r regalloc.VReg) { + o.data = uint64(r) +} + +//go:inline +func (o *operand) addressMode() *amode { + return wazevoapi.PtrFromUintptr[amode](uintptr(o.data)) +} + +//go:inline +func (o *operand) imm32() uint32 { + return uint32(o.data) +} + +func (o *operand) label() label { + switch o.kind { + case operandKindLabel: + return label(o.data) + case operandKindMem: + mem := o.addressMode() + if mem.kind() != amodeRipRel { + panic("BUG: invalid label") + } + return label(mem.imm32) + default: + panic("BUG: invalid operand kind") + } +} + +func newOperandLabel(label label) operand { + return operand{kind: operandKindLabel, data: uint64(label)} +} + +func newOperandReg(r regalloc.VReg) operand { + return operand{kind: operandKindReg, data: uint64(r)} +} + +func newOperandImm32(imm32 uint32) operand { + return operand{kind: operandKindImm32, data: uint64(imm32)} +} + +func newOperandMem(amode *amode) operand { + return operand{kind: operandKindMem, data: uint64(uintptr(unsafe.Pointer(amode)))} +} + +// amode is a memory operand (addressing mode). +type amode struct { + kindWithShift uint32 + imm32 uint32 + base regalloc.VReg + + // For amodeRegRegShift: + index regalloc.VReg +} + +type amodeKind byte + +const ( + // amodeRegRegShift calculates sign-extend-32-to-64(Immediate) + base + amodeImmReg amodeKind = iota + 1 + + // amodeImmRBP is the same as amodeImmReg, but the base register is fixed to RBP. + // The only differece is that it doesn't tell the register allocator to use RBP which is distracting for the + // register allocator. + amodeImmRBP + + // amodeRegRegShift calculates sign-extend-32-to-64(Immediate) + base + (Register2 << Shift) + amodeRegRegShift + + // amodeRipRel is a RIP-relative addressing mode specified by the label. + amodeRipRel + + // TODO: there are other addressing modes such as the one without base register. +) + +func (a *amode) kind() amodeKind { + return amodeKind(a.kindWithShift & 0xff) +} + +func (a *amode) shift() byte { + return byte(a.kindWithShift >> 8) +} + +func (a *amode) uses(rs *[]regalloc.VReg) { + switch a.kind() { + case amodeImmReg: + *rs = append(*rs, a.base) + case amodeRegRegShift: + *rs = append(*rs, a.base, a.index) + case amodeImmRBP, amodeRipRel: + default: + panic("BUG: invalid amode kind") + } +} + +func (a *amode) nregs() int { + switch a.kind() { + case amodeImmReg: + return 1 + case amodeRegRegShift: + return 2 + case amodeImmRBP, amodeRipRel: + return 0 + default: + panic("BUG: invalid amode kind") + } +} + +func (a *amode) assignUses(i int, reg regalloc.VReg) { + switch a.kind() { + case amodeImmReg: + if i == 0 { + a.base = reg + } else { + panic("BUG: invalid amode assignment") + } + case amodeRegRegShift: + if i == 0 { + a.base = reg + } else if i == 1 { + a.index = reg + } else { + panic("BUG: invalid amode assignment") + } + default: + panic("BUG: invalid amode assignment") + } +} + +func (m *machine) newAmodeImmReg(imm32 uint32, base regalloc.VReg) *amode { + ret := m.amodePool.Allocate() + *ret = amode{kindWithShift: uint32(amodeImmReg), imm32: imm32, base: base} + return ret +} + +func (m *machine) newAmodeImmRBPReg(imm32 uint32) *amode { + ret := m.amodePool.Allocate() + *ret = amode{kindWithShift: uint32(amodeImmRBP), imm32: imm32, base: rbpVReg} + return ret +} + +func (m *machine) newAmodeRegRegShift(imm32 uint32, base, index regalloc.VReg, shift byte) *amode { + if shift > 3 { + panic(fmt.Sprintf("BUG: invalid shift (must be 3>=): %d", shift)) + } + ret := m.amodePool.Allocate() + *ret = amode{kindWithShift: uint32(amodeRegRegShift) | uint32(shift)<<8, imm32: imm32, base: base, index: index} + return ret +} + +func (m *machine) newAmodeRipRel(label label) *amode { + ret := m.amodePool.Allocate() + *ret = amode{kindWithShift: uint32(amodeRipRel), imm32: uint32(label)} + return ret +} + +// String implements fmt.Stringer. +func (a *amode) String() string { + switch a.kind() { + case amodeImmReg, amodeImmRBP: + if a.imm32 == 0 { + return fmt.Sprintf("(%s)", formatVRegSized(a.base, true)) + } + return fmt.Sprintf("%d(%s)", int32(a.imm32), formatVRegSized(a.base, true)) + case amodeRegRegShift: + shift := 1 << a.shift() + if a.imm32 == 0 { + return fmt.Sprintf( + "(%s,%s,%d)", + formatVRegSized(a.base, true), formatVRegSized(a.index, true), shift) + } + return fmt.Sprintf( + "%d(%s,%s,%d)", + int32(a.imm32), formatVRegSized(a.base, true), formatVRegSized(a.index, true), shift) + case amodeRipRel: + return fmt.Sprintf("%s(%%rip)", label(a.imm32)) + default: + panic("BUG: invalid amode kind") + } +} + +func (m *machine) getOperand_Mem_Reg(def backend.SSAValueDefinition) (op operand) { + if !def.IsFromInstr() { + return newOperandReg(m.c.VRegOf(def.V)) + } + + if def.V.Type() == ssa.TypeV128 { + // SIMD instructions require strict memory alignment, so we don't support the memory operand for V128 at the moment. + return m.getOperand_Reg(def) + } + + if m.c.MatchInstr(def, ssa.OpcodeLoad) { + instr := def.Instr + ptr, offset, _ := instr.LoadData() + op = newOperandMem(m.lowerToAddressMode(ptr, offset)) + instr.MarkLowered() + return op + } + return m.getOperand_Reg(def) +} + +func (m *machine) getOperand_Mem_Imm32_Reg(def backend.SSAValueDefinition) (op operand) { + if !def.IsFromInstr() { + return newOperandReg(m.c.VRegOf(def.V)) + } + + if m.c.MatchInstr(def, ssa.OpcodeLoad) { + instr := def.Instr + ptr, offset, _ := instr.LoadData() + op = newOperandMem(m.lowerToAddressMode(ptr, offset)) + instr.MarkLowered() + return op + } + return m.getOperand_Imm32_Reg(def) +} + +func (m *machine) getOperand_Imm32_Reg(def backend.SSAValueDefinition) (op operand) { + if !def.IsFromInstr() { + return newOperandReg(m.c.VRegOf(def.V)) + } + + instr := def.Instr + if instr.Constant() { + // If the operation is 64-bit, x64 sign-extends the 32-bit immediate value. + // Therefore, we need to check if the immediate value is within the 32-bit range and if the sign bit is set, + // we should not use the immediate value. + if op, ok := asImm32Operand(instr.ConstantVal(), instr.Return().Type() == ssa.TypeI32); ok { + instr.MarkLowered() + return op + } + } + return m.getOperand_Reg(def) +} + +func asImm32Operand(val uint64, allowSignExt bool) (operand, bool) { + if imm32, ok := asImm32(val, allowSignExt); ok { + return newOperandImm32(imm32), true + } + return operand{}, false +} + +func asImm32(val uint64, allowSignExt bool) (uint32, bool) { + u32val := uint32(val) + if uint64(u32val) != val { + return 0, false + } + if !allowSignExt && u32val&0x80000000 != 0 { + return 0, false + } + return u32val, true +} + +func (m *machine) getOperand_Reg(def backend.SSAValueDefinition) (op operand) { + var v regalloc.VReg + if instr := def.Instr; instr != nil && instr.Constant() { + // We inline all the constant instructions so that we could reduce the register usage. + v = m.lowerConstant(instr) + instr.MarkLowered() + } else { + v = m.c.VRegOf(def.V) + } + return newOperandReg(v) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/reg.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/reg.go new file mode 100644 index 00000000000..4aec856fab9 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/reg.go @@ -0,0 +1,181 @@ +package amd64 + +import ( + "fmt" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" +) + +// Amd64-specific registers. +const ( + // rax is a gp register. + rax = regalloc.RealRegInvalid + 1 + iota + // rcx is a gp register. + rcx + // rdx is a gp register. + rdx + // rbx is a gp register. + rbx + // rsp is a gp register. + rsp + // rbp is a gp register. + rbp + // rsi is a gp register. + rsi + // rdi is a gp register. + rdi + // r8 is a gp register. + r8 + // r9 is a gp register. + r9 + // r10 is a gp register. + r10 + // r11 is a gp register. + r11 + // r12 is a gp register. + r12 + // r13 is a gp register. + r13 + // r14 is a gp register. + r14 + // r15 is a gp register. + r15 + + // xmm0 is a vector register. + xmm0 + // xmm1 is a vector register. + xmm1 + // xmm2 is a vector register. + xmm2 + // xmm3 is a vector register. + xmm3 + // xmm4 is a vector register. + xmm4 + // xmm5 is a vector register. + xmm5 + // xmm6 is a vector register. + xmm6 + // xmm7 is a vector register. + xmm7 + // xmm8 is a vector register. + xmm8 + // xmm9 is a vector register. + xmm9 + // xmm10 is a vector register. + xmm10 + // xmm11 is a vector register. + xmm11 + // xmm12 is a vector register. + xmm12 + // xmm13 is a vector register. + xmm13 + // xmm14 is a vector register. + xmm14 + // xmm15 is a vector register. + xmm15 +) + +var ( + raxVReg = regalloc.FromRealReg(rax, regalloc.RegTypeInt) + rcxVReg = regalloc.FromRealReg(rcx, regalloc.RegTypeInt) + rdxVReg = regalloc.FromRealReg(rdx, regalloc.RegTypeInt) + rbxVReg = regalloc.FromRealReg(rbx, regalloc.RegTypeInt) + rspVReg = regalloc.FromRealReg(rsp, regalloc.RegTypeInt) + rbpVReg = regalloc.FromRealReg(rbp, regalloc.RegTypeInt) + rsiVReg = regalloc.FromRealReg(rsi, regalloc.RegTypeInt) + rdiVReg = regalloc.FromRealReg(rdi, regalloc.RegTypeInt) + r8VReg = regalloc.FromRealReg(r8, regalloc.RegTypeInt) + r9VReg = regalloc.FromRealReg(r9, regalloc.RegTypeInt) + r10VReg = regalloc.FromRealReg(r10, regalloc.RegTypeInt) + r11VReg = regalloc.FromRealReg(r11, regalloc.RegTypeInt) + r12VReg = regalloc.FromRealReg(r12, regalloc.RegTypeInt) + r13VReg = regalloc.FromRealReg(r13, regalloc.RegTypeInt) + r14VReg = regalloc.FromRealReg(r14, regalloc.RegTypeInt) + r15VReg = regalloc.FromRealReg(r15, regalloc.RegTypeInt) + + xmm0VReg = regalloc.FromRealReg(xmm0, regalloc.RegTypeFloat) + xmm1VReg = regalloc.FromRealReg(xmm1, regalloc.RegTypeFloat) + xmm2VReg = regalloc.FromRealReg(xmm2, regalloc.RegTypeFloat) + xmm3VReg = regalloc.FromRealReg(xmm3, regalloc.RegTypeFloat) + xmm4VReg = regalloc.FromRealReg(xmm4, regalloc.RegTypeFloat) + xmm5VReg = regalloc.FromRealReg(xmm5, regalloc.RegTypeFloat) + xmm6VReg = regalloc.FromRealReg(xmm6, regalloc.RegTypeFloat) + xmm7VReg = regalloc.FromRealReg(xmm7, regalloc.RegTypeFloat) + xmm8VReg = regalloc.FromRealReg(xmm8, regalloc.RegTypeFloat) + xmm9VReg = regalloc.FromRealReg(xmm9, regalloc.RegTypeFloat) + xmm10VReg = regalloc.FromRealReg(xmm10, regalloc.RegTypeFloat) + xmm11VReg = regalloc.FromRealReg(xmm11, regalloc.RegTypeFloat) + xmm12VReg = regalloc.FromRealReg(xmm12, regalloc.RegTypeFloat) + xmm13VReg = regalloc.FromRealReg(xmm13, regalloc.RegTypeFloat) + xmm14VReg = regalloc.FromRealReg(xmm14, regalloc.RegTypeFloat) + xmm15VReg = regalloc.FromRealReg(xmm15, regalloc.RegTypeFloat) +) + +var regNames = [...]string{ + rax: "rax", + rcx: "rcx", + rdx: "rdx", + rbx: "rbx", + rsp: "rsp", + rbp: "rbp", + rsi: "rsi", + rdi: "rdi", + r8: "r8", + r9: "r9", + r10: "r10", + r11: "r11", + r12: "r12", + r13: "r13", + r14: "r14", + r15: "r15", + xmm0: "xmm0", + xmm1: "xmm1", + xmm2: "xmm2", + xmm3: "xmm3", + xmm4: "xmm4", + xmm5: "xmm5", + xmm6: "xmm6", + xmm7: "xmm7", + xmm8: "xmm8", + xmm9: "xmm9", + xmm10: "xmm10", + xmm11: "xmm11", + xmm12: "xmm12", + xmm13: "xmm13", + xmm14: "xmm14", + xmm15: "xmm15", +} + +func formatVRegSized(r regalloc.VReg, _64 bool) string { + if r.IsRealReg() { + if r.RegType() == regalloc.RegTypeInt { + rr := r.RealReg() + orig := regNames[rr] + if rr <= rdi { + if _64 { + return "%" + orig + } else { + return "%e" + orig[1:] + } + } else { + if _64 { + return "%" + orig + } else { + return "%" + orig + "d" + } + } + } else { + return "%" + regNames[r.RealReg()] + } + } else { + if r.RegType() == regalloc.RegTypeInt { + if _64 { + return fmt.Sprintf("%%r%d?", r.ID()) + } else { + return fmt.Sprintf("%%r%dd?", r.ID()) + } + } else { + return fmt.Sprintf("%%xmm%d?", r.ID()) + } + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/stack.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/stack.go new file mode 100644 index 00000000000..ef823bdbdc7 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/stack.go @@ -0,0 +1,130 @@ +package amd64 + +import ( + "encoding/binary" + "reflect" + "unsafe" + + "github.com/tetratelabs/wazero/internal/wasmdebug" +) + +func stackView(rbp, top uintptr) []byte { + l := int(top - rbp) + var stackBuf []byte + { + //nolint:staticcheck + hdr := (*reflect.SliceHeader)(unsafe.Pointer(&stackBuf)) + hdr.Data = rbp + hdr.Len = l + hdr.Cap = l + } + return stackBuf +} + +// UnwindStack implements wazevo.unwindStack. +func UnwindStack(_, rbp, top uintptr, returnAddresses []uintptr) []uintptr { + stackBuf := stackView(rbp, top) + + for i := uint64(0); i < uint64(len(stackBuf)); { + // (high address) + // +-----------------+ + // | ....... | + // | ret Y | + // | ....... | + // | ret 0 | + // | arg X | + // | ....... | + // | arg 1 | + // | arg 0 | + // | ReturnAddress | + // | Caller_RBP | + // +-----------------+ <---- Caller_RBP + // | ........... | + // | clobbered M | + // | ............ | + // | clobbered 0 | + // | spill slot N | + // | ............ | + // | spill slot 0 | + // | ReturnAddress | + // | Caller_RBP | + // +-----------------+ <---- RBP + // (low address) + + callerRBP := binary.LittleEndian.Uint64(stackBuf[i:]) + retAddr := binary.LittleEndian.Uint64(stackBuf[i+8:]) + returnAddresses = append(returnAddresses, uintptr(retAddr)) + i = callerRBP - uint64(rbp) + if len(returnAddresses) == wasmdebug.MaxFrames { + break + } + } + return returnAddresses +} + +// GoCallStackView implements wazevo.goCallStackView. +func GoCallStackView(stackPointerBeforeGoCall *uint64) []uint64 { + // (high address) + // +-----------------+ <----+ + // | xxxxxxxxxxx | | ;; optional unused space to make it 16-byte aligned. + // ^ | arg[N]/ret[M] | | + // sliceSize | | ............ | | SizeInBytes/8 + // | | arg[1]/ret[1] | | + // v | arg[0]/ret[0] | <----+ + // | SizeInBytes | + // +-----------------+ <---- stackPointerBeforeGoCall + // (low address) + data := unsafe.Add(unsafe.Pointer(stackPointerBeforeGoCall), 8) + size := *stackPointerBeforeGoCall / 8 + return unsafe.Slice((*uint64)(data), size) +} + +func AdjustClonedStack(oldRsp, oldTop, rsp, rbp, top uintptr) { + diff := uint64(rsp - oldRsp) + + newBuf := stackView(rbp, top) + for i := uint64(0); i < uint64(len(newBuf)); { + // (high address) + // +-----------------+ + // | ....... | + // | ret Y | + // | ....... | + // | ret 0 | + // | arg X | + // | ....... | + // | arg 1 | + // | arg 0 | + // | ReturnAddress | + // | Caller_RBP | + // +-----------------+ <---- Caller_RBP + // | ........... | + // | clobbered M | + // | ............ | + // | clobbered 0 | + // | spill slot N | + // | ............ | + // | spill slot 0 | + // | ReturnAddress | + // | Caller_RBP | + // +-----------------+ <---- RBP + // (low address) + + callerRBP := binary.LittleEndian.Uint64(newBuf[i:]) + if callerRBP == 0 { + // End of stack. + break + } + if i64 := int64(callerRBP); i64 < int64(oldRsp) || i64 >= int64(oldTop) { + panic("BUG: callerRBP is out of range") + } + if int(callerRBP) < 0 { + panic("BUG: callerRBP is negative") + } + adjustedCallerRBP := callerRBP + diff + if int(adjustedCallerRBP) < 0 { + panic("BUG: adjustedCallerRBP is negative") + } + binary.LittleEndian.PutUint64(newBuf[i:], adjustedCallerRBP) + i = adjustedCallerRBP - uint64(rbp) + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi.go new file mode 100644 index 00000000000..f9e7be90527 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi.go @@ -0,0 +1,376 @@ +package arm64 + +import ( + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" +) + +// References: +// * https://github.com/golang/go/blob/go1.24.0/src/cmd/compile/abi-internal.md#arm64-architecture +// * https://developer.arm.com/documentation/102374/0101/Procedure-Call-Standard + +var ( + intParamResultRegs = []regalloc.RealReg{x0, x1, x2, x3, x4, x5, x6, x7} + floatParamResultRegs = []regalloc.RealReg{v0, v1, v2, v3, v4, v5, v6, v7} +) + +var regInfo = ®alloc.RegisterInfo{ + AllocatableRegisters: [regalloc.NumRegType][]regalloc.RealReg{ + // We don't allocate: + // - x18: Reserved by the macOS: https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms#Respect-the-purpose-of-specific-CPU-registers + // - x28: Reserved by Go runtime. + // - x27(=tmpReg): because of the reason described on tmpReg. + regalloc.RegTypeInt: { + x8, x9, x10, x11, x12, x13, x14, x15, + x16, x17, x19, x20, x21, x22, x23, x24, x25, + x26, x29, x30, + // These are the argument/return registers. Less preferred in the allocation. + x7, x6, x5, x4, x3, x2, x1, x0, + }, + regalloc.RegTypeFloat: { + v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, + // These are the argument/return registers. Less preferred in the allocation. + v7, v6, v5, v4, v3, v2, v1, v0, + }, + }, + CalleeSavedRegisters: regalloc.NewRegSet( + x19, x20, x21, x22, x23, x24, x25, x26, x28, + v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, + ), + CallerSavedRegisters: regalloc.NewRegSet( + x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x29, x30, + v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, + ), + RealRegToVReg: []regalloc.VReg{ + x0: x0VReg, x1: x1VReg, x2: x2VReg, x3: x3VReg, x4: x4VReg, x5: x5VReg, x6: x6VReg, x7: x7VReg, x8: x8VReg, x9: x9VReg, x10: x10VReg, x11: x11VReg, x12: x12VReg, x13: x13VReg, x14: x14VReg, x15: x15VReg, x16: x16VReg, x17: x17VReg, x18: x18VReg, x19: x19VReg, x20: x20VReg, x21: x21VReg, x22: x22VReg, x23: x23VReg, x24: x24VReg, x25: x25VReg, x26: x26VReg, x27: x27VReg, x28: x28VReg, x29: x29VReg, x30: x30VReg, + v0: v0VReg, v1: v1VReg, v2: v2VReg, v3: v3VReg, v4: v4VReg, v5: v5VReg, v6: v6VReg, v7: v7VReg, v8: v8VReg, v9: v9VReg, v10: v10VReg, v11: v11VReg, v12: v12VReg, v13: v13VReg, v14: v14VReg, v15: v15VReg, v16: v16VReg, v17: v17VReg, v18: v18VReg, v19: v19VReg, v20: v20VReg, v21: v21VReg, v22: v22VReg, v23: v23VReg, v24: v24VReg, v25: v25VReg, v26: v26VReg, v27: v27VReg, v28: v28VReg, v29: v29VReg, v30: v30VReg, v31: v31VReg, + }, + RealRegName: func(r regalloc.RealReg) string { return regNames[r] }, + RealRegType: func(r regalloc.RealReg) regalloc.RegType { + if r < v0 { + return regalloc.RegTypeInt + } + return regalloc.RegTypeFloat + }, +} + +// ArgsResultsRegs implements backend.Machine. +func (m *machine) ArgsResultsRegs() (argResultInts, argResultFloats []regalloc.RealReg) { + return intParamResultRegs, floatParamResultRegs +} + +// LowerParams implements backend.FunctionABI. +func (m *machine) LowerParams(args []ssa.Value) { + a := m.currentABI + + for i, ssaArg := range args { + if !ssaArg.Valid() { + continue + } + reg := m.compiler.VRegOf(ssaArg) + arg := &a.Args[i] + if arg.Kind == backend.ABIArgKindReg { + m.InsertMove(reg, arg.Reg, arg.Type) + } else { + // TODO: we could use pair load if there's consecutive loads for the same type. + // + // (high address) + // +-----------------+ + // | ....... | + // | ret Y | + // | ....... | + // | ret 0 | + // | arg X | + // | ....... | + // | arg 1 | + // | arg 0 | <-| + // | ReturnAddress | | + // +-----------------+ | + // | ........... | | + // | clobbered M | | argStackOffset: is unknown at this point of compilation. + // | ............ | | + // | clobbered 0 | | + // | spill slot N | | + // | ........... | | + // | spill slot 0 | | + // SP---> +-----------------+ <-+ + // (low address) + + bits := arg.Type.Bits() + // At this point of compilation, we don't yet know how much space exist below the return address. + // So we instruct the address mode to add the `argStackOffset` to the offset at the later phase of compilation. + amode := m.amodePool.Allocate() + *amode = addressMode{imm: arg.Offset, rn: spVReg, kind: addressModeKindArgStackSpace} + load := m.allocateInstr() + switch arg.Type { + case ssa.TypeI32, ssa.TypeI64: + load.asULoad(reg, amode, bits) + case ssa.TypeF32, ssa.TypeF64, ssa.TypeV128: + load.asFpuLoad(reg, amode, bits) + default: + panic("BUG") + } + m.insert(load) + m.unresolvedAddressModes = append(m.unresolvedAddressModes, load) + } + } +} + +// LowerReturns lowers the given returns. +func (m *machine) LowerReturns(rets []ssa.Value) { + a := m.currentABI + + l := len(rets) - 1 + for i := range rets { + // Reverse order in order to avoid overwriting the stack returns existing in the return registers. + ret := rets[l-i] + r := &a.Rets[l-i] + reg := m.compiler.VRegOf(ret) + if def := m.compiler.ValueDefinition(ret); def.IsFromInstr() { + // Constant instructions are inlined. + if inst := def.Instr; inst.Constant() { + val := inst.Return() + valType := val.Type() + v := inst.ConstantVal() + m.insertLoadConstant(v, valType, reg) + } + } + if r.Kind == backend.ABIArgKindReg { + m.InsertMove(r.Reg, reg, ret.Type()) + } else { + // TODO: we could use pair store if there's consecutive stores for the same type. + // + // (high address) + // +-----------------+ + // | ....... | + // | ret Y | + // | ....... | + // | ret 0 | <-+ + // | arg X | | + // | ....... | | + // | arg 1 | | + // | arg 0 | | + // | ReturnAddress | | + // +-----------------+ | + // | ........... | | + // | spill slot M | | retStackOffset: is unknown at this point of compilation. + // | ............ | | + // | spill slot 2 | | + // | spill slot 1 | | + // | clobbered 0 | | + // | clobbered 1 | | + // | ........... | | + // | clobbered N | | + // SP---> +-----------------+ <-+ + // (low address) + + bits := r.Type.Bits() + + // At this point of compilation, we don't yet know how much space exist below the return address. + // So we instruct the address mode to add the `retStackOffset` to the offset at the later phase of compilation. + amode := m.amodePool.Allocate() + *amode = addressMode{imm: r.Offset, rn: spVReg, kind: addressModeKindResultStackSpace} + store := m.allocateInstr() + store.asStore(operandNR(reg), amode, bits) + m.insert(store) + m.unresolvedAddressModes = append(m.unresolvedAddressModes, store) + } + } +} + +// callerGenVRegToFunctionArg is the opposite of GenFunctionArgToVReg, which is used to generate the +// caller side of the function call. +func (m *machine) callerGenVRegToFunctionArg(a *backend.FunctionABI, argIndex int, reg regalloc.VReg, def backend.SSAValueDefinition, slotBegin int64) { + arg := &a.Args[argIndex] + if def.IsFromInstr() { + // Constant instructions are inlined. + if inst := def.Instr; inst.Constant() { + val := inst.Return() + valType := val.Type() + v := inst.ConstantVal() + m.insertLoadConstant(v, valType, reg) + } + } + if arg.Kind == backend.ABIArgKindReg { + m.InsertMove(arg.Reg, reg, arg.Type) + } else { + // TODO: we could use pair store if there's consecutive stores for the same type. + // + // Note that at this point, stack pointer is already adjusted. + bits := arg.Type.Bits() + amode := m.resolveAddressModeForOffset(arg.Offset-slotBegin, bits, spVReg, false) + store := m.allocateInstr() + store.asStore(operandNR(reg), amode, bits) + m.insert(store) + } +} + +func (m *machine) callerGenFunctionReturnVReg(a *backend.FunctionABI, retIndex int, reg regalloc.VReg, slotBegin int64) { + r := &a.Rets[retIndex] + if r.Kind == backend.ABIArgKindReg { + m.InsertMove(reg, r.Reg, r.Type) + } else { + // TODO: we could use pair load if there's consecutive loads for the same type. + amode := m.resolveAddressModeForOffset(a.ArgStackSize+r.Offset-slotBegin, r.Type.Bits(), spVReg, false) + ldr := m.allocateInstr() + switch r.Type { + case ssa.TypeI32, ssa.TypeI64: + ldr.asULoad(reg, amode, r.Type.Bits()) + case ssa.TypeF32, ssa.TypeF64, ssa.TypeV128: + ldr.asFpuLoad(reg, amode, r.Type.Bits()) + default: + panic("BUG") + } + m.insert(ldr) + } +} + +func (m *machine) resolveAddressModeForOffsetAndInsert(cur *instruction, offset int64, dstBits byte, rn regalloc.VReg, allowTmpRegUse bool) (*instruction, *addressMode) { + m.pendingInstructions = m.pendingInstructions[:0] + mode := m.resolveAddressModeForOffset(offset, dstBits, rn, allowTmpRegUse) + for _, instr := range m.pendingInstructions { + cur = linkInstr(cur, instr) + } + return cur, mode +} + +func (m *machine) resolveAddressModeForOffset(offset int64, dstBits byte, rn regalloc.VReg, allowTmpRegUse bool) *addressMode { + if rn.RegType() != regalloc.RegTypeInt { + panic("BUG: rn should be a pointer: " + formatVRegSized(rn, 64)) + } + amode := m.amodePool.Allocate() + if offsetFitsInAddressModeKindRegUnsignedImm12(dstBits, offset) { + *amode = addressMode{kind: addressModeKindRegUnsignedImm12, rn: rn, imm: offset} + } else if offsetFitsInAddressModeKindRegSignedImm9(offset) { + *amode = addressMode{kind: addressModeKindRegSignedImm9, rn: rn, imm: offset} + } else { + var indexReg regalloc.VReg + if allowTmpRegUse { + m.lowerConstantI64(tmpRegVReg, offset) + indexReg = tmpRegVReg + } else { + indexReg = m.compiler.AllocateVReg(ssa.TypeI64) + m.lowerConstantI64(indexReg, offset) + } + *amode = addressMode{kind: addressModeKindRegReg, rn: rn, rm: indexReg, extOp: extendOpUXTX /* indicates index rm is 64-bit */} + } + return amode +} + +func (m *machine) lowerCall(si *ssa.Instruction) { + isDirectCall := si.Opcode() == ssa.OpcodeCall + indirectCalleePtr, directCallee, calleeABI, stackSlotSize := m.prepareCall(si, isDirectCall) + + if isDirectCall { + call := m.allocateInstr() + call.asCall(directCallee, calleeABI) + m.insert(call) + } else { + ptr := m.compiler.VRegOf(indirectCalleePtr) + callInd := m.allocateInstr() + callInd.asCallIndirect(ptr, calleeABI) + m.insert(callInd) + } + + m.insertReturns(si, calleeABI, stackSlotSize) +} + +func (m *machine) prepareCall(si *ssa.Instruction, isDirectCall bool) (ssa.Value, ssa.FuncRef, *backend.FunctionABI, int64) { + var indirectCalleePtr ssa.Value + var directCallee ssa.FuncRef + var sigID ssa.SignatureID + var args []ssa.Value + if isDirectCall { + directCallee, sigID, args = si.CallData() + } else { + indirectCalleePtr, sigID, args, _ /* on arm64, the calling convention is compatible with the Go runtime */ = si.CallIndirectData() + } + calleeABI := m.compiler.GetFunctionABI(m.compiler.SSABuilder().ResolveSignature(sigID)) + + stackSlotSize := int64(calleeABI.AlignedArgResultStackSlotSize()) + if m.maxRequiredStackSizeForCalls < stackSlotSize+16 { + m.maxRequiredStackSizeForCalls = stackSlotSize + 16 // return address frame. + } + + for i, arg := range args { + reg := m.compiler.VRegOf(arg) + def := m.compiler.ValueDefinition(arg) + m.callerGenVRegToFunctionArg(calleeABI, i, reg, def, stackSlotSize) + } + return indirectCalleePtr, directCallee, calleeABI, stackSlotSize +} + +func (m *machine) insertReturns(si *ssa.Instruction, calleeABI *backend.FunctionABI, stackSlotSize int64) { + var index int + r1, rs := si.Returns() + if r1.Valid() { + m.callerGenFunctionReturnVReg(calleeABI, 0, m.compiler.VRegOf(r1), stackSlotSize) + index++ + } + + for _, r := range rs { + m.callerGenFunctionReturnVReg(calleeABI, index, m.compiler.VRegOf(r), stackSlotSize) + index++ + } +} + +func (m *machine) lowerTailCall(si *ssa.Instruction) { + isDirectCall := si.Opcode() == ssa.OpcodeTailCallReturnCall + indirectCalleePtr, directCallee, calleeABI, stackSlotSize := m.prepareCall(si, isDirectCall) + + // We currently support tail calls only when the args are passed via registers + // otherwise we fall back to a plain call. + // For details, see internal/engine/RATIONALE.md + isAllRegs := stackSlotSize == 0 + + switch { + case isDirectCall && isAllRegs: + tailJump := m.allocateInstr() + tailJump.asTailCall(directCallee, calleeABI) + m.insert(tailJump) + case !isDirectCall && isAllRegs: + ptr := m.compiler.VRegOf(indirectCalleePtr) + callInd := m.allocateInstr() + callInd.asTailCallIndirect(ptr, calleeABI) + m.insert(callInd) + case isDirectCall && !isAllRegs: + tailJump := m.allocateInstr() + tailJump.asCall(directCallee, calleeABI) + m.insert(tailJump) + case !isDirectCall && !isAllRegs: + ptr := m.compiler.VRegOf(indirectCalleePtr) + callInd := m.allocateInstr() + callInd.asCallIndirect(ptr, calleeABI) + m.insert(callInd) + } + + // If this is a proper tail call, returns will be cleared in the postRegAlloc phase. + m.insertReturns(si, calleeABI, stackSlotSize) +} + +func (m *machine) insertAddOrSubStackPointer(rd regalloc.VReg, diff int64, add bool) { + if imm12Operand, ok := asImm12Operand(uint64(diff)); ok { + alu := m.allocateInstr() + var ao aluOp + if add { + ao = aluOpAdd + } else { + ao = aluOpSub + } + alu.asALU(ao, rd, operandNR(spVReg), imm12Operand, true) + m.insert(alu) + } else { + m.lowerConstantI64(tmpRegVReg, diff) + alu := m.allocateInstr() + var ao aluOp + if add { + ao = aluOpAdd + } else { + ao = aluOpSub + } + alu.asALU(ao, rd, operandNR(spVReg), operandNR(tmpRegVReg), true) + m.insert(alu) + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi_entry_arm64.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi_entry_arm64.go new file mode 100644 index 00000000000..5f0c613df86 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi_entry_arm64.go @@ -0,0 +1,9 @@ +package arm64 + +// entrypoint enters the machine code generated by this backend which begins with the preamble generated by functionABI.EmitGoEntryPreamble below. +// This implements wazevo.entrypoint, and see the comments there for detail. +func entrypoint(preambleExecutable, functionExecutable *byte, executionContextPtr uintptr, moduleContextPtr *byte, paramResultPtr *uint64, goAllocatedStackSlicePtr uintptr) + +// afterGoFunctionCallEntrypoint enters the machine code after growing the stack. +// This implements wazevo.afterGoFunctionCallEntrypoint, and see the comments there for detail. +func afterGoFunctionCallEntrypoint(executable *byte, executionContextPtr uintptr, stackPointer, framePointer uintptr) diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi_entry_arm64.s b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi_entry_arm64.s new file mode 100644 index 00000000000..0b579f8525d --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi_entry_arm64.s @@ -0,0 +1,29 @@ +//go:build arm64 + +#include "funcdata.h" +#include "textflag.h" + +// See the comments on EmitGoEntryPreamble for what this function is supposed to do. +TEXT ·entrypoint(SB), NOSPLIT|NOFRAME, $0-48 + MOVD preambleExecutable+0(FP), R27 + MOVD functionExectuable+8(FP), R24 + MOVD executionContextPtr+16(FP), R0 + MOVD moduleContextPtr+24(FP), R1 + MOVD paramResultSlicePtr+32(FP), R19 + MOVD goAllocatedStackSlicePtr+40(FP), R26 + JMP (R27) + +TEXT ·afterGoFunctionCallEntrypoint(SB), NOSPLIT|NOFRAME, $0-32 + MOVD goCallReturnAddress+0(FP), R20 + MOVD executionContextPtr+8(FP), R0 + MOVD stackPointer+16(FP), R19 + + // Save the current FP(R29), SP and LR(R30) into the wazevo.executionContext (stored in R0). + MOVD R29, 16(R0) // Store FP(R29) into [RO, #ExecutionContextOffsets.OriginalFramePointer] + MOVD RSP, R27 // Move SP to R27 (temporary register) since SP cannot be stored directly in str instructions. + MOVD R27, 24(R0) // Store R27 into [RO, #ExecutionContextOffsets.OriginalFramePointer] + MOVD R30, 32(R0) // Store R30 into [R0, #ExecutionContextOffsets.GoReturnAddress] + + // Load the new stack pointer (which sits somewhere in Go-allocated stack) into SP. + MOVD R19, RSP + JMP (R20) diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi_entry_preamble.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi_entry_preamble.go new file mode 100644 index 00000000000..f8b5d97ac77 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi_entry_preamble.go @@ -0,0 +1,233 @@ +package arm64 + +import ( + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +// CompileEntryPreamble implements backend.Machine. This assumes `entrypoint` function (in abi_go_entry_arm64.s) passes: +// +// 1. First (execution context ptr) and Second arguments are already passed in x0, and x1. +// 2. param/result slice ptr in x19; the pointer to []uint64{} which is used to pass arguments and accept return values. +// 3. Go-allocated stack slice ptr in x26. +// 4. Function executable in x24. +// +// also SP and FP are correct Go-runtime-based values, and LR is the return address to the Go-side caller. +func (m *machine) CompileEntryPreamble(signature *ssa.Signature) []byte { + root := m.constructEntryPreamble(signature) + m.encode(root) + return m.compiler.Buf() +} + +var ( + executionContextPtrReg = x0VReg + // callee-saved regs so that they can be used in the prologue and epilogue. + paramResultSlicePtr = x19VReg + savedExecutionContextPtr = x20VReg + // goAllocatedStackPtr is not used in the epilogue. + goAllocatedStackPtr = x26VReg + // paramResultSliceCopied is not used in the epilogue. + paramResultSliceCopied = x25VReg + // tmpRegVReg is not used in the epilogue. + functionExecutable = x24VReg +) + +func (m *machine) goEntryPreamblePassArg(cur *instruction, paramSlicePtr regalloc.VReg, arg *backend.ABIArg, argStartOffsetFromSP int64) *instruction { + typ := arg.Type + bits := typ.Bits() + isStackArg := arg.Kind == backend.ABIArgKindStack + + var loadTargetReg operand + if !isStackArg { + loadTargetReg = operandNR(arg.Reg) + } else { + switch typ { + case ssa.TypeI32, ssa.TypeI64: + loadTargetReg = operandNR(x15VReg) + case ssa.TypeF32, ssa.TypeF64, ssa.TypeV128: + loadTargetReg = operandNR(v15VReg) + default: + panic("TODO?") + } + } + + var postIndexImm int64 + if typ == ssa.TypeV128 { + postIndexImm = 16 // v128 is represented as 2x64-bit in Go slice. + } else { + postIndexImm = 8 + } + loadMode := m.amodePool.Allocate() + *loadMode = addressMode{kind: addressModeKindPostIndex, rn: paramSlicePtr, imm: postIndexImm} + + instr := m.allocateInstr() + switch typ { + case ssa.TypeI32: + instr.asULoad(loadTargetReg.reg(), loadMode, 32) + case ssa.TypeI64: + instr.asULoad(loadTargetReg.reg(), loadMode, 64) + case ssa.TypeF32: + instr.asFpuLoad(loadTargetReg.reg(), loadMode, 32) + case ssa.TypeF64: + instr.asFpuLoad(loadTargetReg.reg(), loadMode, 64) + case ssa.TypeV128: + instr.asFpuLoad(loadTargetReg.reg(), loadMode, 128) + } + cur = linkInstr(cur, instr) + + if isStackArg { + var storeMode *addressMode + cur, storeMode = m.resolveAddressModeForOffsetAndInsert(cur, argStartOffsetFromSP+arg.Offset, bits, spVReg, true) + toStack := m.allocateInstr() + toStack.asStore(loadTargetReg, storeMode, bits) + cur = linkInstr(cur, toStack) + } + return cur +} + +func (m *machine) goEntryPreamblePassResult(cur *instruction, resultSlicePtr regalloc.VReg, result *backend.ABIArg, resultStartOffsetFromSP int64) *instruction { + isStackArg := result.Kind == backend.ABIArgKindStack + typ := result.Type + bits := typ.Bits() + + var storeTargetReg operand + if !isStackArg { + storeTargetReg = operandNR(result.Reg) + } else { + switch typ { + case ssa.TypeI32, ssa.TypeI64: + storeTargetReg = operandNR(x15VReg) + case ssa.TypeF32, ssa.TypeF64, ssa.TypeV128: + storeTargetReg = operandNR(v15VReg) + default: + panic("TODO?") + } + } + + var postIndexImm int64 + if typ == ssa.TypeV128 { + postIndexImm = 16 // v128 is represented as 2x64-bit in Go slice. + } else { + postIndexImm = 8 + } + + if isStackArg { + var loadMode *addressMode + cur, loadMode = m.resolveAddressModeForOffsetAndInsert(cur, resultStartOffsetFromSP+result.Offset, bits, spVReg, true) + toReg := m.allocateInstr() + switch typ { + case ssa.TypeI32, ssa.TypeI64: + toReg.asULoad(storeTargetReg.reg(), loadMode, bits) + case ssa.TypeF32, ssa.TypeF64, ssa.TypeV128: + toReg.asFpuLoad(storeTargetReg.reg(), loadMode, bits) + default: + panic("TODO?") + } + cur = linkInstr(cur, toReg) + } + + mode := m.amodePool.Allocate() + *mode = addressMode{kind: addressModeKindPostIndex, rn: resultSlicePtr, imm: postIndexImm} + instr := m.allocateInstr() + instr.asStore(storeTargetReg, mode, bits) + cur = linkInstr(cur, instr) + return cur +} + +func (m *machine) constructEntryPreamble(sig *ssa.Signature) (root *instruction) { + abi := backend.FunctionABI{} + abi.Init(sig, intParamResultRegs, floatParamResultRegs) + + root = m.allocateNop() + + //// ----------------------------------- prologue ----------------------------------- //// + + // First, we save executionContextPtrReg into a callee-saved register so that it can be used in epilogue as well. + // mov savedExecutionContextPtr, x0 + cur := m.move64(savedExecutionContextPtr, executionContextPtrReg, root) + + // Next, save the current FP, SP and LR into the wazevo.executionContext: + // str fp, [savedExecutionContextPtr, #OriginalFramePointer] + // mov tmp, sp ;; sp cannot be str'ed directly. + // str sp, [savedExecutionContextPtr, #OriginalStackPointer] + // str lr, [savedExecutionContextPtr, #GoReturnAddress] + cur = m.loadOrStoreAtExecutionContext(fpVReg, wazevoapi.ExecutionContextOffsetOriginalFramePointer, true, cur) + cur = m.move64(tmpRegVReg, spVReg, cur) + cur = m.loadOrStoreAtExecutionContext(tmpRegVReg, wazevoapi.ExecutionContextOffsetOriginalStackPointer, true, cur) + cur = m.loadOrStoreAtExecutionContext(lrVReg, wazevoapi.ExecutionContextOffsetGoReturnAddress, true, cur) + + // Then, move the Go-allocated stack pointer to SP: + // mov sp, goAllocatedStackPtr + cur = m.move64(spVReg, goAllocatedStackPtr, cur) + + prReg := paramResultSlicePtr + if len(abi.Args) > 2 && len(abi.Rets) > 0 { + // paramResultSlicePtr is modified during the execution of goEntryPreamblePassArg, + // so copy it to another reg. + cur = m.move64(paramResultSliceCopied, paramResultSlicePtr, cur) + prReg = paramResultSliceCopied + } + + stackSlotSize := int64(abi.AlignedArgResultStackSlotSize()) + for i := range abi.Args { + if i < 2 { + // module context ptr and execution context ptr are passed in x0 and x1 by the Go assembly function. + continue + } + arg := &abi.Args[i] + cur = m.goEntryPreamblePassArg(cur, prReg, arg, -stackSlotSize) + } + + // Call the real function. + bl := m.allocateInstr() + bl.asCallIndirect(functionExecutable, &abi) + cur = linkInstr(cur, bl) + + ///// ----------------------------------- epilogue ----------------------------------- ///// + + // Store the register results into paramResultSlicePtr. + for i := range abi.Rets { + cur = m.goEntryPreamblePassResult(cur, paramResultSlicePtr, &abi.Rets[i], abi.ArgStackSize-stackSlotSize) + } + + // Finally, restore the FP, SP and LR, and return to the Go code. + // ldr fp, [savedExecutionContextPtr, #OriginalFramePointer] + // ldr tmp, [savedExecutionContextPtr, #OriginalStackPointer] + // mov sp, tmp ;; sp cannot be str'ed directly. + // ldr lr, [savedExecutionContextPtr, #GoReturnAddress] + // ret ;; --> return to the Go code + cur = m.loadOrStoreAtExecutionContext(fpVReg, wazevoapi.ExecutionContextOffsetOriginalFramePointer, false, cur) + cur = m.loadOrStoreAtExecutionContext(tmpRegVReg, wazevoapi.ExecutionContextOffsetOriginalStackPointer, false, cur) + cur = m.move64(spVReg, tmpRegVReg, cur) + cur = m.loadOrStoreAtExecutionContext(lrVReg, wazevoapi.ExecutionContextOffsetGoReturnAddress, false, cur) + retInst := m.allocateInstr() + retInst.asRet() + linkInstr(cur, retInst) + return +} + +func (m *machine) move64(dst, src regalloc.VReg, prev *instruction) *instruction { + instr := m.allocateInstr() + instr.asMove64(dst, src) + return linkInstr(prev, instr) +} + +func (m *machine) loadOrStoreAtExecutionContext(d regalloc.VReg, offset wazevoapi.Offset, store bool, prev *instruction) *instruction { + instr := m.allocateInstr() + mode := m.amodePool.Allocate() + *mode = addressMode{kind: addressModeKindRegUnsignedImm12, rn: savedExecutionContextPtr, imm: offset.I64()} + if store { + instr.asStore(operandNR(d), mode, 64) + } else { + instr.asULoad(d, mode, 64) + } + return linkInstr(prev, instr) +} + +func linkInstr(prev, next *instruction) *instruction { + prev.next = next + next.prev = prev + return next +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi_go_call.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi_go_call.go new file mode 100644 index 00000000000..06f8a4a0536 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi_go_call.go @@ -0,0 +1,430 @@ +package arm64 + +import ( + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +var calleeSavedRegistersSorted = []regalloc.VReg{ + x19VReg, x20VReg, x21VReg, x22VReg, x23VReg, x24VReg, x25VReg, x26VReg, x28VReg, + v18VReg, v19VReg, v20VReg, v21VReg, v22VReg, v23VReg, v24VReg, v25VReg, v26VReg, v27VReg, v28VReg, v29VReg, v30VReg, v31VReg, +} + +// CompileGoFunctionTrampoline implements backend.Machine. +func (m *machine) CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *ssa.Signature, needModuleContextPtr bool) []byte { + argBegin := 1 // Skips exec context by default. + if needModuleContextPtr { + argBegin++ + } + + abi := &backend.FunctionABI{} + abi.Init(sig, intParamResultRegs, floatParamResultRegs) + m.currentABI = abi + + cur := m.allocateInstr() + cur.asNop0() + m.rootInstr = cur + + // Execution context is always the first argument. + execCtrPtr := x0VReg + + // In the following, we create the following stack layout: + // + // (high address) + // SP ------> +-----------------+ <----+ + // | ....... | | + // | ret Y | | + // | ....... | | + // | ret 0 | | + // | arg X | | size_of_arg_ret + // | ....... | | + // | arg 1 | | + // | arg 0 | <----+ <-------- originalArg0Reg + // | size_of_arg_ret | + // | ReturnAddress | + // +-----------------+ <----+ + // | xxxx | | ;; might be padded to make it 16-byte aligned. + // +--->| arg[N]/ret[M] | | + // sliceSize| | ............ | | goCallStackSize + // | | arg[1]/ret[1] | | + // +--->| arg[0]/ret[0] | <----+ <-------- arg0ret0AddrReg + // | sliceSize | + // | frame_size | + // +-----------------+ + // (low address) + // + // where the region of "arg[0]/ret[0] ... arg[N]/ret[M]" is the stack used by the Go functions, + // therefore will be accessed as the usual []uint64. So that's where we need to pass/receive + // the arguments/return values. + + // First of all, to update the SP, and create "ReturnAddress + size_of_arg_ret". + cur = m.createReturnAddrAndSizeOfArgRetSlot(cur) + + const frameInfoSize = 16 // == frame_size + sliceSize. + + // Next, we should allocate the stack for the Go function call if necessary. + goCallStackSize, sliceSizeInBytes := backend.GoFunctionCallRequiredStackSize(sig, argBegin) + cur = m.insertStackBoundsCheck(goCallStackSize+frameInfoSize, cur) + + originalArg0Reg := x17VReg // Caller save, so we can use it for whatever we want. + if m.currentABI.AlignedArgResultStackSlotSize() > 0 { + // At this point, SP points to `ReturnAddress`, so add 16 to get the original arg 0 slot. + cur = m.addsAddOrSubStackPointer(cur, originalArg0Reg, frameInfoSize, true) + } + + // Save the callee saved registers. + cur = m.saveRegistersInExecutionContext(cur, calleeSavedRegistersSorted) + + if needModuleContextPtr { + offset := wazevoapi.ExecutionContextOffsetGoFunctionCallCalleeModuleContextOpaque.I64() + if !offsetFitsInAddressModeKindRegUnsignedImm12(64, offset) { + panic("BUG: too large or un-aligned offset for goFunctionCallCalleeModuleContextOpaque in execution context") + } + + // Module context is always the second argument. + moduleCtrPtr := x1VReg + store := m.allocateInstr() + amode := m.amodePool.Allocate() + *amode = addressMode{kind: addressModeKindRegUnsignedImm12, rn: execCtrPtr, imm: offset} + store.asStore(operandNR(moduleCtrPtr), amode, 64) + cur = linkInstr(cur, store) + } + + // Advances the stack pointer. + cur = m.addsAddOrSubStackPointer(cur, spVReg, goCallStackSize, false) + + // Copy the pointer to x15VReg. + arg0ret0AddrReg := x15VReg // Caller save, so we can use it for whatever we want. + copySp := m.allocateInstr() + copySp.asMove64(arg0ret0AddrReg, spVReg) + cur = linkInstr(cur, copySp) + + // Next, we need to store all the arguments to the stack in the typical Wasm stack style. + for i := range abi.Args[argBegin:] { + arg := &abi.Args[argBegin+i] + store := m.allocateInstr() + var v regalloc.VReg + if arg.Kind == backend.ABIArgKindReg { + v = arg.Reg + } else { + cur, v = m.goFunctionCallLoadStackArg(cur, originalArg0Reg, arg, + // Caller save, so we can use it for whatever we want. + x11VReg, v11VReg) + } + + var sizeInBits byte + if arg.Type == ssa.TypeV128 { + sizeInBits = 128 + } else { + sizeInBits = 64 + } + amode := m.amodePool.Allocate() + *amode = addressMode{kind: addressModeKindPostIndex, rn: arg0ret0AddrReg, imm: int64(sizeInBits / 8)} + store.asStore(operandNR(v), amode, sizeInBits) + cur = linkInstr(cur, store) + } + + // Finally, now that we've advanced SP to arg[0]/ret[0], we allocate `frame_size + sliceSize`. + var frameSizeReg, sliceSizeReg regalloc.VReg + if goCallStackSize > 0 { + cur = m.lowerConstantI64AndInsert(cur, tmpRegVReg, goCallStackSize) + frameSizeReg = tmpRegVReg + cur = m.lowerConstantI64AndInsert(cur, x16VReg, sliceSizeInBytes/8) + sliceSizeReg = x16VReg + } else { + frameSizeReg = xzrVReg + sliceSizeReg = xzrVReg + } + _amode := addressModePreOrPostIndex(m, spVReg, -16, true) + storeP := m.allocateInstr() + storeP.asStorePair64(frameSizeReg, sliceSizeReg, _amode) + cur = linkInstr(cur, storeP) + + // Set the exit status on the execution context. + cur = m.setExitCode(cur, x0VReg, exitCode) + + // Save the current stack pointer. + cur = m.saveCurrentStackPointer(cur, x0VReg) + + // Exit the execution. + cur = m.storeReturnAddressAndExit(cur) + + // After the call, we need to restore the callee saved registers. + cur = m.restoreRegistersInExecutionContext(cur, calleeSavedRegistersSorted) + + // Get the pointer to the arg[0]/ret[0]: We need to skip `frame_size + sliceSize`. + if len(abi.Rets) > 0 { + cur = m.addsAddOrSubStackPointer(cur, arg0ret0AddrReg, frameInfoSize, true) + } + + // Advances the SP so that it points to `ReturnAddress`. + cur = m.addsAddOrSubStackPointer(cur, spVReg, frameInfoSize+goCallStackSize, true) + ldr := m.allocateInstr() + // And load the return address. + amode := addressModePreOrPostIndex(m, spVReg, 16 /* stack pointer must be 16-byte aligned. */, false /* increment after loads */) + ldr.asULoad(lrVReg, amode, 64) + cur = linkInstr(cur, ldr) + + originalRet0Reg := x17VReg // Caller save, so we can use it for whatever we want. + if m.currentABI.RetStackSize > 0 { + cur = m.addsAddOrSubStackPointer(cur, originalRet0Reg, m.currentABI.ArgStackSize, true) + } + + // Make the SP point to the original address (above the result slot). + if s := int64(m.currentABI.AlignedArgResultStackSlotSize()); s > 0 { + cur = m.addsAddOrSubStackPointer(cur, spVReg, s, true) + } + + for i := range abi.Rets { + r := &abi.Rets[i] + if r.Kind == backend.ABIArgKindReg { + loadIntoReg := m.allocateInstr() + mode := m.amodePool.Allocate() + *mode = addressMode{kind: addressModeKindPostIndex, rn: arg0ret0AddrReg} + switch r.Type { + case ssa.TypeI32: + mode.imm = 8 // We use uint64 for all basic types, except SIMD v128. + loadIntoReg.asULoad(r.Reg, mode, 32) + case ssa.TypeI64: + mode.imm = 8 // We use uint64 for all basic types, except SIMD v128. + loadIntoReg.asULoad(r.Reg, mode, 64) + case ssa.TypeF32: + mode.imm = 8 // We use uint64 for all basic types, except SIMD v128. + loadIntoReg.asFpuLoad(r.Reg, mode, 32) + case ssa.TypeF64: + mode.imm = 8 // We use uint64 for all basic types, except SIMD v128. + loadIntoReg.asFpuLoad(r.Reg, mode, 64) + case ssa.TypeV128: + mode.imm = 16 + loadIntoReg.asFpuLoad(r.Reg, mode, 128) + default: + panic("TODO") + } + cur = linkInstr(cur, loadIntoReg) + } else { + // First we need to load the value to a temporary just like ^^. + intTmp, floatTmp := x11VReg, v11VReg + loadIntoTmpReg := m.allocateInstr() + mode := m.amodePool.Allocate() + *mode = addressMode{kind: addressModeKindPostIndex, rn: arg0ret0AddrReg} + var resultReg regalloc.VReg + switch r.Type { + case ssa.TypeI32: + mode.imm = 8 // We use uint64 for all basic types, except SIMD v128. + loadIntoTmpReg.asULoad(intTmp, mode, 32) + resultReg = intTmp + case ssa.TypeI64: + mode.imm = 8 // We use uint64 for all basic types, except SIMD v128. + loadIntoTmpReg.asULoad(intTmp, mode, 64) + resultReg = intTmp + case ssa.TypeF32: + mode.imm = 8 // We use uint64 for all basic types, except SIMD v128. + loadIntoTmpReg.asFpuLoad(floatTmp, mode, 32) + resultReg = floatTmp + case ssa.TypeF64: + mode.imm = 8 // We use uint64 for all basic types, except SIMD v128. + loadIntoTmpReg.asFpuLoad(floatTmp, mode, 64) + resultReg = floatTmp + case ssa.TypeV128: + mode.imm = 16 + loadIntoTmpReg.asFpuLoad(floatTmp, mode, 128) + resultReg = floatTmp + default: + panic("TODO") + } + cur = linkInstr(cur, loadIntoTmpReg) + cur = m.goFunctionCallStoreStackResult(cur, originalRet0Reg, r, resultReg) + } + } + + ret := m.allocateInstr() + ret.asRet() + linkInstr(cur, ret) + + m.encode(m.rootInstr) + return m.compiler.Buf() +} + +func (m *machine) saveRegistersInExecutionContext(cur *instruction, regs []regalloc.VReg) *instruction { + offset := wazevoapi.ExecutionContextOffsetSavedRegistersBegin.I64() + for _, v := range regs { + store := m.allocateInstr() + var sizeInBits byte + switch v.RegType() { + case regalloc.RegTypeInt: + sizeInBits = 64 + case regalloc.RegTypeFloat: + sizeInBits = 128 + } + mode := m.amodePool.Allocate() + *mode = addressMode{ + kind: addressModeKindRegUnsignedImm12, + // Execution context is always the first argument. + rn: x0VReg, imm: offset, + } + store.asStore(operandNR(v), mode, sizeInBits) + store.prev = cur + cur.next = store + cur = store + offset += 16 // Imm12 must be aligned 16 for vector regs, so we unconditionally store regs at the offset of multiple of 16. + } + return cur +} + +func (m *machine) restoreRegistersInExecutionContext(cur *instruction, regs []regalloc.VReg) *instruction { + offset := wazevoapi.ExecutionContextOffsetSavedRegistersBegin.I64() + for _, v := range regs { + load := m.allocateInstr() + var as func(dst regalloc.VReg, amode *addressMode, sizeInBits byte) + var sizeInBits byte + switch v.RegType() { + case regalloc.RegTypeInt: + as = load.asULoad + sizeInBits = 64 + case regalloc.RegTypeFloat: + as = load.asFpuLoad + sizeInBits = 128 + } + mode := m.amodePool.Allocate() + *mode = addressMode{ + kind: addressModeKindRegUnsignedImm12, + // Execution context is always the first argument. + rn: x0VReg, imm: offset, + } + as(v, mode, sizeInBits) + cur = linkInstr(cur, load) + offset += 16 // Imm12 must be aligned 16 for vector regs, so we unconditionally load regs at the offset of multiple of 16. + } + return cur +} + +func (m *machine) lowerConstantI64AndInsert(cur *instruction, dst regalloc.VReg, v int64) *instruction { + m.pendingInstructions = m.pendingInstructions[:0] + m.lowerConstantI64(dst, v) + for _, instr := range m.pendingInstructions { + cur = linkInstr(cur, instr) + } + return cur +} + +func (m *machine) lowerConstantI32AndInsert(cur *instruction, dst regalloc.VReg, v int32) *instruction { + m.pendingInstructions = m.pendingInstructions[:0] + m.lowerConstantI32(dst, v) + for _, instr := range m.pendingInstructions { + cur = linkInstr(cur, instr) + } + return cur +} + +func (m *machine) setExitCode(cur *instruction, execCtr regalloc.VReg, exitCode wazevoapi.ExitCode) *instruction { + constReg := x17VReg // caller-saved, so we can use it. + cur = m.lowerConstantI32AndInsert(cur, constReg, int32(exitCode)) + + // Set the exit status on the execution context. + setExistStatus := m.allocateInstr() + mode := m.amodePool.Allocate() + *mode = addressMode{kind: addressModeKindRegUnsignedImm12, rn: execCtr, imm: wazevoapi.ExecutionContextOffsetExitCodeOffset.I64()} + setExistStatus.asStore(operandNR(constReg), mode, 32) + cur = linkInstr(cur, setExistStatus) + return cur +} + +func (m *machine) storeReturnAddressAndExit(cur *instruction) *instruction { + // Read the return address into tmp, and store it in the execution context. + adr := m.allocateInstr() + adr.asAdr(tmpRegVReg, exitSequenceSize+8) + cur = linkInstr(cur, adr) + + storeReturnAddr := m.allocateInstr() + mode := m.amodePool.Allocate() + *mode = addressMode{ + kind: addressModeKindRegUnsignedImm12, + // Execution context is always the first argument. + rn: x0VReg, imm: wazevoapi.ExecutionContextOffsetGoCallReturnAddress.I64(), + } + storeReturnAddr.asStore(operandNR(tmpRegVReg), mode, 64) + cur = linkInstr(cur, storeReturnAddr) + + // Exit the execution. + trapSeq := m.allocateInstr() + trapSeq.asExitSequence(x0VReg) + cur = linkInstr(cur, trapSeq) + return cur +} + +func (m *machine) saveCurrentStackPointer(cur *instruction, execCtr regalloc.VReg) *instruction { + // Save the current stack pointer: + // mov tmp, sp, + // str tmp, [exec_ctx, #stackPointerBeforeGoCall] + movSp := m.allocateInstr() + movSp.asMove64(tmpRegVReg, spVReg) + cur = linkInstr(cur, movSp) + + strSp := m.allocateInstr() + mode := m.amodePool.Allocate() + *mode = addressMode{ + kind: addressModeKindRegUnsignedImm12, + rn: execCtr, imm: wazevoapi.ExecutionContextOffsetStackPointerBeforeGoCall.I64(), + } + strSp.asStore(operandNR(tmpRegVReg), mode, 64) + cur = linkInstr(cur, strSp) + return cur +} + +func (m *machine) goFunctionCallLoadStackArg(cur *instruction, originalArg0Reg regalloc.VReg, arg *backend.ABIArg, intVReg, floatVReg regalloc.VReg) (*instruction, regalloc.VReg) { + load := m.allocateInstr() + var result regalloc.VReg + mode := m.amodePool.Allocate() + *mode = addressMode{kind: addressModeKindPostIndex, rn: originalArg0Reg} + switch arg.Type { + case ssa.TypeI32: + mode.imm = 8 // We use uint64 for all basic types, except SIMD v128. + load.asULoad(intVReg, mode, 32) + result = intVReg + case ssa.TypeI64: + mode.imm = 8 // We use uint64 for all basic types, except SIMD v128. + load.asULoad(intVReg, mode, 64) + result = intVReg + case ssa.TypeF32: + mode.imm = 8 // We use uint64 for all basic types, except SIMD v128. + load.asFpuLoad(floatVReg, mode, 32) + result = floatVReg + case ssa.TypeF64: + mode.imm = 8 // We use uint64 for all basic types, except SIMD v128. + load.asFpuLoad(floatVReg, mode, 64) + result = floatVReg + case ssa.TypeV128: + mode.imm = 16 + load.asFpuLoad(floatVReg, mode, 128) + result = floatVReg + default: + panic("TODO") + } + + cur = linkInstr(cur, load) + return cur, result +} + +func (m *machine) goFunctionCallStoreStackResult(cur *instruction, originalRet0Reg regalloc.VReg, result *backend.ABIArg, resultVReg regalloc.VReg) *instruction { + store := m.allocateInstr() + mode := m.amodePool.Allocate() + *mode = addressMode{kind: addressModeKindPostIndex, rn: originalRet0Reg} + var sizeInBits byte + switch result.Type { + case ssa.TypeI32, ssa.TypeF32: + mode.imm = 8 + sizeInBits = 32 + case ssa.TypeI64, ssa.TypeF64: + mode.imm = 8 + sizeInBits = 64 + case ssa.TypeV128: + mode.imm = 16 + sizeInBits = 128 + default: + panic("TODO") + } + store.asStore(operandNR(resultVReg), mode, sizeInBits) + return linkInstr(cur, store) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/cond.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/cond.go new file mode 100644 index 00000000000..6f6cdd1b2e9 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/cond.go @@ -0,0 +1,215 @@ +package arm64 + +import ( + "strconv" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" +) + +type ( + cond uint64 + condKind byte +) + +const ( + // condKindRegisterZero represents a condition which checks if the register is zero. + // This indicates that the instruction must be encoded as CBZ: + // https://developer.arm.com/documentation/ddi0596/2020-12/Base-Instructions/CBZ--Compare-and-Branch-on-Zero- + condKindRegisterZero condKind = iota + // condKindRegisterNotZero indicates that the instruction must be encoded as CBNZ: + // https://developer.arm.com/documentation/ddi0596/2020-12/Base-Instructions/CBNZ--Compare-and-Branch-on-Nonzero- + condKindRegisterNotZero + // condKindCondFlagSet indicates that the instruction must be encoded as B.cond: + // https://developer.arm.com/documentation/ddi0596/2020-12/Base-Instructions/B-cond--Branch-conditionally- + condKindCondFlagSet +) + +// kind returns the kind of condition which is stored in the first two bits. +func (c cond) kind() condKind { + return condKind(c & 0b11) +} + +func (c cond) asUint64() uint64 { + return uint64(c) +} + +// register returns the register for register conditions. +// This panics if the condition is not a register condition (condKindRegisterZero or condKindRegisterNotZero). +func (c cond) register() regalloc.VReg { + if c.kind() != condKindRegisterZero && c.kind() != condKindRegisterNotZero { + panic("condition is not a register") + } + return regalloc.VReg(c >> 2) +} + +func registerAsRegZeroCond(r regalloc.VReg) cond { + return cond(r)<<2 | cond(condKindRegisterZero) +} + +func registerAsRegNotZeroCond(r regalloc.VReg) cond { + return cond(r)<<2 | cond(condKindRegisterNotZero) +} + +func (c cond) flag() condFlag { + if c.kind() != condKindCondFlagSet { + panic("condition is not a flag") + } + return condFlag(c >> 2) +} + +func (c condFlag) asCond() cond { + return cond(c)<<2 | cond(condKindCondFlagSet) +} + +// condFlag represents a condition flag for conditional branches. +// The value matches the encoding of condition flags in the ARM64 instruction set. +// https://developer.arm.com/documentation/den0024/a/The-A64-instruction-set/Data-processing-instructions/Conditional-instructions +type condFlag uint8 + +const ( + eq condFlag = iota // eq represents "equal" + ne // ne represents "not equal" + hs // hs represents "higher or same" + lo // lo represents "lower" + mi // mi represents "minus or negative result" + pl // pl represents "plus or positive result" + vs // vs represents "overflow set" + vc // vc represents "overflow clear" + hi // hi represents "higher" + ls // ls represents "lower or same" + ge // ge represents "greater or equal" + lt // lt represents "less than" + gt // gt represents "greater than" + le // le represents "less than or equal" + al // al represents "always" + nv // nv represents "never" +) + +// invert returns the inverted condition. +func (c condFlag) invert() condFlag { + switch c { + case eq: + return ne + case ne: + return eq + case hs: + return lo + case lo: + return hs + case mi: + return pl + case pl: + return mi + case vs: + return vc + case vc: + return vs + case hi: + return ls + case ls: + return hi + case ge: + return lt + case lt: + return ge + case gt: + return le + case le: + return gt + case al: + return nv + case nv: + return al + default: + panic(c) + } +} + +// String implements fmt.Stringer. +func (c condFlag) String() string { + switch c { + case eq: + return "eq" + case ne: + return "ne" + case hs: + return "hs" + case lo: + return "lo" + case mi: + return "mi" + case pl: + return "pl" + case vs: + return "vs" + case vc: + return "vc" + case hi: + return "hi" + case ls: + return "ls" + case ge: + return "ge" + case lt: + return "lt" + case gt: + return "gt" + case le: + return "le" + case al: + return "al" + case nv: + return "nv" + default: + panic(strconv.Itoa(int(c))) + } +} + +// condFlagFromSSAIntegerCmpCond returns the condition flag for the given ssa.IntegerCmpCond. +func condFlagFromSSAIntegerCmpCond(c ssa.IntegerCmpCond) condFlag { + switch c { + case ssa.IntegerCmpCondEqual: + return eq + case ssa.IntegerCmpCondNotEqual: + return ne + case ssa.IntegerCmpCondSignedLessThan: + return lt + case ssa.IntegerCmpCondSignedGreaterThanOrEqual: + return ge + case ssa.IntegerCmpCondSignedGreaterThan: + return gt + case ssa.IntegerCmpCondSignedLessThanOrEqual: + return le + case ssa.IntegerCmpCondUnsignedLessThan: + return lo + case ssa.IntegerCmpCondUnsignedGreaterThanOrEqual: + return hs + case ssa.IntegerCmpCondUnsignedGreaterThan: + return hi + case ssa.IntegerCmpCondUnsignedLessThanOrEqual: + return ls + default: + panic(c) + } +} + +// condFlagFromSSAFloatCmpCond returns the condition flag for the given ssa.FloatCmpCond. +func condFlagFromSSAFloatCmpCond(c ssa.FloatCmpCond) condFlag { + switch c { + case ssa.FloatCmpCondEqual: + return eq + case ssa.FloatCmpCondNotEqual: + return ne + case ssa.FloatCmpCondLessThan: + return mi + case ssa.FloatCmpCondLessThanOrEqual: + return ls + case ssa.FloatCmpCondGreaterThan: + return gt + case ssa.FloatCmpCondGreaterThanOrEqual: + return ge + default: + panic(c) + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/instr.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/instr.go new file mode 100644 index 00000000000..560044673f3 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/instr.go @@ -0,0 +1,2562 @@ +package arm64 + +import ( + "fmt" + "math" + "unsafe" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +type ( + // instruction represents either a real instruction in arm64, or the meta instructions + // that are convenient for code generation. For example, inline constants are also treated + // as instructions. + // + // Basically, each instruction knows how to get encoded in binaries. Hence, the final output of compilation + // can be considered equivalent to the sequence of such instructions. + // + // Each field is interpreted depending on the kind. + // + // TODO: optimize the layout later once the impl settles. + instruction struct { + prev, next *instruction + u1, u2 uint64 + rd regalloc.VReg + rm, rn operand + kind instructionKind + addedBeforeRegAlloc bool + } + + // instructionKind represents the kind of instruction. + // This controls how the instruction struct is interpreted. + instructionKind byte +) + +// IsCall implements regalloc.Instr IsCall. +func (i *instruction) IsCall() bool { + return i.kind == call +} + +// IsIndirectCall implements regalloc.Instr IsIndirectCall. +func (i *instruction) IsIndirectCall() bool { + return i.kind == callInd +} + +// IsReturn implements regalloc.Instr IsReturn. +func (i *instruction) IsReturn() bool { + return i.kind == ret +} + +type defKind byte + +const ( + defKindNone defKind = iota + 1 + defKindRD + defKindCall +) + +var defKinds = [numInstructionKinds]defKind{ + adr: defKindRD, + aluRRR: defKindRD, + aluRRRR: defKindRD, + aluRRImm12: defKindRD, + aluRRBitmaskImm: defKindRD, + aluRRRShift: defKindRD, + aluRRImmShift: defKindRD, + aluRRRExtend: defKindRD, + bitRR: defKindRD, + movZ: defKindRD, + movK: defKindRD, + movN: defKindRD, + mov32: defKindRD, + mov64: defKindRD, + fpuMov64: defKindRD, + fpuMov128: defKindRD, + fpuRR: defKindRD, + fpuRRR: defKindRD, + nop0: defKindNone, + call: defKindCall, + callInd: defKindCall, + ret: defKindNone, + store8: defKindNone, + store16: defKindNone, + store32: defKindNone, + store64: defKindNone, + exitSequence: defKindNone, + condBr: defKindNone, + br: defKindNone, + brTableSequence: defKindNone, + cSet: defKindRD, + extend: defKindRD, + fpuCmp: defKindNone, + uLoad8: defKindRD, + uLoad16: defKindRD, + uLoad32: defKindRD, + sLoad8: defKindRD, + sLoad16: defKindRD, + sLoad32: defKindRD, + uLoad64: defKindRD, + fpuLoad32: defKindRD, + fpuLoad64: defKindRD, + fpuLoad128: defKindRD, + vecLoad1R: defKindRD, + loadFpuConst32: defKindRD, + loadFpuConst64: defKindRD, + loadFpuConst128: defKindRD, + fpuStore32: defKindNone, + fpuStore64: defKindNone, + fpuStore128: defKindNone, + udf: defKindNone, + cSel: defKindRD, + fpuCSel: defKindRD, + movToVec: defKindRD, + movFromVec: defKindRD, + movFromVecSigned: defKindRD, + vecDup: defKindRD, + vecDupElement: defKindRD, + vecExtract: defKindRD, + vecMisc: defKindRD, + vecMovElement: defKindRD, + vecLanes: defKindRD, + vecShiftImm: defKindRD, + vecTbl: defKindRD, + vecTbl2: defKindRD, + vecPermute: defKindRD, + vecRRR: defKindRD, + vecRRRRewrite: defKindNone, + fpuToInt: defKindRD, + intToFpu: defKindRD, + cCmpImm: defKindNone, + movToFPSR: defKindNone, + movFromFPSR: defKindRD, + emitSourceOffsetInfo: defKindNone, + atomicRmw: defKindRD, + atomicCas: defKindNone, + atomicLoad: defKindRD, + atomicStore: defKindNone, + dmb: defKindNone, + loadConstBlockArg: defKindRD, + tailCall: defKindCall, + tailCallInd: defKindCall, +} + +// Defs returns the list of regalloc.VReg that are defined by the instruction. +// In order to reduce the number of allocations, the caller can pass the slice to be used. +func (i *instruction) Defs(regs *[]regalloc.VReg) []regalloc.VReg { + *regs = (*regs)[:0] + switch defKinds[i.kind] { + case defKindNone: + case defKindRD: + *regs = append(*regs, i.rd) + case defKindCall: + _, _, retIntRealRegs, retFloatRealRegs, _ := backend.ABIInfoFromUint64(i.u2) + for i := byte(0); i < retIntRealRegs; i++ { + *regs = append(*regs, regInfo.RealRegToVReg[intParamResultRegs[i]]) + } + for i := byte(0); i < retFloatRealRegs; i++ { + *regs = append(*regs, regInfo.RealRegToVReg[floatParamResultRegs[i]]) + } + default: + panic(fmt.Sprintf("defKind for %v not defined", i)) + } + return *regs +} + +// AssignDef implements regalloc.Instr AssignDef. +func (i *instruction) AssignDef(reg regalloc.VReg) { + switch defKinds[i.kind] { + case defKindNone: + case defKindRD: + i.rd = reg + case defKindCall: + panic("BUG: call instructions shouldn't be assigned") + default: + panic(fmt.Sprintf("defKind for %v not defined", i)) + } +} + +type useKind byte + +const ( + useKindNone useKind = iota + 1 + useKindRN + useKindRNRM + useKindRNRMRA + useKindRNRN1RM + useKindCall + useKindCallInd + useKindAMode + useKindRNAMode + useKindCond + // useKindRDRewrite indicates an instruction where RD is used both as a source and destination. + // A temporary register for RD must be allocated explicitly with the source copied to this + // register before the instruction and the value copied from this register to the instruction + // return register. + useKindRDRewrite +) + +var useKinds = [numInstructionKinds]useKind{ + udf: useKindNone, + aluRRR: useKindRNRM, + aluRRRR: useKindRNRMRA, + aluRRImm12: useKindRN, + aluRRBitmaskImm: useKindRN, + aluRRRShift: useKindRNRM, + aluRRImmShift: useKindRN, + aluRRRExtend: useKindRNRM, + bitRR: useKindRN, + movZ: useKindNone, + movK: useKindNone, + movN: useKindNone, + mov32: useKindRN, + mov64: useKindRN, + fpuMov64: useKindRN, + fpuMov128: useKindRN, + fpuRR: useKindRN, + fpuRRR: useKindRNRM, + nop0: useKindNone, + call: useKindCall, + callInd: useKindCallInd, + ret: useKindNone, + store8: useKindRNAMode, + store16: useKindRNAMode, + store32: useKindRNAMode, + store64: useKindRNAMode, + exitSequence: useKindRN, + condBr: useKindCond, + br: useKindNone, + brTableSequence: useKindRN, + cSet: useKindNone, + extend: useKindRN, + fpuCmp: useKindRNRM, + uLoad8: useKindAMode, + uLoad16: useKindAMode, + uLoad32: useKindAMode, + sLoad8: useKindAMode, + sLoad16: useKindAMode, + sLoad32: useKindAMode, + uLoad64: useKindAMode, + fpuLoad32: useKindAMode, + fpuLoad64: useKindAMode, + fpuLoad128: useKindAMode, + fpuStore32: useKindRNAMode, + fpuStore64: useKindRNAMode, + fpuStore128: useKindRNAMode, + loadFpuConst32: useKindNone, + loadFpuConst64: useKindNone, + loadFpuConst128: useKindNone, + vecLoad1R: useKindRN, + cSel: useKindRNRM, + fpuCSel: useKindRNRM, + movToVec: useKindRN, + movFromVec: useKindRN, + movFromVecSigned: useKindRN, + vecDup: useKindRN, + vecDupElement: useKindRN, + vecExtract: useKindRNRM, + cCmpImm: useKindRN, + vecMisc: useKindRN, + vecMovElement: useKindRN, + vecLanes: useKindRN, + vecShiftImm: useKindRN, + vecTbl: useKindRNRM, + vecTbl2: useKindRNRN1RM, + vecRRR: useKindRNRM, + vecRRRRewrite: useKindRDRewrite, + vecPermute: useKindRNRM, + fpuToInt: useKindRN, + intToFpu: useKindRN, + movToFPSR: useKindRN, + movFromFPSR: useKindNone, + adr: useKindNone, + emitSourceOffsetInfo: useKindNone, + atomicRmw: useKindRNRM, + atomicCas: useKindRDRewrite, + atomicLoad: useKindRN, + atomicStore: useKindRNRM, + loadConstBlockArg: useKindNone, + dmb: useKindNone, + tailCall: useKindCall, + tailCallInd: useKindCallInd, +} + +// Uses returns the list of regalloc.VReg that are used by the instruction. +// In order to reduce the number of allocations, the caller can pass the slice to be used. +func (i *instruction) Uses(regs *[]regalloc.VReg) []regalloc.VReg { + *regs = (*regs)[:0] + switch useKinds[i.kind] { + case useKindNone: + case useKindRN: + if rn := i.rn.reg(); rn.Valid() { + *regs = append(*regs, rn) + } + case useKindRNRM: + if rn := i.rn.reg(); rn.Valid() { + *regs = append(*regs, rn) + } + if rm := i.rm.reg(); rm.Valid() { + *regs = append(*regs, rm) + } + case useKindRNRMRA: + if rn := i.rn.reg(); rn.Valid() { + *regs = append(*regs, rn) + } + if rm := i.rm.reg(); rm.Valid() { + *regs = append(*regs, rm) + } + if ra := regalloc.VReg(i.u2); ra.Valid() { + *regs = append(*regs, ra) + } + case useKindRNRN1RM: + if rn := i.rn.reg(); rn.Valid() && rn.IsRealReg() { + rn1 := regalloc.FromRealReg(rn.RealReg()+1, rn.RegType()) + *regs = append(*regs, rn, rn1) + } + if rm := i.rm.reg(); rm.Valid() { + *regs = append(*regs, rm) + } + case useKindAMode: + amode := i.getAmode() + if amodeRN := amode.rn; amodeRN.Valid() { + *regs = append(*regs, amodeRN) + } + if amodeRM := amode.rm; amodeRM.Valid() { + *regs = append(*regs, amodeRM) + } + case useKindRNAMode: + *regs = append(*regs, i.rn.reg()) + amode := i.getAmode() + if amodeRN := amode.rn; amodeRN.Valid() { + *regs = append(*regs, amodeRN) + } + if amodeRM := amode.rm; amodeRM.Valid() { + *regs = append(*regs, amodeRM) + } + case useKindCond: + cnd := cond(i.u1) + if cnd.kind() != condKindCondFlagSet { + *regs = append(*regs, cnd.register()) + } + case useKindCallInd: + *regs = append(*regs, i.rn.nr()) + fallthrough + case useKindCall: + argIntRealRegs, argFloatRealRegs, _, _, _ := backend.ABIInfoFromUint64(i.u2) + for i := byte(0); i < argIntRealRegs; i++ { + *regs = append(*regs, regInfo.RealRegToVReg[intParamResultRegs[i]]) + } + for i := byte(0); i < argFloatRealRegs; i++ { + *regs = append(*regs, regInfo.RealRegToVReg[floatParamResultRegs[i]]) + } + case useKindRDRewrite: + *regs = append(*regs, i.rn.reg()) + *regs = append(*regs, i.rm.reg()) + *regs = append(*regs, i.rd) + default: + panic(fmt.Sprintf("useKind for %v not defined", i)) + } + return *regs +} + +func (i *instruction) AssignUse(index int, reg regalloc.VReg) { + switch useKinds[i.kind] { + case useKindNone: + case useKindRN: + if rn := i.rn.reg(); rn.Valid() { + i.rn = i.rn.assignReg(reg) + } + case useKindRNRM: + if index == 0 { + if rn := i.rn.reg(); rn.Valid() { + i.rn = i.rn.assignReg(reg) + } + } else { + if rm := i.rm.reg(); rm.Valid() { + i.rm = i.rm.assignReg(reg) + } + } + case useKindRDRewrite: + if index == 0 { + if rn := i.rn.reg(); rn.Valid() { + i.rn = i.rn.assignReg(reg) + } + } else if index == 1 { + if rm := i.rm.reg(); rm.Valid() { + i.rm = i.rm.assignReg(reg) + } + } else { + if rd := i.rd; rd.Valid() { + i.rd = reg + } + } + case useKindRNRN1RM: + if index == 0 { + if rn := i.rn.reg(); rn.Valid() { + i.rn = i.rn.assignReg(reg) + } + if rn1 := i.rn.reg() + 1; rn1.Valid() { + i.rm = i.rm.assignReg(reg + 1) + } + } else { + if rm := i.rm.reg(); rm.Valid() { + i.rm = i.rm.assignReg(reg) + } + } + case useKindRNRMRA: + if index == 0 { + if rn := i.rn.reg(); rn.Valid() { + i.rn = i.rn.assignReg(reg) + } + } else if index == 1 { + if rm := i.rm.reg(); rm.Valid() { + i.rm = i.rm.assignReg(reg) + } + } else { + if ra := regalloc.VReg(i.u2); ra.Valid() { + i.u2 = uint64(reg) + } + } + case useKindAMode: + if index == 0 { + amode := i.getAmode() + if amodeRN := amode.rn; amodeRN.Valid() { + amode.rn = reg + } + } else { + amode := i.getAmode() + if amodeRM := amode.rm; amodeRM.Valid() { + amode.rm = reg + } + } + case useKindRNAMode: + if index == 0 { + i.rn = i.rn.assignReg(reg) + } else if index == 1 { + amode := i.getAmode() + if amodeRN := amode.rn; amodeRN.Valid() { + amode.rn = reg + } else { + panic("BUG") + } + } else { + amode := i.getAmode() + if amodeRM := amode.rm; amodeRM.Valid() { + amode.rm = reg + } else { + panic("BUG") + } + } + case useKindCond: + c := cond(i.u1) + switch c.kind() { + case condKindRegisterZero: + i.u1 = uint64(registerAsRegZeroCond(reg)) + case condKindRegisterNotZero: + i.u1 = uint64(registerAsRegNotZeroCond(reg)) + } + case useKindCall: + panic("BUG: call instructions shouldn't be assigned") + case useKindCallInd: + i.rn = i.rn.assignReg(reg) + default: + panic(fmt.Sprintf("useKind for %v not defined", i)) + } +} + +func (i *instruction) asCall(ref ssa.FuncRef, abi *backend.FunctionABI) { + i.kind = call + i.u1 = uint64(ref) + if abi != nil { + i.u2 = abi.ABIInfoAsUint64() + } +} + +func (i *instruction) asCallIndirect(ptr regalloc.VReg, abi *backend.FunctionABI) { + i.kind = callInd + i.rn = operandNR(ptr) + if abi != nil { + i.u2 = abi.ABIInfoAsUint64() + } +} + +func (i *instruction) callFuncRef() ssa.FuncRef { + return ssa.FuncRef(i.u1) +} + +// shift must be divided by 16 and must be in range 0-3 (if dst64bit is true) or 0-1 (if dst64bit is false) +func (i *instruction) asMOVZ(dst regalloc.VReg, imm uint64, shift uint32, dst64bit bool) { + i.kind = movZ + i.rd = dst + i.u1 = imm + i.u2 = uint64(shift) + if dst64bit { + i.u2 |= 1 << 32 + } +} + +// shift must be divided by 16 and must be in range 0-3 (if dst64bit is true) or 0-1 (if dst64bit is false) +func (i *instruction) asMOVK(dst regalloc.VReg, imm uint64, shift uint32, dst64bit bool) { + i.kind = movK + i.rd = dst + i.u1 = imm + i.u2 = uint64(shift) + if dst64bit { + i.u2 |= 1 << 32 + } +} + +// shift must be divided by 16 and must be in range 0-3 (if dst64bit is true) or 0-1 (if dst64bit is false) +func (i *instruction) asMOVN(dst regalloc.VReg, imm uint64, shift uint32, dst64bit bool) { + i.kind = movN + i.rd = dst + i.u1 = imm + i.u2 = uint64(shift) + if dst64bit { + i.u2 |= 1 << 32 + } +} + +func (i *instruction) asNop0() *instruction { + i.kind = nop0 + return i +} + +func (i *instruction) asNop0WithLabel(l label) { + i.kind = nop0 + i.u1 = uint64(l) +} + +func (i *instruction) nop0Label() label { + return label(i.u1) +} + +func (i *instruction) asRet() { + i.kind = ret +} + +func (i *instruction) asStorePair64(src1, src2 regalloc.VReg, amode *addressMode) { + i.kind = storeP64 + i.rn = operandNR(src1) + i.rm = operandNR(src2) + i.setAmode(amode) +} + +func (i *instruction) asLoadPair64(src1, src2 regalloc.VReg, amode *addressMode) { + i.kind = loadP64 + i.rn = operandNR(src1) + i.rm = operandNR(src2) + i.setAmode(amode) +} + +func (i *instruction) asStore(src operand, amode *addressMode, sizeInBits byte) { + switch sizeInBits { + case 8: + i.kind = store8 + case 16: + i.kind = store16 + case 32: + if src.reg().RegType() == regalloc.RegTypeInt { + i.kind = store32 + } else { + i.kind = fpuStore32 + } + case 64: + if src.reg().RegType() == regalloc.RegTypeInt { + i.kind = store64 + } else { + i.kind = fpuStore64 + } + case 128: + i.kind = fpuStore128 + } + i.rn = src + i.setAmode(amode) +} + +func (i *instruction) asSLoad(dst regalloc.VReg, amode *addressMode, sizeInBits byte) { + switch sizeInBits { + case 8: + i.kind = sLoad8 + case 16: + i.kind = sLoad16 + case 32: + i.kind = sLoad32 + default: + panic("BUG") + } + i.rd = dst + i.setAmode(amode) +} + +func (i *instruction) asULoad(dst regalloc.VReg, amode *addressMode, sizeInBits byte) { + switch sizeInBits { + case 8: + i.kind = uLoad8 + case 16: + i.kind = uLoad16 + case 32: + i.kind = uLoad32 + case 64: + i.kind = uLoad64 + } + i.rd = dst + i.setAmode(amode) +} + +func (i *instruction) asFpuLoad(dst regalloc.VReg, amode *addressMode, sizeInBits byte) { + switch sizeInBits { + case 32: + i.kind = fpuLoad32 + case 64: + i.kind = fpuLoad64 + case 128: + i.kind = fpuLoad128 + } + i.rd = dst + i.setAmode(amode) +} + +func (i *instruction) getAmode() *addressMode { + return wazevoapi.PtrFromUintptr[addressMode](uintptr(i.u1)) +} + +func (i *instruction) setAmode(a *addressMode) { + i.u1 = uint64(uintptr(unsafe.Pointer(a))) +} + +func (i *instruction) asVecLoad1R(rd regalloc.VReg, rn operand, arr vecArrangement) { + // NOTE: currently only has support for no-offset loads, though it is suspicious that + // we would need to support offset load (that is only available for post-index). + i.kind = vecLoad1R + i.rd = rd + i.rn = rn + i.u1 = uint64(arr) +} + +func (i *instruction) asCSet(rd regalloc.VReg, mask bool, c condFlag) { + i.kind = cSet + i.rd = rd + i.u1 = uint64(c) + if mask { + i.u2 = 1 + } +} + +func (i *instruction) asCSel(rd regalloc.VReg, rn, rm operand, c condFlag, _64bit bool) { + i.kind = cSel + i.rd = rd + i.rn = rn + i.rm = rm + i.u1 = uint64(c) + if _64bit { + i.u2 = 1 + } +} + +func (i *instruction) asFpuCSel(rd regalloc.VReg, rn, rm operand, c condFlag, _64bit bool) { + i.kind = fpuCSel + i.rd = rd + i.rn = rn + i.rm = rm + i.u1 = uint64(c) + if _64bit { + i.u2 = 1 + } +} + +func (i *instruction) asBr(target label) { + if target == labelReturn { + panic("BUG: call site should special case for returnLabel") + } + i.kind = br + i.u1 = uint64(target) +} + +func (i *instruction) asBrTableSequence(indexReg regalloc.VReg, targetIndex, targetCounts int) { + i.kind = brTableSequence + i.rn = operandNR(indexReg) + i.u1 = uint64(targetIndex) + i.u2 = uint64(targetCounts) +} + +func (i *instruction) brTableSequenceOffsetsResolved() { + i.rm.data = 1 // indicate that the offsets are resolved, for debugging. +} + +func (i *instruction) brLabel() label { + return label(i.u1) +} + +// brOffsetResolved is called when the target label is resolved. +func (i *instruction) brOffsetResolve(offset int64) { + i.u2 = uint64(offset) + i.rm.data = 1 // indicate that the offset is resolved, for debugging. +} + +func (i *instruction) brOffset() int64 { + return int64(i.u2) +} + +// asCondBr encodes a conditional branch instruction. is64bit is only needed when cond is not flag. +func (i *instruction) asCondBr(c cond, target label, is64bit bool) { + i.kind = condBr + i.u1 = c.asUint64() + i.u2 = uint64(target) + if is64bit { + i.u2 |= 1 << 32 + } +} + +func (i *instruction) setCondBrTargets(target label) { + i.u2 = uint64(target) +} + +func (i *instruction) condBrLabel() label { + return label(i.u2) +} + +// condBrOffsetResolve is called when the target label is resolved. +func (i *instruction) condBrOffsetResolve(offset int64) { + i.rn.data = uint64(offset) + i.rn.data2 = 1 // indicate that the offset is resolved, for debugging. +} + +// condBrOffsetResolved returns true if condBrOffsetResolve is already called. +func (i *instruction) condBrOffsetResolved() bool { + return i.rn.data2 == 1 +} + +func (i *instruction) condBrOffset() int64 { + return int64(i.rn.data) +} + +func (i *instruction) condBrCond() cond { + return cond(i.u1) +} + +func (i *instruction) condBr64bit() bool { + return i.u2&(1<<32) != 0 +} + +func (i *instruction) asLoadFpuConst32(rd regalloc.VReg, raw uint64) { + i.kind = loadFpuConst32 + i.u1 = raw + i.rd = rd +} + +func (i *instruction) asLoadFpuConst64(rd regalloc.VReg, raw uint64) { + i.kind = loadFpuConst64 + i.u1 = raw + i.rd = rd +} + +func (i *instruction) asLoadFpuConst128(rd regalloc.VReg, lo, hi uint64) { + i.kind = loadFpuConst128 + i.u1 = lo + i.u2 = hi + i.rd = rd +} + +func (i *instruction) asFpuCmp(rn, rm operand, is64bit bool) { + i.kind = fpuCmp + i.rn, i.rm = rn, rm + if is64bit { + i.u1 = 1 + } +} + +func (i *instruction) asCCmpImm(rn operand, imm uint64, c condFlag, flag byte, is64bit bool) { + i.kind = cCmpImm + i.rn = rn + i.rm.data = imm + i.u1 = uint64(c) + i.u2 = uint64(flag) + if is64bit { + i.u2 |= 1 << 32 + } +} + +// asALU setups a basic ALU instruction. +func (i *instruction) asALU(aluOp aluOp, rd regalloc.VReg, rn, rm operand, dst64bit bool) { + switch rm.kind { + case operandKindNR: + i.kind = aluRRR + case operandKindSR: + i.kind = aluRRRShift + case operandKindER: + i.kind = aluRRRExtend + case operandKindImm12: + i.kind = aluRRImm12 + default: + panic("BUG") + } + i.u1 = uint64(aluOp) + i.rd, i.rn, i.rm = rd, rn, rm + if dst64bit { + i.u2 |= 1 << 32 + } +} + +// asALU setups a basic ALU instruction. +func (i *instruction) asALURRRR(aluOp aluOp, rd regalloc.VReg, rn, rm operand, ra regalloc.VReg, dst64bit bool) { + i.kind = aluRRRR + i.u1 = uint64(aluOp) + i.rd, i.rn, i.rm, i.u2 = rd, rn, rm, uint64(ra) + if dst64bit { + i.u1 |= 1 << 32 + } +} + +// asALUShift setups a shift based ALU instruction. +func (i *instruction) asALUShift(aluOp aluOp, rd regalloc.VReg, rn, rm operand, dst64bit bool) { + switch rm.kind { + case operandKindNR: + i.kind = aluRRR // If the shift amount op is a register, then the instruction is encoded as a normal ALU instruction with two register operands. + case operandKindShiftImm: + i.kind = aluRRImmShift + default: + panic("BUG") + } + i.u1 = uint64(aluOp) + i.rd, i.rn, i.rm = rd, rn, rm + if dst64bit { + i.u2 |= 1 << 32 + } +} + +func (i *instruction) asALUBitmaskImm(aluOp aluOp, rd, rn regalloc.VReg, imm uint64, dst64bit bool) { + i.kind = aluRRBitmaskImm + i.u1 = uint64(aluOp) + i.rn, i.rd = operandNR(rn), rd + i.u2 = imm + if dst64bit { + i.u1 |= 1 << 32 + } +} + +func (i *instruction) asMovToFPSR(rn regalloc.VReg) { + i.kind = movToFPSR + i.rn = operandNR(rn) +} + +func (i *instruction) asMovFromFPSR(rd regalloc.VReg) { + i.kind = movFromFPSR + i.rd = rd +} + +func (i *instruction) asBitRR(bitOp bitOp, rd, rn regalloc.VReg, is64bit bool) { + i.kind = bitRR + i.rn, i.rd = operandNR(rn), rd + i.u1 = uint64(bitOp) + if is64bit { + i.u2 = 1 + } +} + +func (i *instruction) asFpuRRR(op fpuBinOp, rd regalloc.VReg, rn, rm operand, dst64bit bool) { + i.kind = fpuRRR + i.u1 = uint64(op) + i.rd, i.rn, i.rm = rd, rn, rm + if dst64bit { + i.u2 = 1 + } +} + +func (i *instruction) asFpuRR(op fpuUniOp, rd regalloc.VReg, rn operand, dst64bit bool) { + i.kind = fpuRR + i.u1 = uint64(op) + i.rd, i.rn = rd, rn + if dst64bit { + i.u2 = 1 + } +} + +func (i *instruction) asExtend(rd, rn regalloc.VReg, fromBits, toBits byte, signed bool) { + i.kind = extend + i.rn, i.rd = operandNR(rn), rd + i.u1 = uint64(fromBits) + i.u2 = uint64(toBits) + if signed { + i.u2 |= 1 << 32 + } +} + +func (i *instruction) asMove32(rd, rn regalloc.VReg) { + i.kind = mov32 + i.rn, i.rd = operandNR(rn), rd +} + +func (i *instruction) asMove64(rd, rn regalloc.VReg) *instruction { + i.kind = mov64 + i.rn, i.rd = operandNR(rn), rd + return i +} + +func (i *instruction) asFpuMov64(rd, rn regalloc.VReg) { + i.kind = fpuMov64 + i.rn, i.rd = operandNR(rn), rd +} + +func (i *instruction) asFpuMov128(rd, rn regalloc.VReg) *instruction { + i.kind = fpuMov128 + i.rn, i.rd = operandNR(rn), rd + return i +} + +func (i *instruction) asMovToVec(rd regalloc.VReg, rn operand, arr vecArrangement, index vecIndex) { + i.kind = movToVec + i.rd = rd + i.rn = rn + i.u1, i.u2 = uint64(arr), uint64(index) +} + +func (i *instruction) asMovFromVec(rd regalloc.VReg, rn operand, arr vecArrangement, index vecIndex, signed bool) { + if signed { + i.kind = movFromVecSigned + } else { + i.kind = movFromVec + } + i.rd = rd + i.rn = rn + i.u1, i.u2 = uint64(arr), uint64(index) +} + +func (i *instruction) asVecDup(rd regalloc.VReg, rn operand, arr vecArrangement) { + i.kind = vecDup + i.u1 = uint64(arr) + i.rn, i.rd = rn, rd +} + +func (i *instruction) asVecDupElement(rd regalloc.VReg, rn operand, arr vecArrangement, index vecIndex) { + i.kind = vecDupElement + i.u1 = uint64(arr) + i.rn, i.rd = rn, rd + i.u2 = uint64(index) +} + +func (i *instruction) asVecExtract(rd regalloc.VReg, rn, rm operand, arr vecArrangement, index uint32) { + i.kind = vecExtract + i.u1 = uint64(arr) + i.rn, i.rm, i.rd = rn, rm, rd + i.u2 = uint64(index) +} + +func (i *instruction) asVecMovElement(rd regalloc.VReg, rn operand, arr vecArrangement, rdIndex, rnIndex vecIndex) { + i.kind = vecMovElement + i.u1 = uint64(arr) + i.u2 = uint64(rdIndex) | uint64(rnIndex)<<32 + i.rn, i.rd = rn, rd +} + +func (i *instruction) asVecMisc(op vecOp, rd regalloc.VReg, rn operand, arr vecArrangement) { + i.kind = vecMisc + i.u1 = uint64(op) + i.rn, i.rd = rn, rd + i.u2 = uint64(arr) +} + +func (i *instruction) asVecLanes(op vecOp, rd regalloc.VReg, rn operand, arr vecArrangement) { + i.kind = vecLanes + i.u1 = uint64(op) + i.rn, i.rd = rn, rd + i.u2 = uint64(arr) +} + +func (i *instruction) asVecShiftImm(op vecOp, rd regalloc.VReg, rn, rm operand, arr vecArrangement) *instruction { + i.kind = vecShiftImm + i.u1 = uint64(op) + i.rn, i.rm, i.rd = rn, rm, rd + i.u2 = uint64(arr) + return i +} + +func (i *instruction) asVecTbl(nregs byte, rd regalloc.VReg, rn, rm operand, arr vecArrangement) { + switch nregs { + case 0, 1: + i.kind = vecTbl + case 2: + i.kind = vecTbl2 + if !rn.reg().IsRealReg() { + panic("rn is not a RealReg") + } + if rn.realReg() == v31 { + panic("rn cannot be v31") + } + default: + panic(fmt.Sprintf("unsupported number of registers %d", nregs)) + } + i.rn, i.rm, i.rd = rn, rm, rd + i.u2 = uint64(arr) +} + +func (i *instruction) asVecPermute(op vecOp, rd regalloc.VReg, rn, rm operand, arr vecArrangement) { + i.kind = vecPermute + i.u1 = uint64(op) + i.rn, i.rm, i.rd = rn, rm, rd + i.u2 = uint64(arr) +} + +func (i *instruction) asVecRRR(op vecOp, rd regalloc.VReg, rn, rm operand, arr vecArrangement) *instruction { + i.kind = vecRRR + i.u1 = uint64(op) + i.rn, i.rd, i.rm = rn, rd, rm + i.u2 = uint64(arr) + return i +} + +// asVecRRRRewrite encodes a vector instruction that rewrites the destination register. +// IMPORTANT: the destination register must be already defined before this instruction. +func (i *instruction) asVecRRRRewrite(op vecOp, rd regalloc.VReg, rn, rm operand, arr vecArrangement) { + i.kind = vecRRRRewrite + i.u1 = uint64(op) + i.rn, i.rd, i.rm = rn, rd, rm + i.u2 = uint64(arr) +} + +func (i *instruction) IsCopy() bool { + op := i.kind + // We do not include mov32 as it is not a copy instruction in the sense that it does not preserve the upper 32 bits, + // and it is only used in the translation of IReduce, not the actual copy indeed. + return op == mov64 || op == fpuMov64 || op == fpuMov128 +} + +// String implements fmt.Stringer. +func (i *instruction) String() (str string) { + is64SizeBitToSize := func(v uint64) byte { + if v == 0 { + return 32 + } + return 64 + } + + switch i.kind { + case nop0: + if i.u1 != 0 { + l := label(i.u1) + str = fmt.Sprintf("%s:", l) + } else { + str = "nop0" + } + case aluRRR: + size := is64SizeBitToSize(i.u2 >> 32) + str = fmt.Sprintf("%s %s, %s, %s", aluOp(i.u1).String(), + formatVRegSized(i.rd, size), formatVRegSized(i.rn.nr(), size), + i.rm.format(size)) + case aluRRRR: + size := is64SizeBitToSize(i.u1 >> 32) + str = fmt.Sprintf("%s %s, %s, %s, %s", aluOp(i.u1).String(), + formatVRegSized(i.rd, size), formatVRegSized(i.rn.nr(), size), formatVRegSized(i.rm.nr(), size), formatVRegSized(regalloc.VReg(i.u2), size)) + case aluRRImm12: + size := is64SizeBitToSize(i.u2 >> 32) + str = fmt.Sprintf("%s %s, %s, %s", aluOp(i.u1).String(), + formatVRegSized(i.rd, size), formatVRegSized(i.rn.nr(), size), i.rm.format(size)) + case aluRRBitmaskImm: + size := is64SizeBitToSize(i.u1 >> 32) + rd, rn := formatVRegSized(i.rd, size), formatVRegSized(i.rn.nr(), size) + if size == 32 { + str = fmt.Sprintf("%s %s, %s, #%#x", aluOp(i.u1).String(), rd, rn, uint32(i.u2)) + } else { + str = fmt.Sprintf("%s %s, %s, #%#x", aluOp(i.u1).String(), rd, rn, i.u2) + } + case aluRRImmShift: + size := is64SizeBitToSize(i.u2 >> 32) + str = fmt.Sprintf("%s %s, %s, %#x", + aluOp(i.u1).String(), + formatVRegSized(i.rd, size), + formatVRegSized(i.rn.nr(), size), + i.rm.shiftImm(), + ) + case aluRRRShift: + size := is64SizeBitToSize(i.u2 >> 32) + str = fmt.Sprintf("%s %s, %s, %s", + aluOp(i.u1).String(), + formatVRegSized(i.rd, size), + formatVRegSized(i.rn.nr(), size), + i.rm.format(size), + ) + case aluRRRExtend: + size := is64SizeBitToSize(i.u2 >> 32) + str = fmt.Sprintf("%s %s, %s, %s", aluOp(i.u1).String(), + formatVRegSized(i.rd, size), + formatVRegSized(i.rn.nr(), size), + // Regardless of the source size, the register is formatted in 32-bit. + i.rm.format(32), + ) + case bitRR: + size := is64SizeBitToSize(i.u2) + str = fmt.Sprintf("%s %s, %s", + bitOp(i.u1), + formatVRegSized(i.rd, size), + formatVRegSized(i.rn.nr(), size), + ) + case uLoad8: + str = fmt.Sprintf("ldrb %s, %s", formatVRegSized(i.rd, 32), i.getAmode().format(32)) + case sLoad8: + str = fmt.Sprintf("ldrsb %s, %s", formatVRegSized(i.rd, 32), i.getAmode().format(32)) + case uLoad16: + str = fmt.Sprintf("ldrh %s, %s", formatVRegSized(i.rd, 32), i.getAmode().format(32)) + case sLoad16: + str = fmt.Sprintf("ldrsh %s, %s", formatVRegSized(i.rd, 32), i.getAmode().format(32)) + case uLoad32: + str = fmt.Sprintf("ldr %s, %s", formatVRegSized(i.rd, 32), i.getAmode().format(32)) + case sLoad32: + str = fmt.Sprintf("ldrs %s, %s", formatVRegSized(i.rd, 32), i.getAmode().format(32)) + case uLoad64: + str = fmt.Sprintf("ldr %s, %s", formatVRegSized(i.rd, 64), i.getAmode().format(64)) + case store8: + str = fmt.Sprintf("strb %s, %s", formatVRegSized(i.rn.nr(), 32), i.getAmode().format(8)) + case store16: + str = fmt.Sprintf("strh %s, %s", formatVRegSized(i.rn.nr(), 32), i.getAmode().format(16)) + case store32: + str = fmt.Sprintf("str %s, %s", formatVRegSized(i.rn.nr(), 32), i.getAmode().format(32)) + case store64: + str = fmt.Sprintf("str %s, %s", formatVRegSized(i.rn.nr(), 64), i.getAmode().format(64)) + case storeP64: + str = fmt.Sprintf("stp %s, %s, %s", + formatVRegSized(i.rn.nr(), 64), formatVRegSized(i.rm.nr(), 64), i.getAmode().format(64)) + case loadP64: + str = fmt.Sprintf("ldp %s, %s, %s", + formatVRegSized(i.rn.nr(), 64), formatVRegSized(i.rm.nr(), 64), i.getAmode().format(64)) + case mov64: + str = fmt.Sprintf("mov %s, %s", + formatVRegSized(i.rd, 64), + formatVRegSized(i.rn.nr(), 64)) + case mov32: + str = fmt.Sprintf("mov %s, %s", formatVRegSized(i.rd, 32), formatVRegSized(i.rn.nr(), 32)) + case movZ: + size := is64SizeBitToSize(i.u2 >> 32) + str = fmt.Sprintf("movz %s, #%#x, lsl %d", formatVRegSized(i.rd, size), uint16(i.u1), uint32(i.u2)*16) + case movN: + size := is64SizeBitToSize(i.u2 >> 32) + str = fmt.Sprintf("movn %s, #%#x, lsl %d", formatVRegSized(i.rd, size), uint16(i.u1), uint32(i.u2)*16) + case movK: + size := is64SizeBitToSize(i.u2 >> 32) + str = fmt.Sprintf("movk %s, #%#x, lsl %d", formatVRegSized(i.rd, size), uint16(i.u1), uint32(i.u2)*16) + case extend: + fromBits, toBits := byte(i.u1), byte(i.u2) + + var signedStr string + if i.u2>>32 == 1 { + signedStr = "s" + } else { + signedStr = "u" + } + var fromStr string + switch fromBits { + case 8: + fromStr = "b" + case 16: + fromStr = "h" + case 32: + fromStr = "w" + } + str = fmt.Sprintf("%sxt%s %s, %s", signedStr, fromStr, formatVRegSized(i.rd, toBits), formatVRegSized(i.rn.nr(), 32)) + case cSel: + size := is64SizeBitToSize(i.u2) + str = fmt.Sprintf("csel %s, %s, %s, %s", + formatVRegSized(i.rd, size), + formatVRegSized(i.rn.nr(), size), + formatVRegSized(i.rm.nr(), size), + condFlag(i.u1), + ) + case cSet: + if i.u2 != 0 { + str = fmt.Sprintf("csetm %s, %s", formatVRegSized(i.rd, 64), condFlag(i.u1)) + } else { + str = fmt.Sprintf("cset %s, %s", formatVRegSized(i.rd, 64), condFlag(i.u1)) + } + case cCmpImm: + size := is64SizeBitToSize(i.u2 >> 32) + str = fmt.Sprintf("ccmp %s, #%#x, #%#x, %s", + formatVRegSized(i.rn.nr(), size), i.rm.data, + i.u2&0b1111, + condFlag(i.u1)) + case fpuMov64: + str = fmt.Sprintf("mov %s, %s", + formatVRegVec(i.rd, vecArrangement8B, vecIndexNone), + formatVRegVec(i.rn.nr(), vecArrangement8B, vecIndexNone)) + case fpuMov128: + str = fmt.Sprintf("mov %s, %s", + formatVRegVec(i.rd, vecArrangement16B, vecIndexNone), + formatVRegVec(i.rn.nr(), vecArrangement16B, vecIndexNone)) + case fpuMovFromVec: + panic("TODO") + case fpuRR: + dstSz := is64SizeBitToSize(i.u2) + srcSz := dstSz + op := fpuUniOp(i.u1) + switch op { + case fpuUniOpCvt32To64: + srcSz = 32 + case fpuUniOpCvt64To32: + srcSz = 64 + } + str = fmt.Sprintf("%s %s, %s", op.String(), + formatVRegSized(i.rd, dstSz), formatVRegSized(i.rn.nr(), srcSz)) + case fpuRRR: + size := is64SizeBitToSize(i.u2) + str = fmt.Sprintf("%s %s, %s, %s", fpuBinOp(i.u1).String(), + formatVRegSized(i.rd, size), formatVRegSized(i.rn.nr(), size), formatVRegSized(i.rm.nr(), size)) + case fpuRRI: + panic("TODO") + case fpuRRRR: + panic("TODO") + case fpuCmp: + size := is64SizeBitToSize(i.u1) + str = fmt.Sprintf("fcmp %s, %s", + formatVRegSized(i.rn.nr(), size), formatVRegSized(i.rm.nr(), size)) + case fpuLoad32: + str = fmt.Sprintf("ldr %s, %s", formatVRegSized(i.rd, 32), i.getAmode().format(32)) + case fpuStore32: + str = fmt.Sprintf("str %s, %s", formatVRegSized(i.rn.nr(), 32), i.getAmode().format(64)) + case fpuLoad64: + str = fmt.Sprintf("ldr %s, %s", formatVRegSized(i.rd, 64), i.getAmode().format(64)) + case fpuStore64: + str = fmt.Sprintf("str %s, %s", formatVRegSized(i.rn.nr(), 64), i.getAmode().format(64)) + case fpuLoad128: + str = fmt.Sprintf("ldr %s, %s", formatVRegSized(i.rd, 128), i.getAmode().format(64)) + case fpuStore128: + str = fmt.Sprintf("str %s, %s", formatVRegSized(i.rn.nr(), 128), i.getAmode().format(64)) + case loadFpuConst32: + str = fmt.Sprintf("ldr %s, #8; b 8; data.f32 %f", formatVRegSized(i.rd, 32), math.Float32frombits(uint32(i.u1))) + case loadFpuConst64: + str = fmt.Sprintf("ldr %s, #8; b 16; data.f64 %f", formatVRegSized(i.rd, 64), math.Float64frombits(i.u1)) + case loadFpuConst128: + str = fmt.Sprintf("ldr %s, #8; b 32; data.v128 %016x %016x", + formatVRegSized(i.rd, 128), i.u1, i.u2) + case fpuToInt: + var op, src, dst string + if signed := i.u1 == 1; signed { + op = "fcvtzs" + } else { + op = "fcvtzu" + } + if src64 := i.u2&1 != 0; src64 { + src = formatVRegWidthVec(i.rn.nr(), vecArrangementD) + } else { + src = formatVRegWidthVec(i.rn.nr(), vecArrangementS) + } + if dst64 := i.u2&2 != 0; dst64 { + dst = formatVRegSized(i.rd, 64) + } else { + dst = formatVRegSized(i.rd, 32) + } + str = fmt.Sprintf("%s %s, %s", op, dst, src) + + case intToFpu: + var op, src, dst string + if signed := i.u1 == 1; signed { + op = "scvtf" + } else { + op = "ucvtf" + } + if src64 := i.u2&1 != 0; src64 { + src = formatVRegSized(i.rn.nr(), 64) + } else { + src = formatVRegSized(i.rn.nr(), 32) + } + if dst64 := i.u2&2 != 0; dst64 { + dst = formatVRegWidthVec(i.rd, vecArrangementD) + } else { + dst = formatVRegWidthVec(i.rd, vecArrangementS) + } + str = fmt.Sprintf("%s %s, %s", op, dst, src) + case fpuCSel: + size := is64SizeBitToSize(i.u2) + str = fmt.Sprintf("fcsel %s, %s, %s, %s", + formatVRegSized(i.rd, size), + formatVRegSized(i.rn.nr(), size), + formatVRegSized(i.rm.nr(), size), + condFlag(i.u1), + ) + case movToVec: + var size byte + arr := vecArrangement(i.u1) + switch arr { + case vecArrangementB, vecArrangementH, vecArrangementS: + size = 32 + case vecArrangementD: + size = 64 + default: + panic("unsupported arrangement " + arr.String()) + } + str = fmt.Sprintf("ins %s, %s", formatVRegVec(i.rd, arr, vecIndex(i.u2)), formatVRegSized(i.rn.nr(), size)) + case movFromVec, movFromVecSigned: + var size byte + var opcode string + arr := vecArrangement(i.u1) + signed := i.kind == movFromVecSigned + switch arr { + case vecArrangementB, vecArrangementH, vecArrangementS: + size = 32 + if signed { + opcode = "smov" + } else { + opcode = "umov" + } + case vecArrangementD: + size = 64 + if signed { + opcode = "smov" + } else { + opcode = "mov" + } + default: + panic("unsupported arrangement " + arr.String()) + } + str = fmt.Sprintf("%s %s, %s", opcode, formatVRegSized(i.rd, size), formatVRegVec(i.rn.nr(), arr, vecIndex(i.u2))) + case vecDup: + str = fmt.Sprintf("dup %s, %s", + formatVRegVec(i.rd, vecArrangement(i.u1), vecIndexNone), + formatVRegSized(i.rn.nr(), 64), + ) + case vecDupElement: + arr := vecArrangement(i.u1) + str = fmt.Sprintf("dup %s, %s", + formatVRegVec(i.rd, arr, vecIndexNone), + formatVRegVec(i.rn.nr(), arr, vecIndex(i.u2)), + ) + case vecDupFromFpu: + panic("TODO") + case vecExtract: + str = fmt.Sprintf("ext %s, %s, %s, #%d", + formatVRegVec(i.rd, vecArrangement(i.u1), vecIndexNone), + formatVRegVec(i.rn.nr(), vecArrangement(i.u1), vecIndexNone), + formatVRegVec(i.rm.nr(), vecArrangement(i.u1), vecIndexNone), + uint32(i.u2), + ) + case vecExtend: + panic("TODO") + case vecMovElement: + str = fmt.Sprintf("mov %s, %s", + formatVRegVec(i.rd, vecArrangement(i.u1), vecIndex(i.u2&0xffffffff)), + formatVRegVec(i.rn.nr(), vecArrangement(i.u1), vecIndex(i.u2>>32)), + ) + case vecMiscNarrow: + panic("TODO") + case vecRRR, vecRRRRewrite: + str = fmt.Sprintf("%s %s, %s, %s", + vecOp(i.u1), + formatVRegVec(i.rd, vecArrangement(i.u2), vecIndexNone), + formatVRegVec(i.rn.nr(), vecArrangement(i.u2), vecIndexNone), + formatVRegVec(i.rm.nr(), vecArrangement(i.u2), vecIndexNone), + ) + case vecMisc: + vop := vecOp(i.u1) + if vop == vecOpCmeq0 { + str = fmt.Sprintf("cmeq %s, %s, #0", + formatVRegVec(i.rd, vecArrangement(i.u2), vecIndexNone), + formatVRegVec(i.rn.nr(), vecArrangement(i.u2), vecIndexNone)) + } else { + str = fmt.Sprintf("%s %s, %s", + vop, + formatVRegVec(i.rd, vecArrangement(i.u2), vecIndexNone), + formatVRegVec(i.rn.nr(), vecArrangement(i.u2), vecIndexNone)) + } + case vecLanes: + arr := vecArrangement(i.u2) + var destArr vecArrangement + switch arr { + case vecArrangement8B, vecArrangement16B: + destArr = vecArrangementH + case vecArrangement4H, vecArrangement8H: + destArr = vecArrangementS + case vecArrangement4S: + destArr = vecArrangementD + default: + panic("invalid arrangement " + arr.String()) + } + str = fmt.Sprintf("%s %s, %s", + vecOp(i.u1), + formatVRegWidthVec(i.rd, destArr), + formatVRegVec(i.rn.nr(), arr, vecIndexNone)) + case vecShiftImm: + arr := vecArrangement(i.u2) + str = fmt.Sprintf("%s %s, %s, #%d", + vecOp(i.u1), + formatVRegVec(i.rd, arr, vecIndexNone), + formatVRegVec(i.rn.nr(), arr, vecIndexNone), + i.rm.shiftImm()) + case vecTbl: + arr := vecArrangement(i.u2) + str = fmt.Sprintf("tbl %s, { %s }, %s", + formatVRegVec(i.rd, arr, vecIndexNone), + formatVRegVec(i.rn.nr(), vecArrangement16B, vecIndexNone), + formatVRegVec(i.rm.nr(), arr, vecIndexNone)) + case vecTbl2: + arr := vecArrangement(i.u2) + rd, rn, rm := i.rd, i.rn.nr(), i.rm.nr() + rn1 := regalloc.FromRealReg(rn.RealReg()+1, rn.RegType()) + str = fmt.Sprintf("tbl %s, { %s, %s }, %s", + formatVRegVec(rd, arr, vecIndexNone), + formatVRegVec(rn, vecArrangement16B, vecIndexNone), + formatVRegVec(rn1, vecArrangement16B, vecIndexNone), + formatVRegVec(rm, arr, vecIndexNone)) + case vecPermute: + arr := vecArrangement(i.u2) + str = fmt.Sprintf("%s %s, %s, %s", + vecOp(i.u1), + formatVRegVec(i.rd, arr, vecIndexNone), + formatVRegVec(i.rn.nr(), arr, vecIndexNone), + formatVRegVec(i.rm.nr(), arr, vecIndexNone)) + case movToFPSR: + str = fmt.Sprintf("msr fpsr, %s", formatVRegSized(i.rn.nr(), 64)) + case movFromFPSR: + str = fmt.Sprintf("mrs %s fpsr", formatVRegSized(i.rd, 64)) + case call: + str = fmt.Sprintf("bl %s", ssa.FuncRef(i.u1)) + case callInd: + str = fmt.Sprintf("bl %s", formatVRegSized(i.rn.nr(), 64)) + case ret: + str = "ret" + case br: + target := label(i.u1) + if i.rm.data != 0 { + str = fmt.Sprintf("b #%#x (%s)", i.brOffset(), target.String()) + } else { + str = fmt.Sprintf("b %s", target.String()) + } + case condBr: + size := is64SizeBitToSize(i.u2 >> 32) + c := cond(i.u1) + target := label(i.u2 & 0xffffffff) + switch c.kind() { + case condKindRegisterZero: + if !i.condBrOffsetResolved() { + str = fmt.Sprintf("cbz %s, (%s)", formatVRegSized(c.register(), size), target.String()) + } else { + str = fmt.Sprintf("cbz %s, #%#x %s", formatVRegSized(c.register(), size), i.condBrOffset(), target.String()) + } + case condKindRegisterNotZero: + if offset := i.condBrOffset(); offset != 0 { + str = fmt.Sprintf("cbnz %s, #%#x (%s)", formatVRegSized(c.register(), size), offset, target.String()) + } else { + str = fmt.Sprintf("cbnz %s, %s", formatVRegSized(c.register(), size), target.String()) + } + case condKindCondFlagSet: + if offset := i.condBrOffset(); offset != 0 { + if target == labelInvalid { + str = fmt.Sprintf("b.%s #%#x", c.flag(), offset) + } else { + str = fmt.Sprintf("b.%s #%#x, (%s)", c.flag(), offset, target.String()) + } + } else { + str = fmt.Sprintf("b.%s %s", c.flag(), target.String()) + } + } + case adr: + str = fmt.Sprintf("adr %s, #%#x", formatVRegSized(i.rd, 64), int64(i.u1)) + case brTableSequence: + targetIndex := i.u1 + str = fmt.Sprintf("br_table_sequence %s, table_index=%d", formatVRegSized(i.rn.nr(), 64), targetIndex) + case exitSequence: + str = fmt.Sprintf("exit_sequence %s", formatVRegSized(i.rn.nr(), 64)) + case atomicRmw: + m := atomicRmwOp(i.u1).String() + size := byte(32) + switch i.u2 { + case 8: + size = 64 + case 2: + m = m + "h" + case 1: + m = m + "b" + } + str = fmt.Sprintf("%s %s, %s, %s", m, formatVRegSized(i.rm.nr(), size), formatVRegSized(i.rd, size), formatVRegSized(i.rn.nr(), 64)) + case atomicCas: + m := "casal" + size := byte(32) + switch i.u2 { + case 8: + size = 64 + case 2: + m = m + "h" + case 1: + m = m + "b" + } + str = fmt.Sprintf("%s %s, %s, %s", m, formatVRegSized(i.rd, size), formatVRegSized(i.rm.nr(), size), formatVRegSized(i.rn.nr(), 64)) + case atomicLoad: + m := "ldar" + size := byte(32) + switch i.u2 { + case 8: + size = 64 + case 2: + m = m + "h" + case 1: + m = m + "b" + } + str = fmt.Sprintf("%s %s, %s", m, formatVRegSized(i.rd, size), formatVRegSized(i.rn.nr(), 64)) + case atomicStore: + m := "stlr" + size := byte(32) + switch i.u2 { + case 8: + size = 64 + case 2: + m = m + "h" + case 1: + m = m + "b" + } + str = fmt.Sprintf("%s %s, %s", m, formatVRegSized(i.rm.nr(), size), formatVRegSized(i.rn.nr(), 64)) + case dmb: + str = "dmb" + case tailCall: + str = fmt.Sprintf("b %s", ssa.FuncRef(i.u1)) + case tailCallInd: + str = fmt.Sprintf("b %s", formatVRegSized(i.rn.nr(), 64)) + case udf: + str = "udf" + case emitSourceOffsetInfo: + str = fmt.Sprintf("source_offset_info %d", ssa.SourceOffset(i.u1)) + case vecLoad1R: + str = fmt.Sprintf("ld1r {%s}, [%s]", formatVRegVec(i.rd, vecArrangement(i.u1), vecIndexNone), formatVRegSized(i.rn.nr(), 64)) + case loadConstBlockArg: + str = fmt.Sprintf("load_const_block_arg %s, %#x", formatVRegSized(i.rd, 64), i.u1) + default: + panic(i.kind) + } + return +} + +func (i *instruction) asAdr(rd regalloc.VReg, offset int64) { + i.kind = adr + i.rd = rd + i.u1 = uint64(offset) +} + +func (i *instruction) asAtomicRmw(op atomicRmwOp, rn, rs, rt regalloc.VReg, size uint64) { + i.kind = atomicRmw + i.rd, i.rn, i.rm = rt, operandNR(rn), operandNR(rs) + i.u1 = uint64(op) + i.u2 = size +} + +func (i *instruction) asAtomicCas(rn, rs, rt regalloc.VReg, size uint64) { + i.kind = atomicCas + i.rm, i.rn, i.rd = operandNR(rt), operandNR(rn), rs + i.u2 = size +} + +func (i *instruction) asAtomicLoad(rn, rt regalloc.VReg, size uint64) { + i.kind = atomicLoad + i.rn, i.rd = operandNR(rn), rt + i.u2 = size +} + +func (i *instruction) asAtomicStore(rn, rt operand, size uint64) { + i.kind = atomicStore + i.rn, i.rm = rn, rt + i.u2 = size +} + +func (i *instruction) asDMB() { + i.kind = dmb +} + +func (i *instruction) asTailCall(ref ssa.FuncRef, abi *backend.FunctionABI) { + i.kind = tailCall + i.u1 = uint64(ref) + if abi != nil { + i.u2 = abi.ABIInfoAsUint64() + } +} + +func (i *instruction) asTailCallIndirect(ptr regalloc.VReg, abi *backend.FunctionABI) { + i.kind = tailCallInd + i.rn = operandNR(ptr) + if abi != nil { + i.u2 = abi.ABIInfoAsUint64() + } +} + +// TODO: delete unnecessary things. +const ( + // nop0 represents a no-op of zero size. + nop0 instructionKind = iota + 1 + // aluRRR represents an ALU operation with two register sources and a register destination. + aluRRR + // aluRRRR represents an ALU operation with three register sources and a register destination. + aluRRRR + // aluRRImm12 represents an ALU operation with a register source and an immediate-12 source, with a register destination. + aluRRImm12 + // aluRRBitmaskImm represents an ALU operation with a register source and a bitmask immediate, with a register destination. + aluRRBitmaskImm + // aluRRImmShift represents an ALU operation with a register source and an immediate-shifted source, with a register destination. + aluRRImmShift + // aluRRRShift represents an ALU operation with two register sources, one of which can be shifted, with a register destination. + aluRRRShift + // aluRRRExtend represents an ALU operation with two register sources, one of which can be extended, with a register destination. + aluRRRExtend + // bitRR represents a bit op instruction with a single register source. + bitRR + // uLoad8 represents an unsigned 8-bit load. + uLoad8 + // sLoad8 represents a signed 8-bit load into 64-bit register. + sLoad8 + // uLoad16 represents an unsigned 16-bit load into 64-bit register. + uLoad16 + // sLoad16 represents a signed 16-bit load into 64-bit register. + sLoad16 + // uLoad32 represents an unsigned 32-bit load into 64-bit register. + uLoad32 + // sLoad32 represents a signed 32-bit load into 64-bit register. + sLoad32 + // uLoad64 represents a 64-bit load. + uLoad64 + // store8 represents an 8-bit store. + store8 + // store16 represents a 16-bit store. + store16 + // store32 represents a 32-bit store. + store32 + // store64 represents a 64-bit store. + store64 + // storeP64 represents a store of a pair of registers. + storeP64 + // loadP64 represents a load of a pair of registers. + loadP64 + // mov64 represents a MOV instruction. These are encoded as ORR's but we keep them separate for better handling. + mov64 + // mov32 represents a 32-bit MOV. This zeroes the top 32 bits of the destination. + mov32 + // movZ represents a MOVZ with a 16-bit immediate. + movZ + // movN represents a MOVN with a 16-bit immediate. + movN + // movK represents a MOVK with a 16-bit immediate. + movK + // extend represents a sign- or zero-extend operation. + extend + // cSel represents a conditional-select operation. + cSel + // cSet represents a conditional-set operation. + cSet + // cCmpImm represents a conditional comparison with an immediate. + cCmpImm + // fpuMov64 represents a FPU move. Distinct from a vector-register move; moving just 64 bits appears to be significantly faster. + fpuMov64 + // fpuMov128 represents a vector register move. + fpuMov128 + // fpuMovFromVec represents a move to scalar from a vector element. + fpuMovFromVec + // fpuRR represents a 1-op FPU instruction. + fpuRR + // fpuRRR represents a 2-op FPU instruction. + fpuRRR + // fpuRRI represents a 2-op FPU instruction with immediate value. + fpuRRI + // fpuRRRR represents a 3-op FPU instruction. + fpuRRRR + // fpuCmp represents a FPU comparison, either 32 or 64 bit. + fpuCmp + // fpuLoad32 represents a floating-point load, single-precision (32 bit). + fpuLoad32 + // fpuStore32 represents a floating-point store, single-precision (32 bit). + fpuStore32 + // fpuLoad64 represents a floating-point load, double-precision (64 bit). + fpuLoad64 + // fpuStore64 represents a floating-point store, double-precision (64 bit). + fpuStore64 + // fpuLoad128 represents a floating-point/vector load, 128 bit. + fpuLoad128 + // fpuStore128 represents a floating-point/vector store, 128 bit. + fpuStore128 + // loadFpuConst32 represents a load of a 32-bit floating-point constant. + loadFpuConst32 + // loadFpuConst64 represents a load of a 64-bit floating-point constant. + loadFpuConst64 + // loadFpuConst128 represents a load of a 128-bit floating-point constant. + loadFpuConst128 + // vecLoad1R represents a load of a one single-element structure that replicates to all lanes of a vector. + vecLoad1R + // fpuToInt represents a conversion from FP to integer. + fpuToInt + // intToFpu represents a conversion from integer to FP. + intToFpu + // fpuCSel represents a 32/64-bit FP conditional select. + fpuCSel + // movToVec represents a move to a vector element from a GPR. + movToVec + // movFromVec represents an unsigned move from a vector element to a GPR. + movFromVec + // movFromVecSigned represents a signed move from a vector element to a GPR. + movFromVecSigned + // vecDup represents a duplication of general-purpose register to vector. + vecDup + // vecDupElement represents a duplication of a vector element to vector or scalar. + vecDupElement + // vecDupFromFpu represents a duplication of scalar to vector. + vecDupFromFpu + // vecExtract represents a vector extraction operation. + vecExtract + // vecExtend represents a vector extension operation. + vecExtend + // vecMovElement represents a move vector element to another vector element operation. + vecMovElement + // vecMiscNarrow represents a vector narrowing operation. + vecMiscNarrow + // vecRRR represents a vector ALU operation. + vecRRR + // vecRRRRewrite is exactly the same as vecRRR except that this rewrites the destination register. + // For example, BSL instruction rewrites the destination register, and the existing value influences the result. + // Therefore, the "destination" register in vecRRRRewrite will be treated as "use" which makes the register outlive + // the instruction while this instruction doesn't have "def" in the context of register allocation. + vecRRRRewrite + // vecMisc represents a vector two register miscellaneous instruction. + vecMisc + // vecLanes represents a vector instruction across lanes. + vecLanes + // vecShiftImm represents a SIMD scalar shift by immediate instruction. + vecShiftImm + // vecTbl represents a table vector lookup - single register table. + vecTbl + // vecTbl2 represents a table vector lookup - two register table. + vecTbl2 + // vecPermute represents a vector permute instruction. + vecPermute + // movToNZCV represents a move to the FPSR. + movToFPSR + // movFromNZCV represents a move from the FPSR. + movFromFPSR + // call represents a machine call instruction. + call + // callInd represents a machine indirect-call instruction. + callInd + // ret represents a machine return instruction. + ret + // br represents an unconditional branch. + br + // condBr represents a conditional branch. + condBr + // adr represents a compute the address (using a PC-relative offset) of a memory location. + adr + // brTableSequence represents a jump-table sequence. + brTableSequence + // exitSequence consists of multiple instructions, and exits the execution immediately. + // See encodeExitSequence. + exitSequence + // atomicRmw represents an atomic read-modify-write operation with two register sources and a register destination. + atomicRmw + // atomicCas represents an atomic compare-and-swap operation with three register sources. The value is loaded to + // the source register containing the comparison value. + atomicCas + // atomicLoad represents an atomic load with one source register and a register destination. + atomicLoad + // atomicStore represents an atomic store with two source registers and no destination. + atomicStore + // dmb represents the data memory barrier instruction in inner-shareable (ish) mode. + dmb + // tailCall represents a tail call instruction. + tailCall + // tailCallInd represents a tail call indirect instruction. + tailCallInd + // UDF is the undefined instruction. For debugging only. + udf + // loadConstBlockArg represents a load of a constant block argument. + loadConstBlockArg + + // emitSourceOffsetInfo is a dummy instruction to emit source offset info. + // The existence of this instruction does not affect the execution. + emitSourceOffsetInfo + + // ------------------- do not define below this line ------------------- + numInstructionKinds +) + +func (i *instruction) asLoadConstBlockArg(v uint64, typ ssa.Type, dst regalloc.VReg) *instruction { + i.kind = loadConstBlockArg + i.u1 = v + i.u2 = uint64(typ) + i.rd = dst + return i +} + +func (i *instruction) loadConstBlockArgData() (v uint64, typ ssa.Type, dst regalloc.VReg) { + return i.u1, ssa.Type(i.u2), i.rd +} + +func (i *instruction) asEmitSourceOffsetInfo(l ssa.SourceOffset) *instruction { + i.kind = emitSourceOffsetInfo + i.u1 = uint64(l) + return i +} + +func (i *instruction) sourceOffsetInfo() ssa.SourceOffset { + return ssa.SourceOffset(i.u1) +} + +func (i *instruction) asUDF() *instruction { + i.kind = udf + return i +} + +func (i *instruction) asFpuToInt(rd regalloc.VReg, rn operand, rdSigned, src64bit, dst64bit bool) { + i.kind = fpuToInt + i.rn = rn + i.rd = rd + if rdSigned { + i.u1 = 1 + } + if src64bit { + i.u2 = 1 + } + if dst64bit { + i.u2 |= 2 + } +} + +func (i *instruction) asIntToFpu(rd regalloc.VReg, rn operand, rnSigned, src64bit, dst64bit bool) { + i.kind = intToFpu + i.rn = rn + i.rd = rd + if rnSigned { + i.u1 = 1 + } + if src64bit { + i.u2 = 1 + } + if dst64bit { + i.u2 |= 2 + } +} + +func (i *instruction) asExitSequence(ctx regalloc.VReg) *instruction { + i.kind = exitSequence + i.rn = operandNR(ctx) + return i +} + +// aluOp determines the type of ALU operation. Instructions whose kind is one of +// aluRRR, aluRRRR, aluRRImm12, aluRRBitmaskImm, aluRRImmShift, aluRRRShift and aluRRRExtend +// would use this type. +type aluOp uint32 + +func (a aluOp) String() string { + switch a { + case aluOpAdd: + return "add" + case aluOpSub: + return "sub" + case aluOpOrr: + return "orr" + case aluOpOrn: + return "orn" + case aluOpAnd: + return "and" + case aluOpAnds: + return "ands" + case aluOpBic: + return "bic" + case aluOpEor: + return "eor" + case aluOpAddS: + return "adds" + case aluOpSubS: + return "subs" + case aluOpSMulH: + return "sMulH" + case aluOpUMulH: + return "uMulH" + case aluOpSDiv: + return "sdiv" + case aluOpUDiv: + return "udiv" + case aluOpRotR: + return "ror" + case aluOpLsr: + return "lsr" + case aluOpAsr: + return "asr" + case aluOpLsl: + return "lsl" + case aluOpMAdd: + return "madd" + case aluOpMSub: + return "msub" + } + panic(int(a)) +} + +const ( + // 32/64-bit Add. + aluOpAdd aluOp = iota + // 32/64-bit Subtract. + aluOpSub + // 32/64-bit Bitwise OR. + aluOpOrr + // 32/64-bit Bitwise OR NOT. + aluOpOrn + // 32/64-bit Bitwise AND. + aluOpAnd + // 32/64-bit Bitwise ANDS. + aluOpAnds + // 32/64-bit Bitwise AND NOT. + aluOpBic + // 32/64-bit Bitwise XOR (Exclusive OR). + aluOpEor + // 32/64-bit Add setting flags. + aluOpAddS + // 32/64-bit Subtract setting flags. + aluOpSubS + // Signed multiply, high-word result. + aluOpSMulH + // Unsigned multiply, high-word result. + aluOpUMulH + // 64-bit Signed divide. + aluOpSDiv + // 64-bit Unsigned divide. + aluOpUDiv + // 32/64-bit Rotate right. + aluOpRotR + // 32/64-bit Logical shift right. + aluOpLsr + // 32/64-bit Arithmetic shift right. + aluOpAsr + // 32/64-bit Logical shift left. + aluOpLsl /// Multiply-add + + // MAdd and MSub are only applicable for aluRRRR. + aluOpMAdd + aluOpMSub +) + +// vecOp determines the type of vector operation. Instructions whose kind is one of +// vecOpCnt would use this type. +type vecOp int + +// String implements fmt.Stringer. +func (b vecOp) String() string { + switch b { + case vecOpCnt: + return "cnt" + case vecOpCmeq: + return "cmeq" + case vecOpCmgt: + return "cmgt" + case vecOpCmhi: + return "cmhi" + case vecOpCmge: + return "cmge" + case vecOpCmhs: + return "cmhs" + case vecOpFcmeq: + return "fcmeq" + case vecOpFcmgt: + return "fcmgt" + case vecOpFcmge: + return "fcmge" + case vecOpCmeq0: + return "cmeq0" + case vecOpUaddlv: + return "uaddlv" + case vecOpBit: + return "bit" + case vecOpBic: + return "bic" + case vecOpBsl: + return "bsl" + case vecOpNot: + return "not" + case vecOpAnd: + return "and" + case vecOpOrr: + return "orr" + case vecOpEOR: + return "eor" + case vecOpFadd: + return "fadd" + case vecOpAdd: + return "add" + case vecOpAddp: + return "addp" + case vecOpAddv: + return "addv" + case vecOpSub: + return "sub" + case vecOpFsub: + return "fsub" + case vecOpSmin: + return "smin" + case vecOpUmin: + return "umin" + case vecOpUminv: + return "uminv" + case vecOpSmax: + return "smax" + case vecOpUmax: + return "umax" + case vecOpUmaxp: + return "umaxp" + case vecOpUrhadd: + return "urhadd" + case vecOpFmul: + return "fmul" + case vecOpSqrdmulh: + return "sqrdmulh" + case vecOpMul: + return "mul" + case vecOpUmlal: + return "umlal" + case vecOpFdiv: + return "fdiv" + case vecOpFsqrt: + return "fsqrt" + case vecOpAbs: + return "abs" + case vecOpFabs: + return "fabs" + case vecOpNeg: + return "neg" + case vecOpFneg: + return "fneg" + case vecOpFrintp: + return "frintp" + case vecOpFrintm: + return "frintm" + case vecOpFrintn: + return "frintn" + case vecOpFrintz: + return "frintz" + case vecOpFcvtl: + return "fcvtl" + case vecOpFcvtn: + return "fcvtn" + case vecOpFcvtzu: + return "fcvtzu" + case vecOpFcvtzs: + return "fcvtzs" + case vecOpScvtf: + return "scvtf" + case vecOpUcvtf: + return "ucvtf" + case vecOpSqxtn: + return "sqxtn" + case vecOpUqxtn: + return "uqxtn" + case vecOpSqxtun: + return "sqxtun" + case vecOpRev64: + return "rev64" + case vecOpXtn: + return "xtn" + case vecOpShll: + return "shll" + case vecOpSshl: + return "sshl" + case vecOpSshll: + return "sshll" + case vecOpUshl: + return "ushl" + case vecOpUshll: + return "ushll" + case vecOpSshr: + return "sshr" + case vecOpZip1: + return "zip1" + case vecOpFmin: + return "fmin" + case vecOpFmax: + return "fmax" + case vecOpSmull: + return "smull" + case vecOpSmull2: + return "smull2" + } + panic(int(b)) +} + +const ( + vecOpCnt vecOp = iota + vecOpCmeq0 + vecOpCmeq + vecOpCmgt + vecOpCmhi + vecOpCmge + vecOpCmhs + vecOpFcmeq + vecOpFcmgt + vecOpFcmge + vecOpUaddlv + vecOpBit + vecOpBic + vecOpBsl + vecOpNot + vecOpAnd + vecOpOrr + vecOpEOR + vecOpAdd + vecOpFadd + vecOpAddv + vecOpSqadd + vecOpUqadd + vecOpAddp + vecOpSub + vecOpFsub + vecOpSqsub + vecOpUqsub + vecOpSmin + vecOpUmin + vecOpUminv + vecOpFmin + vecOpSmax + vecOpUmax + vecOpUmaxp + vecOpFmax + vecOpUrhadd + vecOpMul + vecOpFmul + vecOpSqrdmulh + vecOpUmlal + vecOpFdiv + vecOpFsqrt + vecOpAbs + vecOpFabs + vecOpNeg + vecOpFneg + vecOpFrintm + vecOpFrintn + vecOpFrintp + vecOpFrintz + vecOpFcvtl + vecOpFcvtn + vecOpFcvtzs + vecOpFcvtzu + vecOpScvtf + vecOpUcvtf + vecOpSqxtn + vecOpSqxtun + vecOpUqxtn + vecOpRev64 + vecOpXtn + vecOpShll + vecOpSshl + vecOpSshll + vecOpUshl + vecOpUshll + vecOpSshr + vecOpZip1 + vecOpSmull + vecOpSmull2 +) + +// bitOp determines the type of bitwise operation. Instructions whose kind is one of +// bitOpRbit and bitOpClz would use this type. +type bitOp int + +// String implements fmt.Stringer. +func (b bitOp) String() string { + switch b { + case bitOpRbit: + return "rbit" + case bitOpClz: + return "clz" + } + panic(int(b)) +} + +const ( + // 32/64-bit Rbit. + bitOpRbit bitOp = iota + // 32/64-bit Clz. + bitOpClz +) + +// fpuUniOp represents a unary floating-point unit (FPU) operation. +type fpuUniOp byte + +const ( + fpuUniOpNeg fpuUniOp = iota + fpuUniOpCvt32To64 + fpuUniOpCvt64To32 + fpuUniOpSqrt + fpuUniOpRoundPlus + fpuUniOpRoundMinus + fpuUniOpRoundZero + fpuUniOpRoundNearest + fpuUniOpAbs +) + +// String implements the fmt.Stringer. +func (f fpuUniOp) String() string { + switch f { + case fpuUniOpNeg: + return "fneg" + case fpuUniOpCvt32To64: + return "fcvt" + case fpuUniOpCvt64To32: + return "fcvt" + case fpuUniOpSqrt: + return "fsqrt" + case fpuUniOpRoundPlus: + return "frintp" + case fpuUniOpRoundMinus: + return "frintm" + case fpuUniOpRoundZero: + return "frintz" + case fpuUniOpRoundNearest: + return "frintn" + case fpuUniOpAbs: + return "fabs" + } + panic(int(f)) +} + +// fpuBinOp represents a binary floating-point unit (FPU) operation. +type fpuBinOp byte + +const ( + fpuBinOpAdd = iota + fpuBinOpSub + fpuBinOpMul + fpuBinOpDiv + fpuBinOpMax + fpuBinOpMin +) + +// String implements the fmt.Stringer. +func (f fpuBinOp) String() string { + switch f { + case fpuBinOpAdd: + return "fadd" + case fpuBinOpSub: + return "fsub" + case fpuBinOpMul: + return "fmul" + case fpuBinOpDiv: + return "fdiv" + case fpuBinOpMax: + return "fmax" + case fpuBinOpMin: + return "fmin" + } + panic(int(f)) +} + +// extMode represents the mode of a register operand extension. +// For example, aluRRRExtend instructions need this info to determine the extensions. +type extMode byte + +const ( + extModeNone extMode = iota + // extModeZeroExtend64 suggests a zero-extension to 32 bits if the original bit size is less than 32. + extModeZeroExtend32 + // extModeSignExtend64 stands for a sign-extension to 32 bits if the original bit size is less than 32. + extModeSignExtend32 + // extModeZeroExtend64 suggests a zero-extension to 64 bits if the original bit size is less than 64. + extModeZeroExtend64 + // extModeSignExtend64 stands for a sign-extension to 64 bits if the original bit size is less than 64. + extModeSignExtend64 +) + +func (e extMode) bits() byte { + switch e { + case extModeZeroExtend32, extModeSignExtend32: + return 32 + case extModeZeroExtend64, extModeSignExtend64: + return 64 + default: + return 0 + } +} + +func (e extMode) signed() bool { + switch e { + case extModeSignExtend32, extModeSignExtend64: + return true + default: + return false + } +} + +func extModeOf(t ssa.Type, signed bool) extMode { + switch t.Bits() { + case 32: + if signed { + return extModeSignExtend32 + } + return extModeZeroExtend32 + case 64: + if signed { + return extModeSignExtend64 + } + return extModeZeroExtend64 + default: + panic("TODO? do we need narrower than 32 bits?") + } +} + +type extendOp byte + +const ( + extendOpUXTB extendOp = 0b000 + extendOpUXTH extendOp = 0b001 + extendOpUXTW extendOp = 0b010 + // extendOpUXTX does nothing, but convenient symbol that officially exists. See: + // https://stackoverflow.com/questions/72041372/what-do-the-uxtx-and-sxtx-extensions-mean-for-32-bit-aarch64-adds-instruct + extendOpUXTX extendOp = 0b011 + extendOpSXTB extendOp = 0b100 + extendOpSXTH extendOp = 0b101 + extendOpSXTW extendOp = 0b110 + // extendOpSXTX does nothing, but convenient symbol that officially exists. See: + // https://stackoverflow.com/questions/72041372/what-do-the-uxtx-and-sxtx-extensions-mean-for-32-bit-aarch64-adds-instruct + extendOpSXTX extendOp = 0b111 + extendOpNone extendOp = 0xff +) + +func (e extendOp) srcBits() byte { + switch e { + case extendOpUXTB, extendOpSXTB: + return 8 + case extendOpUXTH, extendOpSXTH: + return 16 + case extendOpUXTW, extendOpSXTW: + return 32 + case extendOpUXTX, extendOpSXTX: + return 64 + } + panic(int(e)) +} + +func (e extendOp) String() string { + switch e { + case extendOpUXTB: + return "UXTB" + case extendOpUXTH: + return "UXTH" + case extendOpUXTW: + return "UXTW" + case extendOpUXTX: + return "UXTX" + case extendOpSXTB: + return "SXTB" + case extendOpSXTH: + return "SXTH" + case extendOpSXTW: + return "SXTW" + case extendOpSXTX: + return "SXTX" + } + panic(int(e)) +} + +func extendOpFrom(signed bool, from byte) extendOp { + switch from { + case 8: + if signed { + return extendOpSXTB + } + return extendOpUXTB + case 16: + if signed { + return extendOpSXTH + } + return extendOpUXTH + case 32: + if signed { + return extendOpSXTW + } + return extendOpUXTW + case 64: + if signed { + return extendOpSXTX + } + return extendOpUXTX + } + panic("invalid extendOpFrom") +} + +type shiftOp byte + +const ( + shiftOpLSL shiftOp = 0b00 + shiftOpLSR shiftOp = 0b01 + shiftOpASR shiftOp = 0b10 + shiftOpROR shiftOp = 0b11 +) + +func (s shiftOp) String() string { + switch s { + case shiftOpLSL: + return "lsl" + case shiftOpLSR: + return "lsr" + case shiftOpASR: + return "asr" + case shiftOpROR: + return "ror" + } + panic(int(s)) +} + +const exitSequenceSize = 6 * 4 // 6 instructions as in encodeExitSequence. + +// size returns the size of the instruction in encoded bytes. +func (i *instruction) size() int64 { + switch i.kind { + case exitSequence: + return exitSequenceSize // 5 instructions as in encodeExitSequence. + case nop0, loadConstBlockArg: + return 0 + case emitSourceOffsetInfo: + return 0 + case loadFpuConst32: + if i.u1 == 0 { + return 4 // zero loading can be encoded as a single instruction. + } + return 4 + 4 + 4 + case loadFpuConst64: + if i.u1 == 0 { + return 4 // zero loading can be encoded as a single instruction. + } + return 4 + 4 + 8 + case loadFpuConst128: + if i.u1 == 0 && i.u2 == 0 { + return 4 // zero loading can be encoded as a single instruction. + } + return 4 + 4 + 16 + case brTableSequence: + return 4*4 + int64(i.u2)*4 + default: + return 4 + } +} + +// vecArrangement is the arrangement of data within a vector register. +type vecArrangement byte + +const ( + // vecArrangementNone is an arrangement indicating no data is stored. + vecArrangementNone vecArrangement = iota + // vecArrangement8B is an arrangement of 8 bytes (64-bit vector) + vecArrangement8B + // vecArrangement16B is an arrangement of 16 bytes (128-bit vector) + vecArrangement16B + // vecArrangement4H is an arrangement of 4 half precisions (64-bit vector) + vecArrangement4H + // vecArrangement8H is an arrangement of 8 half precisions (128-bit vector) + vecArrangement8H + // vecArrangement2S is an arrangement of 2 single precisions (64-bit vector) + vecArrangement2S + // vecArrangement4S is an arrangement of 4 single precisions (128-bit vector) + vecArrangement4S + // vecArrangement1D is an arrangement of 1 double precision (64-bit vector) + vecArrangement1D + // vecArrangement2D is an arrangement of 2 double precisions (128-bit vector) + vecArrangement2D + + // Assign each vector size specifier to a vector arrangement ID. + // Instructions can only have an arrangement or a size specifier, but not both, so it + // simplifies the internal representation of vector instructions by being able to + // store either into the same field. + + // vecArrangementB is a size specifier of byte + vecArrangementB + // vecArrangementH is a size specifier of word (16-bit) + vecArrangementH + // vecArrangementS is a size specifier of double word (32-bit) + vecArrangementS + // vecArrangementD is a size specifier of quad word (64-bit) + vecArrangementD + // vecArrangementQ is a size specifier of the entire vector (128-bit) + vecArrangementQ +) + +// String implements fmt.Stringer +func (v vecArrangement) String() (ret string) { + switch v { + case vecArrangement8B: + ret = "8B" + case vecArrangement16B: + ret = "16B" + case vecArrangement4H: + ret = "4H" + case vecArrangement8H: + ret = "8H" + case vecArrangement2S: + ret = "2S" + case vecArrangement4S: + ret = "4S" + case vecArrangement1D: + ret = "1D" + case vecArrangement2D: + ret = "2D" + case vecArrangementB: + ret = "B" + case vecArrangementH: + ret = "H" + case vecArrangementS: + ret = "S" + case vecArrangementD: + ret = "D" + case vecArrangementQ: + ret = "Q" + case vecArrangementNone: + ret = "none" + default: + panic(v) + } + return +} + +// vecIndex is the index of an element of a vector register +type vecIndex byte + +// vecIndexNone indicates no vector index specified. +const vecIndexNone = ^vecIndex(0) + +func ssaLaneToArrangement(lane ssa.VecLane) vecArrangement { + switch lane { + case ssa.VecLaneI8x16: + return vecArrangement16B + case ssa.VecLaneI16x8: + return vecArrangement8H + case ssa.VecLaneI32x4: + return vecArrangement4S + case ssa.VecLaneI64x2: + return vecArrangement2D + case ssa.VecLaneF32x4: + return vecArrangement4S + case ssa.VecLaneF64x2: + return vecArrangement2D + default: + panic(lane) + } +} + +// atomicRmwOp is the type of atomic read-modify-write operation. +type atomicRmwOp byte + +const ( + // atomicRmwOpAdd is an atomic add operation. + atomicRmwOpAdd atomicRmwOp = iota + // atomicRmwOpClr is an atomic clear operation, i.e. AND NOT. + atomicRmwOpClr + // atomicRmwOpSet is an atomic set operation, i.e. OR. + atomicRmwOpSet + // atomicRmwOpEor is an atomic exclusive OR operation. + atomicRmwOpEor + // atomicRmwOpSwp is an atomic swap operation. + atomicRmwOpSwp +) + +// String implements fmt.Stringer +func (a atomicRmwOp) String() string { + switch a { + case atomicRmwOpAdd: + return "ldaddal" + case atomicRmwOpClr: + return "ldclral" + case atomicRmwOpSet: + return "ldsetal" + case atomicRmwOpEor: + return "ldeoral" + case atomicRmwOpSwp: + return "swpal" + } + panic(fmt.Sprintf("unknown atomicRmwOp: %d", a)) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go new file mode 100644 index 00000000000..5326a5e2821 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go @@ -0,0 +1,2357 @@ +package arm64 + +import ( + "context" + "fmt" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +// Encode implements backend.Machine Encode. +func (m *machine) Encode(ctx context.Context) error { + m.resolveRelativeAddresses(ctx) + m.encode(m.rootInstr) + if l := len(m.compiler.Buf()); l > maxFunctionExecutableSize { + return fmt.Errorf("function size exceeds the limit: %d > %d", l, maxFunctionExecutableSize) + } + return nil +} + +func (m *machine) encode(root *instruction) { + for cur := root; cur != nil; cur = cur.next { + cur.encode(m) + } +} + +func (i *instruction) encode(m *machine) { + c := m.compiler + switch kind := i.kind; kind { + case nop0, emitSourceOffsetInfo, loadConstBlockArg: + case exitSequence: + encodeExitSequence(c, i.rn.reg()) + case ret: + // https://developer.arm.com/documentation/ddi0596/2020-12/Base-Instructions/RET--Return-from-subroutine-?lang=en + c.Emit4Bytes(encodeRet()) + case br: + imm := i.brOffset() + c.Emit4Bytes(encodeUnconditionalBranch(false, imm)) + case call: + // We still don't know the exact address of the function to call, so we emit a placeholder. + c.AddRelocationInfo(i.callFuncRef(), false) + c.Emit4Bytes(encodeUnconditionalBranch(true, 0)) // 0 = placeholder + case callInd: + c.Emit4Bytes(encodeUnconditionalBranchReg(regNumberInEncoding[i.rn.realReg()], true)) + case store8, store16, store32, store64, fpuStore32, fpuStore64, fpuStore128: + c.Emit4Bytes(encodeLoadOrStore(i.kind, regNumberInEncoding[i.rn.realReg()], *i.getAmode())) + case uLoad8, uLoad16, uLoad32, uLoad64, sLoad8, sLoad16, sLoad32, fpuLoad32, fpuLoad64, fpuLoad128: + c.Emit4Bytes(encodeLoadOrStore(i.kind, regNumberInEncoding[i.rd.RealReg()], *i.getAmode())) + case vecLoad1R: + c.Emit4Bytes(encodeVecLoad1R( + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + vecArrangement(i.u1))) + case condBr: + imm19 := i.condBrOffset() + if imm19%4 != 0 { + panic("imm26 for branch must be a multiple of 4") + } + + imm19U32 := uint32(imm19/4) & 0b111_11111111_11111111 + brCond := i.condBrCond() + switch brCond.kind() { + case condKindRegisterZero: + rt := regNumberInEncoding[brCond.register().RealReg()] + c.Emit4Bytes(encodeCBZCBNZ(rt, false, imm19U32, i.condBr64bit())) + case condKindRegisterNotZero: + rt := regNumberInEncoding[brCond.register().RealReg()] + c.Emit4Bytes(encodeCBZCBNZ(rt, true, imm19U32, i.condBr64bit())) + case condKindCondFlagSet: + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/B-cond--Branch-conditionally- + fl := brCond.flag() + c.Emit4Bytes(0b01010100<<24 | (imm19U32 << 5) | uint32(fl)) + default: + panic("BUG") + } + case movN: + c.Emit4Bytes(encodeMoveWideImmediate(0b00, regNumberInEncoding[i.rd.RealReg()], i.u1, uint32(i.u2), uint32(i.u2>>32))) + case movZ: + c.Emit4Bytes(encodeMoveWideImmediate(0b10, regNumberInEncoding[i.rd.RealReg()], i.u1, uint32(i.u2), uint32(i.u2>>32))) + case movK: + c.Emit4Bytes(encodeMoveWideImmediate(0b11, regNumberInEncoding[i.rd.RealReg()], i.u1, uint32(i.u2), uint32(i.u2>>32))) + case mov32: + to, from := i.rd.RealReg(), i.rn.realReg() + c.Emit4Bytes(encodeAsMov32(regNumberInEncoding[from], regNumberInEncoding[to])) + case mov64: + to, from := i.rd.RealReg(), i.rn.realReg() + toIsSp := to == sp + fromIsSp := from == sp + c.Emit4Bytes(encodeMov64(regNumberInEncoding[to], regNumberInEncoding[from], toIsSp, fromIsSp)) + case loadP64, storeP64: + rt, rt2 := regNumberInEncoding[i.rn.realReg()], regNumberInEncoding[i.rm.realReg()] + amode := i.getAmode() + rn := regNumberInEncoding[amode.rn.RealReg()] + var pre bool + switch amode.kind { + case addressModeKindPostIndex: + case addressModeKindPreIndex: + pre = true + default: + panic("BUG") + } + c.Emit4Bytes(encodePreOrPostIndexLoadStorePair64(pre, kind == loadP64, rn, rt, rt2, amode.imm)) + case loadFpuConst32: + rd := regNumberInEncoding[i.rd.RealReg()] + if i.u1 == 0 { + c.Emit4Bytes(encodeVecRRR(vecOpEOR, rd, rd, rd, vecArrangement8B)) + } else { + encodeLoadFpuConst32(c, rd, i.u1) + } + case loadFpuConst64: + rd := regNumberInEncoding[i.rd.RealReg()] + if i.u1 == 0 { + c.Emit4Bytes(encodeVecRRR(vecOpEOR, rd, rd, rd, vecArrangement8B)) + } else { + encodeLoadFpuConst64(c, regNumberInEncoding[i.rd.RealReg()], i.u1) + } + case loadFpuConst128: + rd := regNumberInEncoding[i.rd.RealReg()] + lo, hi := i.u1, i.u2 + if lo == 0 && hi == 0 { + c.Emit4Bytes(encodeVecRRR(vecOpEOR, rd, rd, rd, vecArrangement16B)) + } else { + encodeLoadFpuConst128(c, rd, lo, hi) + } + case aluRRRR: + c.Emit4Bytes(encodeAluRRRR( + aluOp(i.u1), + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + regNumberInEncoding[i.rm.realReg()], + regNumberInEncoding[regalloc.VReg(i.u2).RealReg()], + uint32(i.u1>>32), + )) + case aluRRImmShift: + c.Emit4Bytes(encodeAluRRImm( + aluOp(i.u1), + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + uint32(i.rm.shiftImm()), + uint32(i.u2>>32), + )) + case aluRRR: + rn := i.rn.realReg() + c.Emit4Bytes(encodeAluRRR( + aluOp(i.u1), + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[rn], + regNumberInEncoding[i.rm.realReg()], + i.u2>>32 == 1, + rn == sp, + )) + case aluRRRExtend: + rm, exo, to := i.rm.er() + c.Emit4Bytes(encodeAluRRRExtend( + aluOp(i.u1), + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + regNumberInEncoding[rm.RealReg()], + exo, + to, + )) + case aluRRRShift: + r, amt, sop := i.rm.sr() + c.Emit4Bytes(encodeAluRRRShift( + aluOp(i.u1), + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + regNumberInEncoding[r.RealReg()], + uint32(amt), + sop, + i.u2>>32 == 1, + )) + case aluRRBitmaskImm: + c.Emit4Bytes(encodeAluBitmaskImmediate( + aluOp(i.u1), + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + i.u2, + i.u1>>32 == 1, + )) + case bitRR: + c.Emit4Bytes(encodeBitRR( + bitOp(i.u1), + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + uint32(i.u2)), + ) + case aluRRImm12: + imm12, shift := i.rm.imm12() + c.Emit4Bytes(encodeAluRRImm12( + aluOp(i.u1), + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + imm12, shift, + i.u2>>32 == 1, + )) + case fpuRRR: + c.Emit4Bytes(encodeFpuRRR( + fpuBinOp(i.u1), + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + regNumberInEncoding[i.rm.realReg()], + i.u2 == 1, + )) + case fpuMov64, fpuMov128: + // https://developer.arm.com/documentation/ddi0596/2021-12/SIMD-FP-Instructions/MOV--vector---Move-vector--an-alias-of-ORR--vector--register-- + rd := regNumberInEncoding[i.rd.RealReg()] + rn := regNumberInEncoding[i.rn.realReg()] + var q uint32 + if kind == fpuMov128 { + q = 0b1 + } + c.Emit4Bytes(q<<30 | 0b1110101<<21 | rn<<16 | 0b000111<<10 | rn<<5 | rd) + case cSet: + rd := regNumberInEncoding[i.rd.RealReg()] + cf := condFlag(i.u1) + if i.u2 == 1 { + // https://developer.arm.com/documentation/ddi0602/2022-03/Base-Instructions/CSETM--Conditional-Set-Mask--an-alias-of-CSINV- + // Note that we set 64bit version here. + c.Emit4Bytes(0b1101101010011111<<16 | uint32(cf.invert())<<12 | 0b011111<<5 | rd) + } else { + // https://developer.arm.com/documentation/ddi0602/2022-06/Base-Instructions/CSET--Conditional-Set--an-alias-of-CSINC- + // Note that we set 64bit version here. + c.Emit4Bytes(0b1001101010011111<<16 | uint32(cf.invert())<<12 | 0b111111<<5 | rd) + } + case extend: + c.Emit4Bytes(encodeExtend((i.u2>>32) == 1, byte(i.u1), byte(i.u2), regNumberInEncoding[i.rd.RealReg()], regNumberInEncoding[i.rn.realReg()])) + case fpuCmp: + // https://developer.arm.com/documentation/ddi0596/2020-12/SIMD-FP-Instructions/FCMP--Floating-point-quiet-Compare--scalar--?lang=en + rn, rm := regNumberInEncoding[i.rn.realReg()], regNumberInEncoding[i.rm.realReg()] + var ftype uint32 + if i.u1 == 1 { + ftype = 0b01 // double precision. + } + c.Emit4Bytes(0b1111<<25 | ftype<<22 | 1<<21 | rm<<16 | 0b1<<13 | rn<<5) + case udf: + // https://developer.arm.com/documentation/ddi0596/2020-12/Base-Instructions/UDF--Permanently-Undefined-?lang=en + if wazevoapi.PrintMachineCodeHexPerFunctionDisassemblable { + c.Emit4Bytes(dummyInstruction) + } else { + c.Emit4Bytes(0) + } + case adr: + c.Emit4Bytes(encodeAdr(regNumberInEncoding[i.rd.RealReg()], uint32(i.u1))) + case cSel: + c.Emit4Bytes(encodeConditionalSelect( + kind, + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + regNumberInEncoding[i.rm.realReg()], + condFlag(i.u1), + i.u2 == 1, + )) + case fpuCSel: + c.Emit4Bytes(encodeFpuCSel( + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + regNumberInEncoding[i.rm.realReg()], + condFlag(i.u1), + i.u2 == 1, + )) + case movToVec: + c.Emit4Bytes(encodeMoveToVec( + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + vecArrangement(byte(i.u1)), + vecIndex(i.u2), + )) + case movFromVec, movFromVecSigned: + c.Emit4Bytes(encodeMoveFromVec( + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + vecArrangement(byte(i.u1)), + vecIndex(i.u2), + i.kind == movFromVecSigned, + )) + case vecDup: + c.Emit4Bytes(encodeVecDup( + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + vecArrangement(byte(i.u1)))) + case vecDupElement: + c.Emit4Bytes(encodeVecDupElement( + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + vecArrangement(byte(i.u1)), + vecIndex(i.u2))) + case vecExtract: + c.Emit4Bytes(encodeVecExtract( + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + regNumberInEncoding[i.rm.realReg()], + vecArrangement(byte(i.u1)), + uint32(i.u2))) + case vecPermute: + c.Emit4Bytes(encodeVecPermute( + vecOp(i.u1), + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + regNumberInEncoding[i.rm.realReg()], + vecArrangement(byte(i.u2)))) + case vecMovElement: + c.Emit4Bytes(encodeVecMovElement( + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + vecArrangement(i.u1), + uint32(i.u2), uint32(i.u2>>32), + )) + case vecMisc: + c.Emit4Bytes(encodeAdvancedSIMDTwoMisc( + vecOp(i.u1), + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + vecArrangement(i.u2), + )) + case vecLanes: + c.Emit4Bytes(encodeVecLanes( + vecOp(i.u1), + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + vecArrangement(i.u2), + )) + case vecShiftImm: + c.Emit4Bytes(encodeVecShiftImm( + vecOp(i.u1), + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + uint32(i.rm.shiftImm()), + vecArrangement(i.u2), + )) + case vecTbl: + c.Emit4Bytes(encodeVecTbl( + 1, + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + regNumberInEncoding[i.rm.realReg()], + vecArrangement(i.u2)), + ) + case vecTbl2: + c.Emit4Bytes(encodeVecTbl( + 2, + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + regNumberInEncoding[i.rm.realReg()], + vecArrangement(i.u2)), + ) + case brTableSequence: + targets := m.jmpTableTargets[i.u1] + encodeBrTableSequence(c, i.rn.reg(), targets) + case fpuToInt, intToFpu: + c.Emit4Bytes(encodeCnvBetweenFloatInt(i)) + case fpuRR: + c.Emit4Bytes(encodeFloatDataOneSource( + fpuUniOp(i.u1), + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + i.u2 == 1, + )) + case vecRRR: + if op := vecOp(i.u1); op == vecOpBsl || op == vecOpBit || op == vecOpUmlal { + panic(fmt.Sprintf("vecOp %s must use vecRRRRewrite instead of vecRRR", op.String())) + } + fallthrough + case vecRRRRewrite: + c.Emit4Bytes(encodeVecRRR( + vecOp(i.u1), + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + regNumberInEncoding[i.rm.realReg()], + vecArrangement(i.u2), + )) + case cCmpImm: + // Conditional compare (immediate) in https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Register?lang=en + sf := uint32((i.u2 >> 32) & 0b1) + nzcv := uint32(i.u2 & 0b1111) + cond := uint32(condFlag(i.u1)) + imm := uint32(i.rm.data & 0b11111) + rn := regNumberInEncoding[i.rn.realReg()] + c.Emit4Bytes( + sf<<31 | 0b111101001<<22 | imm<<16 | cond<<12 | 0b1<<11 | rn<<5 | nzcv, + ) + case movFromFPSR: + rt := regNumberInEncoding[i.rd.RealReg()] + c.Emit4Bytes(encodeSystemRegisterMove(rt, true)) + case movToFPSR: + rt := regNumberInEncoding[i.rn.realReg()] + c.Emit4Bytes(encodeSystemRegisterMove(rt, false)) + case atomicRmw: + c.Emit4Bytes(encodeAtomicRmw( + atomicRmwOp(i.u1), + regNumberInEncoding[i.rm.realReg()], + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rn.realReg()], + uint32(i.u2), + )) + case atomicCas: + c.Emit4Bytes(encodeAtomicCas( + regNumberInEncoding[i.rd.RealReg()], + regNumberInEncoding[i.rm.realReg()], + regNumberInEncoding[i.rn.realReg()], + uint32(i.u2), + )) + case atomicLoad: + c.Emit4Bytes(encodeAtomicLoadStore( + regNumberInEncoding[i.rn.realReg()], + regNumberInEncoding[i.rd.RealReg()], + uint32(i.u2), + 1, + )) + case atomicStore: + c.Emit4Bytes(encodeAtomicLoadStore( + regNumberInEncoding[i.rn.realReg()], + regNumberInEncoding[i.rm.realReg()], + uint32(i.u2), + 0, + )) + case dmb: + c.Emit4Bytes(encodeDMB()) + case tailCall: + // We still don't know the exact address of the function to call, so we emit a placeholder. + c.AddRelocationInfo(i.callFuncRef(), true) // true = IsTailCall + c.Emit4Bytes(encodeUnconditionalBranch(false, 0)) // 0 = placeholder + case tailCallInd: + c.Emit4Bytes(encodeUnconditionalBranchReg(regNumberInEncoding[i.rn.realReg()], false)) + default: + panic(i.String()) + } +} + +func encodeMov64(rd, rn uint32, toIsSp, fromIsSp bool) uint32 { + if toIsSp || fromIsSp { + // This is an alias of ADD (immediate): + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/MOV--to-from-SP---Move-between-register-and-stack-pointer--an-alias-of-ADD--immediate-- + return encodeAddSubtractImmediate(0b100, 0, 0, rn, rd) + } else { + // This is an alias of ORR (shifted register): + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/MOV--register---Move--register---an-alias-of-ORR--shifted-register-- + return encodeLogicalShiftedRegister(0b101, 0, rn, 0, regNumberInEncoding[xzr], rd) + } +} + +// encodeSystemRegisterMove encodes as "System register move" in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Branches--Exception-Generating-and-System-instructions?lang=en +// +// Note that currently we only supports read/write of FPSR. +func encodeSystemRegisterMove(rt uint32, fromSystem bool) uint32 { + ret := 0b11010101<<24 | 0b11011<<16 | 0b01000100<<8 | 0b001<<5 | rt + if fromSystem { + ret |= 0b1 << 21 + } + return ret +} + +// encodeVecRRR encodes as either "Advanced SIMD three *" in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en +func encodeVecRRR(op vecOp, rd, rn, rm uint32, arr vecArrangement) uint32 { + switch op { + case vecOpBit: + _, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00011, 0b10 /* always has size 0b10 */, 0b1, q) + case vecOpBic: + if arr > vecArrangement16B { + panic("unsupported arrangement: " + arr.String()) + } + _, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00011, 0b01 /* always has size 0b01 */, 0b0, q) + case vecOpBsl: + if arr > vecArrangement16B { + panic("unsupported arrangement: " + arr.String()) + } + _, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00011, 0b01 /* always has size 0b01 */, 0b1, q) + case vecOpAnd: + if arr > vecArrangement16B { + panic("unsupported arrangement: " + arr.String()) + } + _, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00011, 0b00 /* always has size 0b00 */, 0b0, q) + case vecOpOrr: + _, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00011, 0b10 /* always has size 0b10 */, 0b0, q) + case vecOpEOR: + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00011, size, 0b1, q) + case vecOpCmeq: + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b10001, size, 0b1, q) + case vecOpCmgt: + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00110, size, 0b0, q) + case vecOpCmhi: + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00110, size, 0b1, q) + case vecOpCmge: + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00111, size, 0b0, q) + case vecOpCmhs: + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00111, size, 0b1, q) + case vecOpFcmeq: + var size, q uint32 + switch arr { + case vecArrangement4S: + size, q = 0b00, 0b1 + case vecArrangement2S: + size, q = 0b00, 0b0 + case vecArrangement2D: + size, q = 0b01, 0b1 + default: + panic("unsupported arrangement: " + arr.String()) + } + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b11100, size, 0b0, q) + case vecOpFcmgt: + if arr < vecArrangement2S || arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b11100, size, 0b1, q) + case vecOpFcmge: + var size, q uint32 + switch arr { + case vecArrangement4S: + size, q = 0b00, 0b1 + case vecArrangement2S: + size, q = 0b00, 0b0 + case vecArrangement2D: + size, q = 0b01, 0b1 + default: + panic("unsupported arrangement: " + arr.String()) + } + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b11100, size, 0b1, q) + case vecOpAdd: + if arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b10000, size, 0b0, q) + case vecOpSqadd: + if arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00001, size, 0b0, q) + case vecOpUqadd: + if arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00001, size, 0b1, q) + case vecOpAddp: + if arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b10111, size, 0b0, q) + case vecOpSqsub: + if arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00101, size, 0b0, q) + case vecOpUqsub: + if arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00101, size, 0b1, q) + case vecOpSub: + if arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b10000, size, 0b1, q) + case vecOpFmin: + if arr < vecArrangement2S || arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b11110, size, 0b0, q) + case vecOpSmin: + if arr > vecArrangement4S { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b01101, size, 0b0, q) + case vecOpUmin: + if arr > vecArrangement4S { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b01101, size, 0b1, q) + case vecOpFmax: + var size, q uint32 + switch arr { + case vecArrangement4S: + size, q = 0b00, 0b1 + case vecArrangement2S: + size, q = 0b00, 0b0 + case vecArrangement2D: + size, q = 0b01, 0b1 + default: + panic("unsupported arrangement: " + arr.String()) + } + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b11110, size, 0b0, q) + case vecOpFadd: + var size, q uint32 + switch arr { + case vecArrangement4S: + size, q = 0b00, 0b1 + case vecArrangement2S: + size, q = 0b00, 0b0 + case vecArrangement2D: + size, q = 0b01, 0b1 + default: + panic("unsupported arrangement: " + arr.String()) + } + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b11010, size, 0b0, q) + case vecOpFsub: + if arr < vecArrangement2S || arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b11010, size, 0b0, q) + case vecOpFmul: + var size, q uint32 + switch arr { + case vecArrangement4S: + size, q = 0b00, 0b1 + case vecArrangement2S: + size, q = 0b00, 0b0 + case vecArrangement2D: + size, q = 0b01, 0b1 + default: + panic("unsupported arrangement: " + arr.String()) + } + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b11011, size, 0b1, q) + case vecOpSqrdmulh: + if arr < vecArrangement4H || arr > vecArrangement4S { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b10110, size, 0b1, q) + case vecOpFdiv: + var size, q uint32 + switch arr { + case vecArrangement4S: + size, q = 0b00, 0b1 + case vecArrangement2S: + size, q = 0b00, 0b0 + case vecArrangement2D: + size, q = 0b01, 0b1 + default: + panic("unsupported arrangement: " + arr.String()) + } + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b11111, size, 0b1, q) + case vecOpSmax: + if arr > vecArrangement4S { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b01100, size, 0b0, q) + case vecOpUmax: + if arr > vecArrangement4S { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b01100, size, 0b1, q) + case vecOpUmaxp: + if arr > vecArrangement4S { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b10100, size, 0b1, q) + case vecOpUrhadd: + if arr > vecArrangement4S { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00010, size, 0b1, q) + case vecOpMul: + if arr > vecArrangement4S { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b10011, size, 0b0, q) + case vecOpUmlal: + if arr > vecArrangement4S { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeDifferent(rd, rn, rm, 0b1000, size, 0b1, q) + case vecOpSshl: + if arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b01000, size, 0b0, q) + case vecOpUshl: + if arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + size, q := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b01000, size, 0b1, q) + + case vecOpSmull: + if arr > vecArrangement4S { + panic("unsupported arrangement: " + arr.String()) + } + size, _ := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeDifferent(rd, rn, rm, 0b1100, size, 0b0, 0b0) + + case vecOpSmull2: + if arr > vecArrangement4S { + panic("unsupported arrangement: " + arr.String()) + } + size, _ := arrToSizeQEncoded(arr) + return encodeAdvancedSIMDThreeDifferent(rd, rn, rm, 0b1100, size, 0b0, 0b1) + + default: + panic("TODO: " + op.String()) + } +} + +func arrToSizeQEncoded(arr vecArrangement) (size, q uint32) { + switch arr { + case vecArrangement16B: + q = 0b1 + fallthrough + case vecArrangement8B: + size = 0b00 + case vecArrangement8H: + q = 0b1 + fallthrough + case vecArrangement4H: + size = 0b01 + case vecArrangement4S: + q = 0b1 + fallthrough + case vecArrangement2S: + size = 0b10 + case vecArrangement2D: + q = 0b1 + fallthrough + case vecArrangement1D: + size = 0b11 + default: + panic("BUG") + } + return +} + +// encodeAdvancedSIMDThreeSame encodes as "Advanced SIMD three same" in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en +func encodeAdvancedSIMDThreeSame(rd, rn, rm, opcode, size, U, Q uint32) uint32 { + return Q<<30 | U<<29 | 0b111<<25 | size<<22 | 0b1<<21 | rm<<16 | opcode<<11 | 0b1<<10 | rn<<5 | rd +} + +// encodeAdvancedSIMDThreeDifferent encodes as "Advanced SIMD three different" in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en +func encodeAdvancedSIMDThreeDifferent(rd, rn, rm, opcode, size, U, Q uint32) uint32 { + return Q<<30 | U<<29 | 0b111<<25 | size<<22 | 0b1<<21 | rm<<16 | opcode<<12 | rn<<5 | rd +} + +// encodeFloatDataOneSource encodes as "Floating-point data-processing (1 source)" in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en#simd-dp +func encodeFloatDataOneSource(op fpuUniOp, rd, rn uint32, dst64bit bool) uint32 { + var opcode, ptype uint32 + switch op { + case fpuUniOpCvt32To64: + opcode = 0b000101 + case fpuUniOpCvt64To32: + opcode = 0b000100 + ptype = 0b01 + case fpuUniOpNeg: + opcode = 0b000010 + if dst64bit { + ptype = 0b01 + } + case fpuUniOpSqrt: + opcode = 0b000011 + if dst64bit { + ptype = 0b01 + } + case fpuUniOpRoundPlus: + opcode = 0b001001 + if dst64bit { + ptype = 0b01 + } + case fpuUniOpRoundMinus: + opcode = 0b001010 + if dst64bit { + ptype = 0b01 + } + case fpuUniOpRoundZero: + opcode = 0b001011 + if dst64bit { + ptype = 0b01 + } + case fpuUniOpRoundNearest: + opcode = 0b001000 + if dst64bit { + ptype = 0b01 + } + case fpuUniOpAbs: + opcode = 0b000001 + if dst64bit { + ptype = 0b01 + } + default: + panic("BUG") + } + return 0b1111<<25 | ptype<<22 | 0b1<<21 | opcode<<15 | 0b1<<14 | rn<<5 | rd +} + +// encodeCnvBetweenFloatInt encodes as "Conversion between floating-point and integer" in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en +func encodeCnvBetweenFloatInt(i *instruction) uint32 { + rd := regNumberInEncoding[i.rd.RealReg()] + rn := regNumberInEncoding[i.rn.realReg()] + + var opcode uint32 + var rmode uint32 + var ptype uint32 + var sf uint32 + switch i.kind { + case intToFpu: // Either UCVTF or SCVTF. + rmode = 0b00 + + signed := i.u1 == 1 + src64bit := i.u2&1 != 0 + dst64bit := i.u2&2 != 0 + if signed { + opcode = 0b010 + } else { + opcode = 0b011 + } + if src64bit { + sf = 0b1 + } + if dst64bit { + ptype = 0b01 + } else { + ptype = 0b00 + } + case fpuToInt: // Either FCVTZU or FCVTZS. + rmode = 0b11 + + signed := i.u1 == 1 + src64bit := i.u2&1 != 0 + dst64bit := i.u2&2 != 0 + + if signed { + opcode = 0b000 + } else { + opcode = 0b001 + } + if dst64bit { + sf = 0b1 + } + if src64bit { + ptype = 0b01 + } else { + ptype = 0b00 + } + } + return sf<<31 | 0b1111<<25 | ptype<<22 | 0b1<<21 | rmode<<19 | opcode<<16 | rn<<5 | rd +} + +// encodeAdr encodes a PC-relative ADR instruction. +// https://developer.arm.com/documentation/ddi0602/2022-06/Base-Instructions/ADR--Form-PC-relative-address- +func encodeAdr(rd uint32, offset uint32) uint32 { + if offset >= 1<<20 { + panic("BUG: too large adr instruction") + } + return offset&0b11<<29 | 0b1<<28 | offset&0b1111111111_1111111100<<3 | rd +} + +// encodeFpuCSel encodes as "Floating-point conditional select" in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en +func encodeFpuCSel(rd, rn, rm uint32, c condFlag, _64bit bool) uint32 { + var ftype uint32 + if _64bit { + ftype = 0b01 // double precision. + } + return 0b1111<<25 | ftype<<22 | 0b1<<21 | rm<<16 | uint32(c)<<12 | 0b11<<10 | rn<<5 | rd +} + +// encodeMoveToVec encodes as "Move general-purpose register to a vector element" (represented as `ins`) in +// https://developer.arm.com/documentation/dui0801/g/A64-SIMD-Vector-Instructions/MOV--vector--from-general- +// https://developer.arm.com/documentation/ddi0596/2020-12/SIMD-FP-Instructions/MOV--from-general---Move-general-purpose-register-to-a-vector-element--an-alias-of-INS--general--?lang=en +func encodeMoveToVec(rd, rn uint32, arr vecArrangement, index vecIndex) uint32 { + var imm5 uint32 + switch arr { + case vecArrangementB: + imm5 |= 0b1 + imm5 |= uint32(index) << 1 + if index > 0b1111 { + panic(fmt.Sprintf("vector index is larger than the allowed bound: %d > 15", index)) + } + case vecArrangementH: + imm5 |= 0b10 + imm5 |= uint32(index) << 2 + if index > 0b111 { + panic(fmt.Sprintf("vector index is larger than the allowed bound: %d > 7", index)) + } + case vecArrangementS: + imm5 |= 0b100 + imm5 |= uint32(index) << 3 + if index > 0b11 { + panic(fmt.Sprintf("vector index is larger than the allowed bound: %d > 3", index)) + } + case vecArrangementD: + imm5 |= 0b1000 + imm5 |= uint32(index) << 4 + if index > 0b1 { + panic(fmt.Sprintf("vector index is larger than the allowed bound: %d > 1", index)) + } + default: + panic("Unsupported arrangement " + arr.String()) + } + + return 0b01001110000<<21 | imm5<<16 | 0b000111<<10 | rn<<5 | rd +} + +// encodeMoveToVec encodes as "Move vector element to another vector element, mov (element)" (represented as `ins`) in +// https://developer.arm.com/documentation/ddi0596/2020-12/SIMD-FP-Instructions/MOV--element---Move-vector-element-to-another-vector-element--an-alias-of-INS--element--?lang=en +// https://developer.arm.com/documentation/ddi0596/2020-12/SIMD-FP-Instructions/INS--element---Insert-vector-element-from-another-vector-element-?lang=en +func encodeVecMovElement(rd, rn uint32, arr vecArrangement, srcIndex, dstIndex uint32) uint32 { + var imm4, imm5 uint32 + switch arr { + case vecArrangementB: + imm5 |= 0b1 + imm5 |= srcIndex << 1 + imm4 = dstIndex + if srcIndex > 0b1111 { + panic(fmt.Sprintf("vector index is larger than the allowed bound: %d > 15", srcIndex)) + } + case vecArrangementH: + imm5 |= 0b10 + imm5 |= srcIndex << 2 + imm4 = dstIndex << 1 + if srcIndex > 0b111 { + panic(fmt.Sprintf("vector index is larger than the allowed bound: %d > 7", srcIndex)) + } + case vecArrangementS: + imm5 |= 0b100 + imm5 |= srcIndex << 3 + imm4 = dstIndex << 2 + if srcIndex > 0b11 { + panic(fmt.Sprintf("vector index is larger than the allowed bound: %d > 3", srcIndex)) + } + case vecArrangementD: + imm5 |= 0b1000 + imm5 |= srcIndex << 4 + imm4 = dstIndex << 3 + if srcIndex > 0b1 { + panic(fmt.Sprintf("vector index is larger than the allowed bound: %d > 1", srcIndex)) + } + default: + panic("Unsupported arrangement " + arr.String()) + } + + return 0b01101110000<<21 | imm5<<16 | imm4<<11 | 0b1<<10 | rn<<5 | rd +} + +// encodeUnconditionalBranchReg encodes as "Unconditional branch (register)" in: +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Branches--Exception-Generating-and-System-instructions?lang=en +func encodeUnconditionalBranchReg(rn uint32, link bool) uint32 { + var opc uint32 + if link { + opc = 0b0001 + } + return 0b1101011<<25 | opc<<21 | 0b11111<<16 | rn<<5 +} + +// encodeMoveFromVec encodes as "Move vector element to a general-purpose register" +// (represented as `umov` when dest is 32-bit, `umov` otherwise) in +// https://developer.arm.com/documentation/ddi0596/2020-12/SIMD-FP-Instructions/UMOV--Unsigned-Move-vector-element-to-general-purpose-register-?lang=en +// https://developer.arm.com/documentation/ddi0596/2020-12/SIMD-FP-Instructions/MOV--to-general---Move-vector-element-to-general-purpose-register--an-alias-of-UMOV-?lang=en +func encodeMoveFromVec(rd, rn uint32, arr vecArrangement, index vecIndex, signed bool) uint32 { + var op, imm4, q, imm5 uint32 + switch { + case arr == vecArrangementB: + imm5 |= 0b1 + imm5 |= uint32(index) << 1 + if index > 0b1111 { + panic(fmt.Sprintf("vector index is larger than the allowed bound: %d > 15", index)) + } + case arr == vecArrangementH: + imm5 |= 0b10 + imm5 |= uint32(index) << 2 + if index > 0b111 { + panic(fmt.Sprintf("vector index is larger than the allowed bound: %d > 7", index)) + } + case arr == vecArrangementS && signed: + q = 0b1 + fallthrough + case arr == vecArrangementS: + imm5 |= 0b100 + imm5 |= uint32(index) << 3 + if index > 0b11 { + panic(fmt.Sprintf("vector index is larger than the allowed bound: %d > 3", index)) + } + case arr == vecArrangementD && !signed: + imm5 |= 0b1000 + imm5 |= uint32(index) << 4 + q = 0b1 + if index > 0b1 { + panic(fmt.Sprintf("vector index is larger than the allowed bound: %d > 1", index)) + } + default: + panic("Unsupported arrangement " + arr.String()) + } + if signed { + op, imm4 = 0, 0b0101 + } else { + op, imm4 = 0, 0b0111 + } + return op<<29 | 0b01110000<<21 | q<<30 | imm5<<16 | imm4<<11 | 1<<10 | rn<<5 | rd +} + +// encodeVecDup encodes as "Duplicate general-purpose register to vector" DUP (general) +// (represented as `dup`) +// https://developer.arm.com/documentation/ddi0596/2020-12/SIMD-FP-Instructions/DUP--general---Duplicate-general-purpose-register-to-vector-?lang=en +func encodeVecDup(rd, rn uint32, arr vecArrangement) uint32 { + var q, imm5 uint32 + switch arr { + case vecArrangement8B: + q, imm5 = 0b0, 0b1 + case vecArrangement16B: + q, imm5 = 0b1, 0b1 + case vecArrangement4H: + q, imm5 = 0b0, 0b10 + case vecArrangement8H: + q, imm5 = 0b1, 0b10 + case vecArrangement2S: + q, imm5 = 0b0, 0b100 + case vecArrangement4S: + q, imm5 = 0b1, 0b100 + case vecArrangement2D: + q, imm5 = 0b1, 0b1000 + default: + panic("Unsupported arrangement " + arr.String()) + } + return q<<30 | 0b001110000<<21 | imm5<<16 | 0b000011<<10 | rn<<5 | rd +} + +// encodeVecDup encodes as "Duplicate vector element to vector or scalar" DUP (element). +// (represented as `dup`) +// https://developer.arm.com/documentation/ddi0596/2020-12/SIMD-FP-Instructions/DUP--element---Duplicate-vector-element-to-vector-or-scalar- +func encodeVecDupElement(rd, rn uint32, arr vecArrangement, srcIndex vecIndex) uint32 { + var q, imm5 uint32 + q = 0b1 + switch arr { + case vecArrangementB: + imm5 |= 0b1 + imm5 |= uint32(srcIndex) << 1 + case vecArrangementH: + imm5 |= 0b10 + imm5 |= uint32(srcIndex) << 2 + case vecArrangementS: + imm5 |= 0b100 + imm5 |= uint32(srcIndex) << 3 + case vecArrangementD: + imm5 |= 0b1000 + imm5 |= uint32(srcIndex) << 4 + default: + panic("unsupported arrangement" + arr.String()) + } + + return q<<30 | 0b001110000<<21 | imm5<<16 | 0b1<<10 | rn<<5 | rd +} + +// encodeVecExtract encodes as "Advanced SIMD extract." +// Currently only `ext` is defined. +// https://developer.arm.com/documentation/ddi0602/2023-06/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en#simd-dp +// https://developer.arm.com/documentation/ddi0602/2023-06/SIMD-FP-Instructions/EXT--Extract-vector-from-pair-of-vectors-?lang=en +func encodeVecExtract(rd, rn, rm uint32, arr vecArrangement, index uint32) uint32 { + var q, imm4 uint32 + switch arr { + case vecArrangement8B: + q, imm4 = 0, 0b0111&uint32(index) + case vecArrangement16B: + q, imm4 = 1, 0b1111&uint32(index) + default: + panic("Unsupported arrangement " + arr.String()) + } + return q<<30 | 0b101110000<<21 | rm<<16 | imm4<<11 | rn<<5 | rd +} + +// encodeVecPermute encodes as "Advanced SIMD permute." +// https://developer.arm.com/documentation/ddi0602/2023-06/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en#simd-dp +func encodeVecPermute(op vecOp, rd, rn, rm uint32, arr vecArrangement) uint32 { + var q, size, opcode uint32 + switch op { + case vecOpZip1: + opcode = 0b011 + if arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + size, q = arrToSizeQEncoded(arr) + default: + panic("TODO: " + op.String()) + } + return q<<30 | 0b001110<<24 | size<<22 | rm<<16 | opcode<<12 | 0b10<<10 | rn<<5 | rd +} + +// encodeConditionalSelect encodes as "Conditional select" in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Register?lang=en#condsel +func encodeConditionalSelect(kind instructionKind, rd, rn, rm uint32, c condFlag, _64bit bool) uint32 { + if kind != cSel { + panic("TODO: support other conditional select") + } + + ret := 0b110101<<23 | rm<<16 | uint32(c)<<12 | rn<<5 | rd + if _64bit { + ret |= 0b1 << 31 + } + return ret +} + +const dummyInstruction uint32 = 0x14000000 // "b 0" + +// encodeLoadFpuConst32 encodes the following three instructions: +// +// ldr s8, #8 ;; literal load of data.f32 +// b 8 ;; skip the data +// data.f32 xxxxxxx +func encodeLoadFpuConst32(c backend.Compiler, rd uint32, rawF32 uint64) { + c.Emit4Bytes( + // https://developer.arm.com/documentation/ddi0596/2020-12/SIMD-FP-Instructions/LDR--literal--SIMD-FP---Load-SIMD-FP-Register--PC-relative-literal--?lang=en + 0b111<<26 | (0x8/4)<<5 | rd, + ) + c.Emit4Bytes(encodeUnconditionalBranch(false, 8)) // b 8 + if wazevoapi.PrintMachineCodeHexPerFunctionDisassemblable { + // Inlined data.f32 cannot be disassembled, so we add a dummy instruction here. + c.Emit4Bytes(dummyInstruction) + } else { + c.Emit4Bytes(uint32(rawF32)) // data.f32 xxxxxxx + } +} + +// encodeLoadFpuConst64 encodes the following three instructions: +// +// ldr d8, #8 ;; literal load of data.f64 +// b 12 ;; skip the data +// data.f64 xxxxxxx +func encodeLoadFpuConst64(c backend.Compiler, rd uint32, rawF64 uint64) { + c.Emit4Bytes( + // https://developer.arm.com/documentation/ddi0596/2020-12/SIMD-FP-Instructions/LDR--literal--SIMD-FP---Load-SIMD-FP-Register--PC-relative-literal--?lang=en + 0b1<<30 | 0b111<<26 | (0x8/4)<<5 | rd, + ) + c.Emit4Bytes(encodeUnconditionalBranch(false, 12)) // b 12 + if wazevoapi.PrintMachineCodeHexPerFunctionDisassemblable { + // Inlined data.f64 cannot be disassembled, so we add dummy instructions here. + c.Emit4Bytes(dummyInstruction) + c.Emit4Bytes(dummyInstruction) + } else { + // data.f64 xxxxxxx + c.Emit4Bytes(uint32(rawF64)) + c.Emit4Bytes(uint32(rawF64 >> 32)) + } +} + +// encodeLoadFpuConst128 encodes the following three instructions: +// +// ldr v8, #8 ;; literal load of data.f64 +// b 20 ;; skip the data +// data.v128 xxxxxxx +func encodeLoadFpuConst128(c backend.Compiler, rd uint32, lo, hi uint64) { + c.Emit4Bytes( + // https://developer.arm.com/documentation/ddi0596/2020-12/SIMD-FP-Instructions/LDR--literal--SIMD-FP---Load-SIMD-FP-Register--PC-relative-literal--?lang=en + 0b1<<31 | 0b111<<26 | (0x8/4)<<5 | rd, + ) + c.Emit4Bytes(encodeUnconditionalBranch(false, 20)) // b 20 + if wazevoapi.PrintMachineCodeHexPerFunctionDisassemblable { + // Inlined data.v128 cannot be disassembled, so we add dummy instructions here. + c.Emit4Bytes(dummyInstruction) + c.Emit4Bytes(dummyInstruction) + c.Emit4Bytes(dummyInstruction) + c.Emit4Bytes(dummyInstruction) + } else { + // data.v128 xxxxxxx + c.Emit4Bytes(uint32(lo)) + c.Emit4Bytes(uint32(lo >> 32)) + c.Emit4Bytes(uint32(hi)) + c.Emit4Bytes(uint32(hi >> 32)) + } +} + +// encodeAluRRRR encodes as Data-processing (3 source) in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Register?lang=en +func encodeAluRRRR(op aluOp, rd, rn, rm, ra, _64bit uint32) uint32 { + var oO, op31 uint32 + switch op { + case aluOpMAdd: + op31, oO = 0b000, 0b0 + case aluOpMSub: + op31, oO = 0b000, 0b1 + default: + panic("TODO/BUG") + } + return _64bit<<31 | 0b11011<<24 | op31<<21 | rm<<16 | oO<<15 | ra<<10 | rn<<5 | rd +} + +// encodeBitRR encodes as Data-processing (1 source) in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Register?lang=en +func encodeBitRR(op bitOp, rd, rn, _64bit uint32) uint32 { + var opcode2, opcode uint32 + switch op { + case bitOpRbit: + opcode2, opcode = 0b00000, 0b000000 + case bitOpClz: + opcode2, opcode = 0b00000, 0b000100 + default: + panic("TODO/BUG") + } + return _64bit<<31 | 0b1_0_11010110<<21 | opcode2<<15 | opcode<<10 | rn<<5 | rd +} + +func encodeAsMov32(rn, rd uint32) uint32 { + // This is an alias of ORR (shifted register): + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/MOV--register---Move--register---an-alias-of-ORR--shifted-register-- + return encodeLogicalShiftedRegister(0b001, 0, rn, 0, regNumberInEncoding[xzr], rd) +} + +// encodeExtend encodes extension instructions. +func encodeExtend(signed bool, from, to byte, rd, rn uint32) uint32 { + // UTXB: https://developer.arm.com/documentation/ddi0596/2020-12/Base-Instructions/UXTB--Unsigned-Extend-Byte--an-alias-of-UBFM-?lang=en + // UTXH: https://developer.arm.com/documentation/ddi0596/2020-12/Base-Instructions/UXTH--Unsigned-Extend-Halfword--an-alias-of-UBFM-?lang=en + // STXB: https://developer.arm.com/documentation/ddi0596/2020-12/Base-Instructions/SXTB--Signed-Extend-Byte--an-alias-of-SBFM- + // STXH: https://developer.arm.com/documentation/ddi0596/2020-12/Base-Instructions/SXTH--Sign-Extend-Halfword--an-alias-of-SBFM- + // STXW: https://developer.arm.com/documentation/ddi0596/2020-12/Base-Instructions/SXTW--Sign-Extend-Word--an-alias-of-SBFM- + var _31to10 uint32 + switch { + case !signed && from == 8 && to == 32: + // 32-bit UXTB + _31to10 = 0b0101001100000000000111 + case !signed && from == 16 && to == 32: + // 32-bit UXTH + _31to10 = 0b0101001100000000001111 + case !signed && from == 8 && to == 64: + // 64-bit UXTB + _31to10 = 0b0101001100000000000111 + case !signed && from == 16 && to == 64: + // 64-bit UXTH + _31to10 = 0b0101001100000000001111 + case !signed && from == 32 && to == 64: + return encodeAsMov32(rn, rd) + case signed && from == 8 && to == 32: + // 32-bit SXTB + _31to10 = 0b0001001100000000000111 + case signed && from == 16 && to == 32: + // 32-bit SXTH + _31to10 = 0b0001001100000000001111 + case signed && from == 8 && to == 64: + // 64-bit SXTB + _31to10 = 0b1001001101000000000111 + case signed && from == 16 && to == 64: + // 64-bit SXTH + _31to10 = 0b1001001101000000001111 + case signed && from == 32 && to == 64: + // SXTW + _31to10 = 0b1001001101000000011111 + default: + panic("BUG") + } + return _31to10<<10 | rn<<5 | rd +} + +func encodeLoadOrStore(kind instructionKind, rt uint32, amode addressMode) uint32 { + var _22to31 uint32 + var bits int64 + switch kind { + case uLoad8: + _22to31 = 0b0011100001 + bits = 8 + case sLoad8: + _22to31 = 0b0011100010 + bits = 8 + case uLoad16: + _22to31 = 0b0111100001 + bits = 16 + case sLoad16: + _22to31 = 0b0111100010 + bits = 16 + case uLoad32: + _22to31 = 0b1011100001 + bits = 32 + case sLoad32: + _22to31 = 0b1011100010 + bits = 32 + case uLoad64: + _22to31 = 0b1111100001 + bits = 64 + case fpuLoad32: + _22to31 = 0b1011110001 + bits = 32 + case fpuLoad64: + _22to31 = 0b1111110001 + bits = 64 + case fpuLoad128: + _22to31 = 0b0011110011 + bits = 128 + case store8: + _22to31 = 0b0011100000 + bits = 8 + case store16: + _22to31 = 0b0111100000 + bits = 16 + case store32: + _22to31 = 0b1011100000 + bits = 32 + case store64: + _22to31 = 0b1111100000 + bits = 64 + case fpuStore32: + _22to31 = 0b1011110000 + bits = 32 + case fpuStore64: + _22to31 = 0b1111110000 + bits = 64 + case fpuStore128: + _22to31 = 0b0011110010 + bits = 128 + default: + panic("BUG") + } + + switch amode.kind { + case addressModeKindRegScaledExtended: + return encodeLoadOrStoreExtended(_22to31, + regNumberInEncoding[amode.rn.RealReg()], + regNumberInEncoding[amode.rm.RealReg()], + rt, true, amode.extOp) + case addressModeKindRegScaled: + return encodeLoadOrStoreExtended(_22to31, + regNumberInEncoding[amode.rn.RealReg()], regNumberInEncoding[amode.rm.RealReg()], + rt, true, extendOpNone) + case addressModeKindRegExtended: + return encodeLoadOrStoreExtended(_22to31, + regNumberInEncoding[amode.rn.RealReg()], regNumberInEncoding[amode.rm.RealReg()], + rt, false, amode.extOp) + case addressModeKindRegReg: + return encodeLoadOrStoreExtended(_22to31, + regNumberInEncoding[amode.rn.RealReg()], regNumberInEncoding[amode.rm.RealReg()], + rt, false, extendOpNone) + case addressModeKindRegSignedImm9: + // e.g. https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDUR--Load-Register--unscaled-- + return encodeLoadOrStoreSIMM9(_22to31, 0b00 /* unscaled */, regNumberInEncoding[amode.rn.RealReg()], rt, amode.imm) + case addressModeKindPostIndex: + return encodeLoadOrStoreSIMM9(_22to31, 0b01 /* post index */, regNumberInEncoding[amode.rn.RealReg()], rt, amode.imm) + case addressModeKindPreIndex: + return encodeLoadOrStoreSIMM9(_22to31, 0b11 /* pre index */, regNumberInEncoding[amode.rn.RealReg()], rt, amode.imm) + case addressModeKindRegUnsignedImm12: + // "unsigned immediate" in https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Loads-and-Stores?lang=en + rn := regNumberInEncoding[amode.rn.RealReg()] + imm := amode.imm + div := bits / 8 + if imm != 0 && !offsetFitsInAddressModeKindRegUnsignedImm12(byte(bits), imm) { + panic("BUG") + } + imm /= div + return _22to31<<22 | 0b1<<24 | uint32(imm&0b111111111111)<<10 | rn<<5 | rt + default: + panic("BUG") + } +} + +// encodeVecLoad1R encodes as Load one single-element structure and Replicate to all lanes (of one register) in +// https://developer.arm.com/documentation/ddi0596/2021-12/SIMD-FP-Instructions/LD1R--Load-one-single-element-structure-and-Replicate-to-all-lanes--of-one-register--?lang=en#sa_imm +func encodeVecLoad1R(rt, rn uint32, arr vecArrangement) uint32 { + size, q := arrToSizeQEncoded(arr) + return q<<30 | 0b001101010000001100<<12 | size<<10 | rn<<5 | rt +} + +// encodeAluBitmaskImmediate encodes as Logical (immediate) in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Immediate?lang=en +func encodeAluBitmaskImmediate(op aluOp, rd, rn uint32, imm uint64, _64bit bool) uint32 { + var _31to23 uint32 + switch op { + case aluOpAnd: + _31to23 = 0b00_100100 + case aluOpOrr: + _31to23 = 0b01_100100 + case aluOpEor: + _31to23 = 0b10_100100 + case aluOpAnds: + _31to23 = 0b11_100100 + default: + panic("BUG") + } + if _64bit { + _31to23 |= 0b1 << 8 + } + immr, imms, N := bitmaskImmediate(imm, _64bit) + return _31to23<<23 | uint32(N)<<22 | uint32(immr)<<16 | uint32(imms)<<10 | rn<<5 | rd +} + +func bitmaskImmediate(c uint64, is64bit bool) (immr, imms, N byte) { + var size uint32 + switch { + case c != c>>32|c<<32: + size = 64 + case c != c>>16|c<<48: + size = 32 + c = uint64(int32(c)) + case c != c>>8|c<<56: + size = 16 + c = uint64(int16(c)) + case c != c>>4|c<<60: + size = 8 + c = uint64(int8(c)) + case c != c>>2|c<<62: + size = 4 + c = uint64(int64(c<<60) >> 60) + default: + size = 2 + c = uint64(int64(c<<62) >> 62) + } + + neg := false + if int64(c) < 0 { + c = ^c + neg = true + } + + onesSize, nonZeroPos := getOnesSequenceSize(c) + if neg { + nonZeroPos = onesSize + nonZeroPos + onesSize = size - onesSize + } + + var mode byte = 32 + if is64bit && size == 64 { + N, mode = 0b1, 64 + } + + immr = byte((size - nonZeroPos) & (size - 1) & uint32(mode-1)) + imms = byte((onesSize - 1) | 63&^(size<<1-1)) + return +} + +func getOnesSequenceSize(x uint64) (size, nonZeroPos uint32) { + // Take 0b00111000 for example: + y := getLowestBit(x) // = 0b0000100 + nonZeroPos = setBitPos(y) // = 2 + size = setBitPos(x+y) - nonZeroPos // = setBitPos(0b0100000) - 2 = 5 - 2 = 3 + return +} + +func setBitPos(x uint64) (ret uint32) { + for ; ; ret++ { + if x == 0b1 { + break + } + x = x >> 1 + } + return +} + +// encodeLoadOrStoreExtended encodes store/load instruction as "extended register offset" in Load/store register (register offset): +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Loads-and-Stores?lang=en +func encodeLoadOrStoreExtended(_22to32 uint32, rn, rm, rt uint32, scaled bool, extOp extendOp) uint32 { + var option uint32 + switch extOp { + case extendOpUXTW: + option = 0b010 + case extendOpSXTW: + option = 0b110 + case extendOpNone: + option = 0b111 + default: + panic("BUG") + } + var s uint32 + if scaled { + s = 0b1 + } + return _22to32<<22 | 0b1<<21 | rm<<16 | option<<13 | s<<12 | 0b10<<10 | rn<<5 | rt +} + +// encodeLoadOrStoreSIMM9 encodes store/load instruction as one of post-index, pre-index or unscaled immediate as in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Loads-and-Stores?lang=en +func encodeLoadOrStoreSIMM9(_22to32, _1011 uint32, rn, rt uint32, imm9 int64) uint32 { + return _22to32<<22 | (uint32(imm9)&0b111111111)<<12 | _1011<<10 | rn<<5 | rt +} + +// encodeFpuRRR encodes as single or double precision (depending on `_64bit`) of Floating-point data-processing (2 source) in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en +func encodeFpuRRR(op fpuBinOp, rd, rn, rm uint32, _64bit bool) (ret uint32) { + // https://developer.arm.com/documentation/ddi0596/2021-12/SIMD-FP-Instructions/ADD--vector--Add-vectors--scalar--floating-point-and-integer- + var opcode uint32 + switch op { + case fpuBinOpAdd: + opcode = 0b0010 + case fpuBinOpSub: + opcode = 0b0011 + case fpuBinOpMul: + opcode = 0b0000 + case fpuBinOpDiv: + opcode = 0b0001 + case fpuBinOpMax: + opcode = 0b0100 + case fpuBinOpMin: + opcode = 0b0101 + default: + panic("BUG") + } + var ptype uint32 + if _64bit { + ptype = 0b01 + } + return 0b1111<<25 | ptype<<22 | 0b1<<21 | rm<<16 | opcode<<12 | 0b1<<11 | rn<<5 | rd +} + +// encodeAluRRImm12 encodes as Add/subtract (immediate) in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Immediate?lang=en +func encodeAluRRImm12(op aluOp, rd, rn uint32, imm12 uint16, shiftBit byte, _64bit bool) uint32 { + var _31to24 uint32 + switch op { + case aluOpAdd: + _31to24 = 0b00_10001 + case aluOpAddS: + _31to24 = 0b01_10001 + case aluOpSub: + _31to24 = 0b10_10001 + case aluOpSubS: + _31to24 = 0b11_10001 + default: + panic("BUG") + } + if _64bit { + _31to24 |= 0b1 << 7 + } + return _31to24<<24 | uint32(shiftBit)<<22 | uint32(imm12&0b111111111111)<<10 | rn<<5 | rd +} + +// encodeAluRRR encodes as Data Processing (shifted register), depending on aluOp. +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Register?lang=en#addsub_shift +func encodeAluRRRShift(op aluOp, rd, rn, rm, amount uint32, shiftOp shiftOp, _64bit bool) uint32 { + var _31to24 uint32 + var opc, n uint32 + switch op { + case aluOpAdd: + _31to24 = 0b00001011 + case aluOpAddS: + _31to24 = 0b00101011 + case aluOpSub: + _31to24 = 0b01001011 + case aluOpSubS: + _31to24 = 0b01101011 + case aluOpAnd, aluOpOrr, aluOpEor, aluOpAnds: + // "Logical (shifted register)". + switch op { + case aluOpAnd: + // all zeros + case aluOpOrr: + opc = 0b01 + case aluOpEor: + opc = 0b10 + case aluOpAnds: + opc = 0b11 + } + _31to24 = 0b000_01010 + default: + panic(op.String()) + } + + if _64bit { + _31to24 |= 0b1 << 7 + } + + var shift uint32 + switch shiftOp { + case shiftOpLSL: + shift = 0b00 + case shiftOpLSR: + shift = 0b01 + case shiftOpASR: + shift = 0b10 + default: + panic(shiftOp.String()) + } + return opc<<29 | n<<21 | _31to24<<24 | shift<<22 | rm<<16 | (amount << 10) | (rn << 5) | rd +} + +// "Add/subtract (extended register)" in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Register?lang=en#addsub_ext +func encodeAluRRRExtend(ao aluOp, rd, rn, rm uint32, extOp extendOp, to byte) uint32 { + var s, op uint32 + switch ao { + case aluOpAdd: + op = 0b0 + case aluOpAddS: + op, s = 0b0, 0b1 + case aluOpSub: + op = 0b1 + case aluOpSubS: + op, s = 0b1, 0b1 + default: + panic("BUG: extended register operand can be used only for add/sub") + } + + var sf uint32 + if to == 64 { + sf = 0b1 + } + + var option uint32 + switch extOp { + case extendOpUXTB: + option = 0b000 + case extendOpUXTH: + option = 0b001 + case extendOpUXTW: + option = 0b010 + case extendOpSXTB: + option = 0b100 + case extendOpSXTH: + option = 0b101 + case extendOpSXTW: + option = 0b110 + case extendOpSXTX, extendOpUXTX: + panic(fmt.Sprintf("%s is essentially noop, and should be handled much earlier than encoding", extOp.String())) + } + return sf<<31 | op<<30 | s<<29 | 0b1011001<<21 | rm<<16 | option<<13 | rn<<5 | rd +} + +// encodeAluRRR encodes as Data Processing (register), depending on aluOp. +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Register?lang=en +func encodeAluRRR(op aluOp, rd, rn, rm uint32, _64bit, isRnSp bool) uint32 { + var _31to21, _15to10 uint32 + switch op { + case aluOpAdd: + if isRnSp { + // "Extended register" with UXTW. + _31to21 = 0b00001011_001 + _15to10 = 0b011000 + } else { + // "Shifted register" with shift = 0 + _31to21 = 0b00001011_000 + } + case aluOpAddS: + if isRnSp { + panic("TODO") + } + // "Shifted register" with shift = 0 + _31to21 = 0b00101011_000 + case aluOpSub: + if isRnSp { + // "Extended register" with UXTW. + _31to21 = 0b01001011_001 + _15to10 = 0b011000 + } else { + // "Shifted register" with shift = 0 + _31to21 = 0b01001011_000 + } + case aluOpSubS: + if isRnSp { + panic("TODO") + } + // "Shifted register" with shift = 0 + _31to21 = 0b01101011_000 + case aluOpAnd, aluOpOrr, aluOpOrn, aluOpEor, aluOpAnds: + // "Logical (shifted register)". + var opc, n uint32 + switch op { + case aluOpAnd: + // all zeros + case aluOpOrr: + opc = 0b01 + case aluOpOrn: + opc = 0b01 + n = 1 + case aluOpEor: + opc = 0b10 + case aluOpAnds: + opc = 0b11 + } + _31to21 = 0b000_01010_000 | opc<<8 | n + case aluOpLsl, aluOpAsr, aluOpLsr, aluOpRotR: + // "Data-processing (2 source)". + _31to21 = 0b00011010_110 + switch op { + case aluOpLsl: + _15to10 = 0b001000 + case aluOpLsr: + _15to10 = 0b001001 + case aluOpAsr: + _15to10 = 0b001010 + case aluOpRotR: + _15to10 = 0b001011 + } + case aluOpSDiv: + // "Data-processing (2 source)". + _31to21 = 0b11010110 + _15to10 = 0b000011 + case aluOpUDiv: + // "Data-processing (2 source)". + _31to21 = 0b11010110 + _15to10 = 0b000010 + default: + panic(op.String()) + } + if _64bit { + _31to21 |= 0b1 << 10 + } + return _31to21<<21 | rm<<16 | (_15to10 << 10) | (rn << 5) | rd +} + +// encodeLogicalShiftedRegister encodes as Logical (shifted register) in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Register?lang=en +func encodeLogicalShiftedRegister(sf_opc uint32, shift_N uint32, rm uint32, imm6 uint32, rn, rd uint32) (ret uint32) { + ret = sf_opc << 29 + ret |= 0b01010 << 24 + ret |= shift_N << 21 + ret |= rm << 16 + ret |= imm6 << 10 + ret |= rn << 5 + ret |= rd + return +} + +// encodeAddSubtractImmediate encodes as Add/subtract (immediate) in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Immediate?lang=en +func encodeAddSubtractImmediate(sf_op_s uint32, sh uint32, imm12 uint32, rn, rd uint32) (ret uint32) { + ret = sf_op_s << 29 + ret |= 0b100010 << 23 + ret |= sh << 22 + ret |= imm12 << 10 + ret |= rn << 5 + ret |= rd + return +} + +// encodePreOrPostIndexLoadStorePair64 encodes as Load/store pair (pre/post-indexed) in +// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDP--Load-Pair-of-Registers- +// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/STP--Store-Pair-of-Registers- +func encodePreOrPostIndexLoadStorePair64(pre bool, load bool, rn, rt, rt2 uint32, imm7 int64) (ret uint32) { + if imm7%8 != 0 { + panic("imm7 for pair load/store must be a multiple of 8") + } + imm7 /= 8 + ret = rt + ret |= rn << 5 + ret |= rt2 << 10 + ret |= (uint32(imm7) & 0b1111111) << 15 + if load { + ret |= 0b1 << 22 + } + ret |= 0b101010001 << 23 + if pre { + ret |= 0b1 << 24 + } + return +} + +// encodeUnconditionalBranch encodes as B or BL instructions: +// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/B--Branch- +// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/BL--Branch-with-Link- +func encodeUnconditionalBranch(link bool, imm26 int64) (ret uint32) { + if imm26%4 != 0 { + panic("imm26 for branch must be a multiple of 4") + } + imm26 /= 4 + ret = uint32(imm26 & 0b11_11111111_11111111_11111111) + ret |= 0b101 << 26 + if link { + ret |= 0b1 << 31 + } + return +} + +// encodeCBZCBNZ encodes as either CBZ or CBNZ: +// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/CBZ--Compare-and-Branch-on-Zero- +// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/CBNZ--Compare-and-Branch-on-Nonzero- +func encodeCBZCBNZ(rt uint32, nz bool, imm19 uint32, _64bit bool) (ret uint32) { + ret = rt + ret |= imm19 << 5 + if nz { + ret |= 1 << 24 + } + ret |= 0b11010 << 25 + if _64bit { + ret |= 1 << 31 + } + return +} + +// encodeMoveWideImmediate encodes as either MOVZ, MOVN or MOVK, as Move wide (immediate) in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Immediate?lang=en +// +// "shift" must have been divided by 16 at this point. +func encodeMoveWideImmediate(opc uint32, rd uint32, imm uint64, shift, _64bit uint32) (ret uint32) { + ret = rd + ret |= uint32(imm&0xffff) << 5 + ret |= (shift) << 21 + ret |= 0b100101 << 23 + ret |= opc << 29 + ret |= _64bit << 31 + return +} + +// encodeAluRRImm encodes as "Bitfield" in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Immediate?lang=en#log_imm +func encodeAluRRImm(op aluOp, rd, rn, amount, _64bit uint32) uint32 { + var opc uint32 + var immr, imms uint32 + switch op { + case aluOpLsl: + // LSL (immediate) is an alias for UBFM. + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/UBFM--Unsigned-Bitfield-Move-?lang=en + opc = 0b10 + if amount == 0 { + // This can be encoded as NOP, but we don't do it for consistency: lsr xn, xm, #0 + immr = 0 + if _64bit == 1 { + imms = 0b111111 + } else { + imms = 0b11111 + } + } else { + if _64bit == 1 { + immr = 64 - amount + } else { + immr = (32 - amount) & 0b11111 + } + imms = immr - 1 + } + case aluOpLsr: + // LSR (immediate) is an alias for UBFM. + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LSR--immediate---Logical-Shift-Right--immediate---an-alias-of-UBFM-?lang=en + opc = 0b10 + imms, immr = 0b011111|_64bit<<5, amount + case aluOpAsr: + // ASR (immediate) is an alias for SBFM. + // https://developer.arm.com/documentation/ddi0596/2020-12/Base-Instructions/SBFM--Signed-Bitfield-Move-?lang=en + opc = 0b00 + imms, immr = 0b011111|_64bit<<5, amount + default: + panic(op.String()) + } + return _64bit<<31 | opc<<29 | 0b100110<<23 | _64bit<<22 | immr<<16 | imms<<10 | rn<<5 | rd +} + +// encodeVecLanes encodes as Data Processing (Advanced SIMD across lanes) depending on vecOp in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en +func encodeVecLanes(op vecOp, rd uint32, rn uint32, arr vecArrangement) uint32 { + var u, q, size, opcode uint32 + switch arr { + case vecArrangement8B: + q, size = 0b0, 0b00 + case vecArrangement16B: + q, size = 0b1, 0b00 + case vecArrangement4H: + q, size = 0, 0b01 + case vecArrangement8H: + q, size = 1, 0b01 + case vecArrangement4S: + q, size = 1, 0b10 + default: + panic("unsupported arrangement: " + arr.String()) + } + switch op { + case vecOpUaddlv: + u, opcode = 1, 0b00011 + case vecOpUminv: + u, opcode = 1, 0b11010 + case vecOpAddv: + u, opcode = 0, 0b11011 + default: + panic("unsupported or illegal vecOp: " + op.String()) + } + return q<<30 | u<<29 | 0b1110<<24 | size<<22 | 0b11000<<17 | opcode<<12 | 0b10<<10 | rn<<5 | rd +} + +// encodeVecLanes encodes as Data Processing (Advanced SIMD scalar shift by immediate) depending on vecOp in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en +func encodeVecShiftImm(op vecOp, rd uint32, rn, amount uint32, arr vecArrangement) uint32 { + var u, q, immh, immb, opcode uint32 + switch op { + case vecOpSshll: + u, opcode = 0b0, 0b10100 + case vecOpUshll: + u, opcode = 0b1, 0b10100 + case vecOpSshr: + u, opcode = 0, 0b00000 + default: + panic("unsupported or illegal vecOp: " + op.String()) + } + switch arr { + case vecArrangement16B: + q = 0b1 + fallthrough + case vecArrangement8B: + immh = 0b0001 + immb = 8 - uint32(amount&0b111) + case vecArrangement8H: + q = 0b1 + fallthrough + case vecArrangement4H: + v := 16 - uint32(amount&0b1111) + immb = v & 0b111 + immh = 0b0010 | (v >> 3) + case vecArrangement4S: + q = 0b1 + fallthrough + case vecArrangement2S: + v := 32 - uint32(amount&0b11111) + immb = v & 0b111 + immh = 0b0100 | (v >> 3) + case vecArrangement2D: + q = 0b1 + v := 64 - uint32(amount&0b111111) + immb = v & 0b111 + immh = 0b1000 | (v >> 3) + default: + panic("unsupported arrangement: " + arr.String()) + } + return q<<30 | u<<29 | 0b011110<<23 | immh<<19 | immb<<16 | 0b000001<<10 | opcode<<11 | 0b1<<10 | rn<<5 | rd +} + +// encodeVecTbl encodes as Data Processing (Advanced SIMD table lookup) in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en#simd-dp +// +// Note: tblOp may encode tbl1, tbl2... in the future. Currently, it is ignored. +func encodeVecTbl(nregs, rd, rn, rm uint32, arr vecArrangement) uint32 { + var q, op2, len, op uint32 + + switch nregs { + case 1: + // tbl: single-register + len = 0b00 + case 2: + // tbl2: 2-register table + len = 0b01 + default: + panic(fmt.Sprintf("unsupported number or registers %d", nregs)) + } + switch arr { + case vecArrangement8B: + q = 0b0 + case vecArrangement16B: + q = 0b1 + default: + panic("unsupported arrangement: " + arr.String()) + } + + return q<<30 | 0b001110<<24 | op2<<22 | rm<<16 | len<<13 | op<<12 | rn<<5 | rd +} + +// encodeVecMisc encodes as Data Processing (Advanced SIMD two-register miscellaneous) depending on vecOp in +// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en#simd-dp +func encodeAdvancedSIMDTwoMisc(op vecOp, rd, rn uint32, arr vecArrangement) uint32 { + var q, u, size, opcode uint32 + switch op { + case vecOpCnt: + opcode = 0b00101 + switch arr { + case vecArrangement8B: + q, size = 0b0, 0b00 + case vecArrangement16B: + q, size = 0b1, 0b00 + default: + panic("unsupported arrangement: " + arr.String()) + } + case vecOpCmeq0: + if arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + opcode = 0b01001 + size, q = arrToSizeQEncoded(arr) + case vecOpNot: + u = 1 + opcode = 0b00101 + switch arr { + case vecArrangement8B: + q, size = 0b0, 0b00 + case vecArrangement16B: + q, size = 0b1, 0b00 + default: + panic("unsupported arrangement: " + arr.String()) + } + case vecOpAbs: + if arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + opcode = 0b01011 + u = 0b0 + size, q = arrToSizeQEncoded(arr) + case vecOpNeg: + if arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + opcode = 0b01011 + u = 0b1 + size, q = arrToSizeQEncoded(arr) + case vecOpFabs: + if arr < vecArrangement2S || arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + opcode = 0b01111 + u = 0b0 + size, q = arrToSizeQEncoded(arr) + case vecOpFneg: + if arr < vecArrangement2S || arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + opcode = 0b01111 + u = 0b1 + size, q = arrToSizeQEncoded(arr) + case vecOpFrintm: + u = 0b0 + opcode = 0b11001 + switch arr { + case vecArrangement2S: + q, size = 0b0, 0b00 + case vecArrangement4S: + q, size = 0b1, 0b00 + case vecArrangement2D: + q, size = 0b1, 0b01 + default: + panic("unsupported arrangement: " + arr.String()) + } + case vecOpFrintn: + u = 0b0 + opcode = 0b11000 + switch arr { + case vecArrangement2S: + q, size = 0b0, 0b00 + case vecArrangement4S: + q, size = 0b1, 0b00 + case vecArrangement2D: + q, size = 0b1, 0b01 + default: + panic("unsupported arrangement: " + arr.String()) + } + case vecOpFrintp: + u = 0b0 + opcode = 0b11000 + if arr < vecArrangement2S || arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + size, q = arrToSizeQEncoded(arr) + case vecOpFrintz: + u = 0b0 + opcode = 0b11001 + if arr < vecArrangement2S || arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + size, q = arrToSizeQEncoded(arr) + case vecOpFsqrt: + if arr < vecArrangement2S || arr == vecArrangement1D { + panic("unsupported arrangement: " + arr.String()) + } + opcode = 0b11111 + u = 0b1 + size, q = arrToSizeQEncoded(arr) + case vecOpFcvtl: + opcode = 0b10111 + u = 0b0 + switch arr { + case vecArrangement2S: + size, q = 0b01, 0b0 + case vecArrangement4H: + size, q = 0b00, 0b0 + default: + panic("unsupported arrangement: " + arr.String()) + } + case vecOpFcvtn: + opcode = 0b10110 + u = 0b0 + switch arr { + case vecArrangement2S: + size, q = 0b01, 0b0 + case vecArrangement4H: + size, q = 0b00, 0b0 + default: + panic("unsupported arrangement: " + arr.String()) + } + case vecOpFcvtzs: + opcode = 0b11011 + u = 0b0 + switch arr { + case vecArrangement2S: + q, size = 0b0, 0b10 + case vecArrangement4S: + q, size = 0b1, 0b10 + case vecArrangement2D: + q, size = 0b1, 0b11 + default: + panic("unsupported arrangement: " + arr.String()) + } + case vecOpFcvtzu: + opcode = 0b11011 + u = 0b1 + switch arr { + case vecArrangement2S: + q, size = 0b0, 0b10 + case vecArrangement4S: + q, size = 0b1, 0b10 + case vecArrangement2D: + q, size = 0b1, 0b11 + default: + panic("unsupported arrangement: " + arr.String()) + } + case vecOpScvtf: + opcode = 0b11101 + u = 0b0 + switch arr { + case vecArrangement4S: + q, size = 0b1, 0b00 + case vecArrangement2S: + q, size = 0b0, 0b00 + case vecArrangement2D: + q, size = 0b1, 0b01 + default: + panic("unsupported arrangement: " + arr.String()) + } + case vecOpUcvtf: + opcode = 0b11101 + u = 0b1 + switch arr { + case vecArrangement4S: + q, size = 0b1, 0b00 + case vecArrangement2S: + q, size = 0b0, 0b00 + case vecArrangement2D: + q, size = 0b1, 0b01 + default: + panic("unsupported arrangement: " + arr.String()) + } + case vecOpSqxtn: + // When q == 1 it encodes sqxtn2 (operates on upper 64 bits). + opcode = 0b10100 + u = 0b0 + if arr > vecArrangement4S { + panic("unsupported arrangement: " + arr.String()) + } + size, q = arrToSizeQEncoded(arr) + case vecOpUqxtn: + // When q == 1 it encodes uqxtn2 (operates on upper 64 bits). + opcode = 0b10100 + u = 0b1 + if arr > vecArrangement4S { + panic("unsupported arrangement: " + arr.String()) + } + size, q = arrToSizeQEncoded(arr) + case vecOpSqxtun: + // When q == 1 it encodes sqxtun2 (operates on upper 64 bits). + opcode = 0b10010 // 0b10100 + u = 0b1 + if arr > vecArrangement4S { + panic("unsupported arrangement: " + arr.String()) + } + size, q = arrToSizeQEncoded(arr) + case vecOpRev64: + opcode = 0b00000 + size, q = arrToSizeQEncoded(arr) + case vecOpXtn: + u = 0b0 + opcode = 0b10010 + size, q = arrToSizeQEncoded(arr) + case vecOpShll: + u = 0b1 + opcode = 0b10011 + switch arr { + case vecArrangement8B: + q, size = 0b0, 0b00 + case vecArrangement4H: + q, size = 0b0, 0b01 + case vecArrangement2S: + q, size = 0b0, 0b10 + default: + panic("unsupported arrangement: " + arr.String()) + } + default: + panic("unsupported or illegal vecOp: " + op.String()) + } + return q<<30 | u<<29 | 0b01110<<24 | size<<22 | 0b10000<<17 | opcode<<12 | 0b10<<10 | rn<<5 | rd +} + +// brTableSequenceOffsetTableBegin is the offset inside the brTableSequence where the table begins after 4 instructions +const brTableSequenceOffsetTableBegin = 16 + +func encodeBrTableSequence(c backend.Compiler, index regalloc.VReg, targets []uint32) { + tmpRegNumber := regNumberInEncoding[tmp] + indexNumber := regNumberInEncoding[index.RealReg()] + + // adr tmpReg, PC+16 (PC+16 is the address of the first label offset) + // ldrsw index, [tmpReg, index, UXTW 2] ;; index = int64(*(tmpReg + index*8)) + // add tmpReg, tmpReg, index + // br tmpReg + // [offset_to_l1, offset_to_l2, ..., offset_to_lN] + c.Emit4Bytes(encodeAdr(tmpRegNumber, 16)) + c.Emit4Bytes(encodeLoadOrStore(sLoad32, indexNumber, + addressMode{kind: addressModeKindRegScaledExtended, rn: tmpRegVReg, rm: index, extOp: extendOpUXTW}, + )) + c.Emit4Bytes(encodeAluRRR(aluOpAdd, tmpRegNumber, tmpRegNumber, indexNumber, true, false)) + c.Emit4Bytes(encodeUnconditionalBranchReg(tmpRegNumber, false)) + + // Offsets are resolved in ResolveRelativeAddress phase. + for _, offset := range targets { + if wazevoapi.PrintMachineCodeHexPerFunctionDisassemblable { + // Inlined offset tables cannot be disassembled properly, so pad dummy instructions to make the debugging easier. + c.Emit4Bytes(dummyInstruction) + } else { + c.Emit4Bytes(offset) + } + } +} + +// encodeExitSequence matches the implementation detail of functionABI.emitGoEntryPreamble. +func encodeExitSequence(c backend.Compiler, ctxReg regalloc.VReg) { + // Restore the FP, SP and LR, and return to the Go code: + // ldr lr, [ctxReg, #GoReturnAddress] + // ldr fp, [ctxReg, #OriginalFramePointer] + // ldr tmp, [ctxReg, #OriginalStackPointer] + // mov sp, tmp ;; sp cannot be str'ed directly. + // ret ;; --> return to the Go code + + var ctxEvicted bool + if ctx := ctxReg.RealReg(); ctx == fp || ctx == lr { + // In order to avoid overwriting the context register, we move ctxReg to tmp. + c.Emit4Bytes(encodeMov64(regNumberInEncoding[tmp], regNumberInEncoding[ctx], false, false)) + ctxReg = tmpRegVReg + ctxEvicted = true + } + + restoreLr := encodeLoadOrStore( + uLoad64, + regNumberInEncoding[lr], + addressMode{ + kind: addressModeKindRegUnsignedImm12, + rn: ctxReg, + imm: wazevoapi.ExecutionContextOffsetGoReturnAddress.I64(), + }, + ) + + restoreFp := encodeLoadOrStore( + uLoad64, + regNumberInEncoding[fp], + addressMode{ + kind: addressModeKindRegUnsignedImm12, + rn: ctxReg, + imm: wazevoapi.ExecutionContextOffsetOriginalFramePointer.I64(), + }, + ) + + restoreSpToTmp := encodeLoadOrStore( + uLoad64, + regNumberInEncoding[tmp], + addressMode{ + kind: addressModeKindRegUnsignedImm12, + rn: ctxReg, + imm: wazevoapi.ExecutionContextOffsetOriginalStackPointer.I64(), + }, + ) + + movTmpToSp := encodeAddSubtractImmediate(0b100, 0, 0, + regNumberInEncoding[tmp], regNumberInEncoding[sp]) + + c.Emit4Bytes(restoreFp) + c.Emit4Bytes(restoreLr) + c.Emit4Bytes(restoreSpToTmp) + c.Emit4Bytes(movTmpToSp) + c.Emit4Bytes(encodeRet()) + if !ctxEvicted { + // In order to have the fixed-length exit sequence, we need to padd the binary. + // Since this will never be reached, we insert a dummy instruction. + c.Emit4Bytes(dummyInstruction) + } +} + +func encodeRet() uint32 { + // https://developer.arm.com/documentation/ddi0596/2020-12/Base-Instructions/RET--Return-from-subroutine-?lang=en + return 0b1101011001011111<<16 | regNumberInEncoding[lr]<<5 +} + +func encodeAtomicRmw(op atomicRmwOp, rs, rt, rn uint32, size uint32) uint32 { + var _31to21, _15to10, sz uint32 + + switch size { + case 8: + sz = 0b11 + case 4: + sz = 0b10 + case 2: + sz = 0b01 + case 1: + sz = 0b00 + } + + _31to21 = 0b00111000_111 | sz<<9 + + switch op { + case atomicRmwOpAdd: + _15to10 = 0b000000 + case atomicRmwOpClr: + _15to10 = 0b000100 + case atomicRmwOpSet: + _15to10 = 0b001100 + case atomicRmwOpEor: + _15to10 = 0b001000 + case atomicRmwOpSwp: + _15to10 = 0b100000 + } + + return _31to21<<21 | rs<<16 | _15to10<<10 | rn<<5 | rt +} + +func encodeAtomicCas(rs, rt, rn uint32, size uint32) uint32 { + var _31to21, _15to10, sz uint32 + + switch size { + case 8: + sz = 0b11 + case 4: + sz = 0b10 + case 2: + sz = 0b01 + case 1: + sz = 0b00 + } + + _31to21 = 0b00001000_111 | sz<<9 + _15to10 = 0b111111 + + return _31to21<<21 | rs<<16 | _15to10<<10 | rn<<5 | rt +} + +func encodeAtomicLoadStore(rn, rt, size, l uint32) uint32 { + var _31to21, _20to16, _15to10, sz uint32 + + switch size { + case 8: + sz = 0b11 + case 4: + sz = 0b10 + case 2: + sz = 0b01 + case 1: + sz = 0b00 + } + + _31to21 = 0b00001000_100 | sz<<9 | l<<1 + _20to16 = 0b11111 + _15to10 = 0b111111 + + return _31to21<<21 | _20to16<<16 | _15to10<<10 | rn<<5 | rt +} + +func encodeDMB() uint32 { + return 0b11010101000000110011101110111111 +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/lower_constant.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/lower_constant.go new file mode 100644 index 00000000000..77256314422 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/lower_constant.go @@ -0,0 +1,301 @@ +package arm64 + +import ( + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" +) + +// lowerConstant allocates a new VReg and inserts the instruction to load the constant value. +func (m *machine) lowerConstant(instr *ssa.Instruction) (vr regalloc.VReg) { + val := instr.Return() + valType := val.Type() + + vr = m.compiler.AllocateVReg(valType) + v := instr.ConstantVal() + m.insertLoadConstant(v, valType, vr) + return +} + +// InsertLoadConstantBlockArg implements backend.Machine. +func (m *machine) InsertLoadConstantBlockArg(instr *ssa.Instruction, vr regalloc.VReg) { + val := instr.Return() + valType := val.Type() + v := instr.ConstantVal() + load := m.allocateInstr() + load.asLoadConstBlockArg(v, valType, vr) + m.insert(load) +} + +func (m *machine) lowerLoadConstantBlockArgAfterRegAlloc(i *instruction) { + v, typ, dst := i.loadConstBlockArgData() + m.insertLoadConstant(v, typ, dst) +} + +func (m *machine) insertLoadConstant(v uint64, valType ssa.Type, vr regalloc.VReg) { + if valType.Bits() < 64 { // Clear the redundant bits just in case it's unexpectedly sign-extended, etc. + v = v & ((1 << valType.Bits()) - 1) + } + + switch valType { + case ssa.TypeF32: + loadF := m.allocateInstr() + loadF.asLoadFpuConst32(vr, v) + m.insert(loadF) + case ssa.TypeF64: + loadF := m.allocateInstr() + loadF.asLoadFpuConst64(vr, v) + m.insert(loadF) + case ssa.TypeI32: + if v == 0 { + m.InsertMove(vr, xzrVReg, ssa.TypeI32) + } else { + m.lowerConstantI32(vr, int32(v)) + } + case ssa.TypeI64: + if v == 0 { + m.InsertMove(vr, xzrVReg, ssa.TypeI64) + } else { + m.lowerConstantI64(vr, int64(v)) + } + default: + panic("TODO") + } +} + +// The following logics are based on the old asm/arm64 package. +// https://github.com/tetratelabs/wazero/blob/39f2ff23a6d609e10c82b9cc0b981f6de5b87a9c/internal/asm/arm64/impl.go + +func (m *machine) lowerConstantI32(dst regalloc.VReg, c int32) { + // Following the logic here: + // https://github.com/golang/go/blob/release-branch.go1.15/src/cmd/internal/obj/arm64/asm7.go#L1637 + ic := int64(uint32(c)) + if ic >= 0 && (ic <= 0xfff || (ic&0xfff) == 0 && (uint64(ic>>12) <= 0xfff)) { + if isBitMaskImmediate(uint64(c), false) { + m.lowerConstViaBitMaskImmediate(uint64(uint32(c)), dst, false) + return + } + } + + if t := const16bitAligned(int64(uint32(c))); t >= 0 { + // If the const can fit within 16-bit alignment, for example, 0xffff, 0xffff_0000 or 0xffff_0000_0000_0000 + // We could load it into temporary with movk. + m.insertMOVZ(dst, uint64(uint32(c)>>(16*t)), t, false) + } else if t := const16bitAligned(int64(^c)); t >= 0 { + // Also, if the inverse of the const can fit within 16-bit range, do the same ^^. + m.insertMOVN(dst, uint64(^c>>(16*t)), t, false) + } else if isBitMaskImmediate(uint64(uint32(c)), false) { + m.lowerConstViaBitMaskImmediate(uint64(c), dst, false) + } else { + // Otherwise, we use MOVZ and MOVK to load it. + c16 := uint16(c) + m.insertMOVZ(dst, uint64(c16), 0, false) + c16 = uint16(uint32(c) >> 16) + m.insertMOVK(dst, uint64(c16), 1, false) + } +} + +func (m *machine) lowerConstantI64(dst regalloc.VReg, c int64) { + // Following the logic here: + // https://github.com/golang/go/blob/go1.24.0/src/cmd/internal/obj/arm64/asm7.go#L2161-L2215 + if c >= 0 && (c <= 0xfff || (c&0xfff) == 0 && (uint64(c>>12) <= 0xfff)) { + if isBitMaskImmediate(uint64(c), true) { + m.lowerConstViaBitMaskImmediate(uint64(c), dst, true) + return + } + } + + if t := const16bitAligned(c); t >= 0 { + // If the const can fit within 16-bit alignment, for example, 0xffff, 0xffff_0000 or 0xffff_0000_0000_0000 + // We could load it into temporary with movk. + m.insertMOVZ(dst, uint64(c)>>(16*t), t, true) + } else if t := const16bitAligned(^c); t >= 0 { + // Also, if the reverse of the const can fit within 16-bit range, do the same ^^. + m.insertMOVN(dst, uint64(^c)>>(16*t), t, true) + } else if isBitMaskImmediate(uint64(c), true) { + m.lowerConstViaBitMaskImmediate(uint64(c), dst, true) + } else { + m.load64bitConst(c, dst) + } +} + +func (m *machine) lowerConstViaBitMaskImmediate(c uint64, dst regalloc.VReg, b64 bool) { + instr := m.allocateInstr() + instr.asALUBitmaskImm(aluOpOrr, dst, xzrVReg, c, b64) + m.insert(instr) +} + +// isBitMaskImmediate determines if the value can be encoded as "bitmask immediate". +// +// Such an immediate is a 32-bit or 64-bit pattern viewed as a vector of identical elements of size e = 2, 4, 8, 16, 32, or 64 bits. +// Each element contains the same sub-pattern: a single run of 1 to e-1 non-zero bits, rotated by 0 to e-1 bits. +// +// See https://developer.arm.com/documentation/dui0802/b/A64-General-Instructions/MOV--bitmask-immediate- +func isBitMaskImmediate(x uint64, _64 bool) bool { + // All zeros and ones are not "bitmask immediate" by definition. + if x == 0 || (_64 && x == 0xffff_ffff_ffff_ffff) || (!_64 && x == 0xffff_ffff) { + return false + } + + switch { + case x != x>>32|x<<32: + // e = 64 + case x != x>>16|x<<48: + // e = 32 (x == x>>32|x<<32). + // e.g. 0x00ff_ff00_00ff_ff00 + x = uint64(int32(x)) + case x != x>>8|x<<56: + // e = 16 (x == x>>16|x<<48). + // e.g. 0x00ff_00ff_00ff_00ff + x = uint64(int16(x)) + case x != x>>4|x<<60: + // e = 8 (x == x>>8|x<<56). + // e.g. 0x0f0f_0f0f_0f0f_0f0f + x = uint64(int8(x)) + default: + // e = 4 or 2. + return true + } + return sequenceOfSetbits(x) || sequenceOfSetbits(^x) +} + +// sequenceOfSetbits returns true if the number's binary representation is the sequence set bit (1). +// For example: 0b1110 -> true, 0b1010 -> false +func sequenceOfSetbits(x uint64) bool { + y := getLowestBit(x) + // If x is a sequence of set bit, this should results in the number + // with only one set bit (i.e. power of two). + y += x + return (y-1)&y == 0 +} + +func getLowestBit(x uint64) uint64 { + return x & (^x + 1) +} + +// const16bitAligned check if the value is on the 16-bit alignment. +// If so, returns the shift num divided by 16, and otherwise -1. +func const16bitAligned(v int64) (ret int) { + ret = -1 + for s := 0; s < 64; s += 16 { + if (uint64(v) &^ (uint64(0xffff) << uint(s))) == 0 { + ret = s / 16 + break + } + } + return +} + +// load64bitConst loads a 64-bit constant into the register, following the same logic to decide how to load large 64-bit +// consts as in the Go assembler. +// +// See https://github.com/golang/go/blob/go1.24.0/src/cmd/internal/obj/arm64/asm7.go#L7555-L7682 +func (m *machine) load64bitConst(c int64, dst regalloc.VReg) { + var bits [4]uint64 + var zeros, negs int + for i := 0; i < 4; i++ { + bits[i] = uint64(c) >> uint(i*16) & 0xffff + if v := bits[i]; v == 0 { + zeros++ + } else if v == 0xffff { + negs++ + } + } + + if zeros == 3 { + // one MOVZ instruction. + for i, v := range bits { + if v != 0 { + m.insertMOVZ(dst, v, i, true) + } + } + } else if negs == 3 { + // one MOVN instruction. + for i, v := range bits { + if v != 0xffff { + v = ^v + m.insertMOVN(dst, v, i, true) + } + } + } else if zeros == 2 { + // one MOVZ then one OVK. + var movz bool + for i, v := range bits { + if !movz && v != 0 { // MOVZ. + m.insertMOVZ(dst, v, i, true) + movz = true + } else if v != 0 { + m.insertMOVK(dst, v, i, true) + } + } + + } else if negs == 2 { + // one MOVN then one or two MOVK. + var movn bool + for i, v := range bits { // Emit MOVN. + if !movn && v != 0xffff { + v = ^v + // https://developer.arm.com/documentation/dui0802/a/A64-General-Instructions/MOVN + m.insertMOVN(dst, v, i, true) + movn = true + } else if v != 0xffff { + m.insertMOVK(dst, v, i, true) + } + } + + } else if zeros == 1 { + // one MOVZ then two MOVK. + var movz bool + for i, v := range bits { + if !movz && v != 0 { // MOVZ. + m.insertMOVZ(dst, v, i, true) + movz = true + } else if v != 0 { + m.insertMOVK(dst, v, i, true) + } + } + + } else if negs == 1 { + // one MOVN then two MOVK. + var movn bool + for i, v := range bits { // Emit MOVN. + if !movn && v != 0xffff { + v = ^v + // https://developer.arm.com/documentation/dui0802/a/A64-General-Instructions/MOVN + m.insertMOVN(dst, v, i, true) + movn = true + } else if v != 0xffff { + m.insertMOVK(dst, v, i, true) + } + } + + } else { + // one MOVZ then up to three MOVK. + var movz bool + for i, v := range bits { + if !movz && v != 0 { // MOVZ. + m.insertMOVZ(dst, v, i, true) + movz = true + } else if v != 0 { + m.insertMOVK(dst, v, i, true) + } + } + } +} + +func (m *machine) insertMOVZ(dst regalloc.VReg, v uint64, shift int, dst64 bool) { + instr := m.allocateInstr() + instr.asMOVZ(dst, v, uint32(shift), dst64) + m.insert(instr) +} + +func (m *machine) insertMOVK(dst regalloc.VReg, v uint64, shift int, dst64 bool) { + instr := m.allocateInstr() + instr.asMOVK(dst, v, uint32(shift), dst64) + m.insert(instr) +} + +func (m *machine) insertMOVN(dst regalloc.VReg, v uint64, shift int, dst64 bool) { + instr := m.allocateInstr() + instr.asMOVN(dst, v, uint32(shift), dst64) + m.insert(instr) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/lower_instr.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/lower_instr.go new file mode 100644 index 00000000000..190bc601496 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/lower_instr.go @@ -0,0 +1,2227 @@ +package arm64 + +// Files prefixed as lower_instr** do the instruction selection, meaning that lowering SSA level instructions +// into machine specific instructions. +// +// Importantly, what the lower** functions does includes tree-matching; find the pattern from the given instruction tree, +// and merge the multiple instructions if possible. It can be considered as "N:1" instruction selection. + +import ( + "fmt" + "math" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +// LowerSingleBranch implements backend.Machine. +func (m *machine) LowerSingleBranch(br *ssa.Instruction) { + switch br.Opcode() { + case ssa.OpcodeJump: + _, _, targetBlkID := br.BranchData() + if br.IsFallthroughJump() { + return + } + b := m.allocateInstr() + targetBlk := m.compiler.SSABuilder().BasicBlock(targetBlkID) + if targetBlk.ReturnBlock() { + b.asRet() + } else { + b.asBr(ssaBlockLabel(targetBlk)) + } + m.insert(b) + case ssa.OpcodeBrTable: + m.lowerBrTable(br) + default: + panic("BUG: unexpected branch opcode" + br.Opcode().String()) + } +} + +func (m *machine) lowerBrTable(i *ssa.Instruction) { + index, targetBlockIDs := i.BrTableData() + targetBlockCount := len(targetBlockIDs.View()) + indexOperand := m.getOperand_NR(m.compiler.ValueDefinition(index), extModeNone) + + // Firstly, we have to do the bounds check of the index, and + // set it to the default target (sitting at the end of the list) if it's out of bounds. + + // mov maxIndexReg #maximum_index + // subs wzr, index, maxIndexReg + // csel adjustedIndex, maxIndexReg, index, hs ;; if index is higher or equal than maxIndexReg. + maxIndexReg := m.compiler.AllocateVReg(ssa.TypeI32) + m.lowerConstantI32(maxIndexReg, int32(targetBlockCount-1)) + subs := m.allocateInstr() + subs.asALU(aluOpSubS, xzrVReg, indexOperand, operandNR(maxIndexReg), false) + m.insert(subs) + csel := m.allocateInstr() + adjustedIndex := m.compiler.AllocateVReg(ssa.TypeI32) + csel.asCSel(adjustedIndex, operandNR(maxIndexReg), indexOperand, hs, false) + m.insert(csel) + + brSequence := m.allocateInstr() + + tableIndex := m.addJmpTableTarget(targetBlockIDs) + brSequence.asBrTableSequence(adjustedIndex, tableIndex, targetBlockCount) + m.insert(brSequence) +} + +// LowerConditionalBranch implements backend.Machine. +func (m *machine) LowerConditionalBranch(b *ssa.Instruction) { + cval, args, targetBlkID := b.BranchData() + if len(args) > 0 { + panic(fmt.Sprintf( + "conditional branch shouldn't have args; likely a bug in critical edge splitting: from %s to %s", + m.currentLabelPos.sb, + targetBlkID, + )) + } + + targetBlk := m.compiler.SSABuilder().BasicBlock(targetBlkID) + target := ssaBlockLabel(targetBlk) + cvalDef := m.compiler.ValueDefinition(cval) + + switch { + case m.compiler.MatchInstr(cvalDef, ssa.OpcodeIcmp): // This case, we can use the ALU flag set by SUBS instruction. + cvalInstr := cvalDef.Instr + x, y, c := cvalInstr.IcmpData() + cc, signed := condFlagFromSSAIntegerCmpCond(c), c.Signed() + if b.Opcode() == ssa.OpcodeBrz { + cc = cc.invert() + } + + if !m.tryLowerBandToFlag(x, y) { + m.lowerIcmpToFlag(x, y, signed) + } + cbr := m.allocateInstr() + cbr.asCondBr(cc.asCond(), target, false /* ignored */) + m.insert(cbr) + cvalDef.Instr.MarkLowered() + case m.compiler.MatchInstr(cvalDef, ssa.OpcodeFcmp): // This case we can use the Fpu flag directly. + cvalInstr := cvalDef.Instr + x, y, c := cvalInstr.FcmpData() + cc := condFlagFromSSAFloatCmpCond(c) + if b.Opcode() == ssa.OpcodeBrz { + cc = cc.invert() + } + m.lowerFcmpToFlag(x, y) + cbr := m.allocateInstr() + cbr.asCondBr(cc.asCond(), target, false /* ignored */) + m.insert(cbr) + cvalDef.Instr.MarkLowered() + default: + rn := m.getOperand_NR(cvalDef, extModeNone) + var c cond + if b.Opcode() == ssa.OpcodeBrz { + c = registerAsRegZeroCond(rn.nr()) + } else { + c = registerAsRegNotZeroCond(rn.nr()) + } + cbr := m.allocateInstr() + cbr.asCondBr(c, target, false) + m.insert(cbr) + } +} + +func (m *machine) tryLowerBandToFlag(x, y ssa.Value) (ok bool) { + xx := m.compiler.ValueDefinition(x) + yy := m.compiler.ValueDefinition(y) + if xx.IsFromInstr() && xx.Instr.Constant() && xx.Instr.ConstantVal() == 0 { + if m.compiler.MatchInstr(yy, ssa.OpcodeBand) { + bandInstr := yy.Instr + m.lowerBitwiseAluOp(bandInstr, aluOpAnds, true) + ok = true + bandInstr.MarkLowered() + return + } + } + + if yy.IsFromInstr() && yy.Instr.Constant() && yy.Instr.ConstantVal() == 0 { + if m.compiler.MatchInstr(xx, ssa.OpcodeBand) { + bandInstr := xx.Instr + m.lowerBitwiseAluOp(bandInstr, aluOpAnds, true) + ok = true + bandInstr.MarkLowered() + return + } + } + return +} + +// LowerInstr implements backend.Machine. +func (m *machine) LowerInstr(instr *ssa.Instruction) { + if l := instr.SourceOffset(); l.Valid() { + info := m.allocateInstr().asEmitSourceOffsetInfo(l) + m.insert(info) + } + + switch op := instr.Opcode(); op { + case ssa.OpcodeBrz, ssa.OpcodeBrnz, ssa.OpcodeJump, ssa.OpcodeBrTable: + panic("BUG: branching instructions are handled by LowerBranches") + case ssa.OpcodeReturn: + panic("BUG: return must be handled by backend.Compiler") + case ssa.OpcodeIadd, ssa.OpcodeIsub: + m.lowerSubOrAdd(instr, op == ssa.OpcodeIadd) + case ssa.OpcodeFadd, ssa.OpcodeFsub, ssa.OpcodeFmul, ssa.OpcodeFdiv, ssa.OpcodeFmax, ssa.OpcodeFmin: + m.lowerFpuBinOp(instr) + case ssa.OpcodeIconst, ssa.OpcodeF32const, ssa.OpcodeF64const: // Constant instructions are inlined. + case ssa.OpcodeExitWithCode: + execCtx, code := instr.ExitWithCodeData() + m.lowerExitWithCode(m.compiler.VRegOf(execCtx), code) + case ssa.OpcodeExitIfTrueWithCode: + execCtx, c, code := instr.ExitIfTrueWithCodeData() + m.lowerExitIfTrueWithCode(m.compiler.VRegOf(execCtx), c, code) + case ssa.OpcodeStore, ssa.OpcodeIstore8, ssa.OpcodeIstore16, ssa.OpcodeIstore32: + m.lowerStore(instr) + case ssa.OpcodeLoad: + dst := instr.Return() + ptr, offset, typ := instr.LoadData() + m.lowerLoad(ptr, offset, typ, dst) + case ssa.OpcodeVZeroExtLoad: + dst := instr.Return() + ptr, offset, typ := instr.VZeroExtLoadData() + m.lowerLoad(ptr, offset, typ, dst) + case ssa.OpcodeUload8, ssa.OpcodeUload16, ssa.OpcodeUload32, ssa.OpcodeSload8, ssa.OpcodeSload16, ssa.OpcodeSload32: + ptr, offset, _ := instr.LoadData() + ret := m.compiler.VRegOf(instr.Return()) + m.lowerExtLoad(op, ptr, offset, ret) + case ssa.OpcodeCall, ssa.OpcodeCallIndirect: + m.lowerCall(instr) + case ssa.OpcodeIcmp: + m.lowerIcmp(instr) + case ssa.OpcodeVIcmp: + m.lowerVIcmp(instr) + case ssa.OpcodeVFcmp: + m.lowerVFcmp(instr) + case ssa.OpcodeVCeil: + m.lowerVecMisc(vecOpFrintp, instr) + case ssa.OpcodeVFloor: + m.lowerVecMisc(vecOpFrintm, instr) + case ssa.OpcodeVTrunc: + m.lowerVecMisc(vecOpFrintz, instr) + case ssa.OpcodeVNearest: + m.lowerVecMisc(vecOpFrintn, instr) + case ssa.OpcodeVMaxPseudo: + m.lowerVMinMaxPseudo(instr, true) + case ssa.OpcodeVMinPseudo: + m.lowerVMinMaxPseudo(instr, false) + case ssa.OpcodeBand: + m.lowerBitwiseAluOp(instr, aluOpAnd, false) + case ssa.OpcodeBor: + m.lowerBitwiseAluOp(instr, aluOpOrr, false) + case ssa.OpcodeBxor: + m.lowerBitwiseAluOp(instr, aluOpEor, false) + case ssa.OpcodeIshl: + m.lowerShifts(instr, extModeNone, aluOpLsl) + case ssa.OpcodeSshr: + if instr.Return().Type().Bits() == 64 { + m.lowerShifts(instr, extModeSignExtend64, aluOpAsr) + } else { + m.lowerShifts(instr, extModeSignExtend32, aluOpAsr) + } + case ssa.OpcodeUshr: + if instr.Return().Type().Bits() == 64 { + m.lowerShifts(instr, extModeZeroExtend64, aluOpLsr) + } else { + m.lowerShifts(instr, extModeZeroExtend32, aluOpLsr) + } + case ssa.OpcodeRotl: + m.lowerRotl(instr) + case ssa.OpcodeRotr: + m.lowerRotr(instr) + case ssa.OpcodeSExtend, ssa.OpcodeUExtend: + from, to, signed := instr.ExtendData() + m.lowerExtend(instr.Arg(), instr.Return(), from, to, signed) + case ssa.OpcodeFcmp: + x, y, c := instr.FcmpData() + m.lowerFcmp(x, y, instr.Return(), c) + case ssa.OpcodeImul: + x, y := instr.Arg2() + result := instr.Return() + m.lowerImul(x, y, result) + case ssa.OpcodeUndefined: + undef := m.allocateInstr() + undef.asUDF() + m.insert(undef) + case ssa.OpcodeSelect: + c, x, y := instr.SelectData() + if x.Type() == ssa.TypeV128 { + rc := m.getOperand_NR(m.compiler.ValueDefinition(c), extModeNone) + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + m.lowerSelectVec(rc, rn, rm, rd) + } else { + m.lowerSelect(c, x, y, instr.Return()) + } + case ssa.OpcodeClz: + x := instr.Arg() + result := instr.Return() + m.lowerClz(x, result) + case ssa.OpcodeCtz: + x := instr.Arg() + result := instr.Return() + m.lowerCtz(x, result) + case ssa.OpcodePopcnt: + x := instr.Arg() + result := instr.Return() + m.lowerPopcnt(x, result) + case ssa.OpcodeFcvtToSint, ssa.OpcodeFcvtToSintSat: + x, ctx := instr.Arg2() + result := instr.Return() + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rd := m.compiler.VRegOf(result) + ctxVReg := m.compiler.VRegOf(ctx) + m.lowerFpuToInt(rd, rn, ctxVReg, true, x.Type() == ssa.TypeF64, + result.Type().Bits() == 64, op == ssa.OpcodeFcvtToSintSat) + case ssa.OpcodeFcvtToUint, ssa.OpcodeFcvtToUintSat: + x, ctx := instr.Arg2() + result := instr.Return() + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rd := m.compiler.VRegOf(result) + ctxVReg := m.compiler.VRegOf(ctx) + m.lowerFpuToInt(rd, rn, ctxVReg, false, x.Type() == ssa.TypeF64, + result.Type().Bits() == 64, op == ssa.OpcodeFcvtToUintSat) + case ssa.OpcodeFcvtFromSint: + x := instr.Arg() + result := instr.Return() + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rd := m.compiler.VRegOf(result) + m.lowerIntToFpu(rd, rn, true, x.Type() == ssa.TypeI64, result.Type().Bits() == 64) + case ssa.OpcodeFcvtFromUint: + x := instr.Arg() + result := instr.Return() + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rd := m.compiler.VRegOf(result) + m.lowerIntToFpu(rd, rn, false, x.Type() == ssa.TypeI64, result.Type().Bits() == 64) + case ssa.OpcodeFdemote: + v := instr.Arg() + rn := m.getOperand_NR(m.compiler.ValueDefinition(v), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + cnt := m.allocateInstr() + cnt.asFpuRR(fpuUniOpCvt64To32, rd, rn, false) + m.insert(cnt) + case ssa.OpcodeFpromote: + v := instr.Arg() + rn := m.getOperand_NR(m.compiler.ValueDefinition(v), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + cnt := m.allocateInstr() + cnt.asFpuRR(fpuUniOpCvt32To64, rd, rn, true) + m.insert(cnt) + case ssa.OpcodeIreduce: + rn := m.getOperand_NR(m.compiler.ValueDefinition(instr.Arg()), extModeNone) + retVal := instr.Return() + rd := m.compiler.VRegOf(retVal) + + if retVal.Type() != ssa.TypeI32 { + panic("TODO?: Ireduce to non-i32") + } + mov := m.allocateInstr() + mov.asMove32(rd, rn.reg()) + m.insert(mov) + case ssa.OpcodeFneg: + m.lowerFpuUniOp(fpuUniOpNeg, instr.Arg(), instr.Return()) + case ssa.OpcodeSqrt: + m.lowerFpuUniOp(fpuUniOpSqrt, instr.Arg(), instr.Return()) + case ssa.OpcodeCeil: + m.lowerFpuUniOp(fpuUniOpRoundPlus, instr.Arg(), instr.Return()) + case ssa.OpcodeFloor: + m.lowerFpuUniOp(fpuUniOpRoundMinus, instr.Arg(), instr.Return()) + case ssa.OpcodeTrunc: + m.lowerFpuUniOp(fpuUniOpRoundZero, instr.Arg(), instr.Return()) + case ssa.OpcodeNearest: + m.lowerFpuUniOp(fpuUniOpRoundNearest, instr.Arg(), instr.Return()) + case ssa.OpcodeFabs: + m.lowerFpuUniOp(fpuUniOpAbs, instr.Arg(), instr.Return()) + case ssa.OpcodeBitcast: + m.lowerBitcast(instr) + case ssa.OpcodeFcopysign: + x, y := instr.Arg2() + m.lowerFcopysign(x, y, instr.Return()) + case ssa.OpcodeSdiv, ssa.OpcodeUdiv: + x, y, ctx := instr.Arg3() + ctxVReg := m.compiler.VRegOf(ctx) + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + m.lowerIDiv(ctxVReg, rd, rn, rm, x.Type() == ssa.TypeI64, op == ssa.OpcodeSdiv) + case ssa.OpcodeSrem, ssa.OpcodeUrem: + x, y, ctx := instr.Arg3() + ctxVReg := m.compiler.VRegOf(ctx) + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + m.lowerIRem(ctxVReg, rd, rn.nr(), rm, x.Type() == ssa.TypeI64, op == ssa.OpcodeSrem) + case ssa.OpcodeVconst: + result := m.compiler.VRegOf(instr.Return()) + lo, hi := instr.VconstData() + v := m.allocateInstr() + v.asLoadFpuConst128(result, lo, hi) + m.insert(v) + case ssa.OpcodeVbnot: + x := instr.Arg() + ins := m.allocateInstr() + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + ins.asVecMisc(vecOpNot, rd, rn, vecArrangement16B) + m.insert(ins) + case ssa.OpcodeVbxor: + x, y := instr.Arg2() + m.lowerVecRRR(vecOpEOR, x, y, instr.Return(), vecArrangement16B) + case ssa.OpcodeVbor: + x, y := instr.Arg2() + m.lowerVecRRR(vecOpOrr, x, y, instr.Return(), vecArrangement16B) + case ssa.OpcodeVband: + x, y := instr.Arg2() + m.lowerVecRRR(vecOpAnd, x, y, instr.Return(), vecArrangement16B) + case ssa.OpcodeVbandnot: + x, y := instr.Arg2() + m.lowerVecRRR(vecOpBic, x, y, instr.Return(), vecArrangement16B) + case ssa.OpcodeVbitselect: + c, x, y := instr.SelectData() + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + creg := m.getOperand_NR(m.compiler.ValueDefinition(c), extModeNone) + tmp := m.compiler.AllocateVReg(ssa.TypeV128) + + // creg is overwritten by BSL, so we need to move it to the result register before the instruction + // in case when it is used somewhere else. + mov := m.allocateInstr() + mov.asFpuMov128(tmp, creg.nr()) + m.insert(mov) + + ins := m.allocateInstr() + ins.asVecRRRRewrite(vecOpBsl, tmp, rn, rm, vecArrangement16B) + m.insert(ins) + + mov2 := m.allocateInstr() + rd := m.compiler.VRegOf(instr.Return()) + mov2.asFpuMov128(rd, tmp) + m.insert(mov2) + case ssa.OpcodeVanyTrue, ssa.OpcodeVallTrue: + x, lane := instr.ArgWithLane() + var arr vecArrangement + if op == ssa.OpcodeVallTrue { + arr = ssaLaneToArrangement(lane) + } + rm := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + m.lowerVcheckTrue(op, rm, rd, arr) + case ssa.OpcodeVhighBits: + x, lane := instr.ArgWithLane() + rm := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + arr := ssaLaneToArrangement(lane) + m.lowerVhighBits(rm, rd, arr) + case ssa.OpcodeVIadd: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + m.lowerVecRRR(vecOpAdd, x, y, instr.Return(), arr) + case ssa.OpcodeExtIaddPairwise: + v, lane, signed := instr.ExtIaddPairwiseData() + vv := m.getOperand_NR(m.compiler.ValueDefinition(v), extModeNone) + + tmpLo, tmpHi := operandNR(m.compiler.AllocateVReg(ssa.TypeV128)), operandNR(m.compiler.AllocateVReg(ssa.TypeV128)) + var widen vecOp + if signed { + widen = vecOpSshll + } else { + widen = vecOpUshll + } + + var loArr, hiArr, dstArr vecArrangement + switch lane { + case ssa.VecLaneI8x16: + loArr, hiArr, dstArr = vecArrangement8B, vecArrangement16B, vecArrangement8H + case ssa.VecLaneI16x8: + loArr, hiArr, dstArr = vecArrangement4H, vecArrangement8H, vecArrangement4S + case ssa.VecLaneI32x4: + loArr, hiArr, dstArr = vecArrangement2S, vecArrangement4S, vecArrangement2D + default: + panic("unsupported lane " + lane.String()) + } + + widenLo := m.allocateInstr().asVecShiftImm(widen, tmpLo.nr(), vv, operandShiftImm(0), loArr) + widenHi := m.allocateInstr().asVecShiftImm(widen, tmpHi.nr(), vv, operandShiftImm(0), hiArr) + addp := m.allocateInstr().asVecRRR(vecOpAddp, m.compiler.VRegOf(instr.Return()), tmpLo, tmpHi, dstArr) + m.insert(widenLo) + m.insert(widenHi) + m.insert(addp) + + case ssa.OpcodeVSaddSat: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + m.lowerVecRRR(vecOpSqadd, x, y, instr.Return(), arr) + case ssa.OpcodeVUaddSat: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + m.lowerVecRRR(vecOpUqadd, x, y, instr.Return(), arr) + case ssa.OpcodeVIsub: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + m.lowerVecRRR(vecOpSub, x, y, instr.Return(), arr) + case ssa.OpcodeVSsubSat: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + m.lowerVecRRR(vecOpSqsub, x, y, instr.Return(), arr) + case ssa.OpcodeVUsubSat: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + m.lowerVecRRR(vecOpUqsub, x, y, instr.Return(), arr) + case ssa.OpcodeVImin: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + m.lowerVecRRR(vecOpSmin, x, y, instr.Return(), arr) + case ssa.OpcodeVUmin: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + m.lowerVecRRR(vecOpUmin, x, y, instr.Return(), arr) + case ssa.OpcodeVImax: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + m.lowerVecRRR(vecOpSmax, x, y, instr.Return(), arr) + case ssa.OpcodeVUmax: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + m.lowerVecRRR(vecOpUmax, x, y, instr.Return(), arr) + case ssa.OpcodeVAvgRound: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + m.lowerVecRRR(vecOpUrhadd, x, y, instr.Return(), arr) + case ssa.OpcodeVImul: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + m.lowerVIMul(rd, rn, rm, arr) + case ssa.OpcodeVIabs: + m.lowerVecMisc(vecOpAbs, instr) + case ssa.OpcodeVIneg: + m.lowerVecMisc(vecOpNeg, instr) + case ssa.OpcodeVIpopcnt: + m.lowerVecMisc(vecOpCnt, instr) + case ssa.OpcodeVIshl, + ssa.OpcodeVSshr, ssa.OpcodeVUshr: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + m.lowerVShift(op, rd, rn, rm, arr) + case ssa.OpcodeVSqrt: + m.lowerVecMisc(vecOpFsqrt, instr) + case ssa.OpcodeVFabs: + m.lowerVecMisc(vecOpFabs, instr) + case ssa.OpcodeVFneg: + m.lowerVecMisc(vecOpFneg, instr) + case ssa.OpcodeVFmin: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + m.lowerVecRRR(vecOpFmin, x, y, instr.Return(), arr) + case ssa.OpcodeVFmax: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + m.lowerVecRRR(vecOpFmax, x, y, instr.Return(), arr) + case ssa.OpcodeVFadd: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + m.lowerVecRRR(vecOpFadd, x, y, instr.Return(), arr) + case ssa.OpcodeVFsub: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + m.lowerVecRRR(vecOpFsub, x, y, instr.Return(), arr) + case ssa.OpcodeVFmul: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + m.lowerVecRRR(vecOpFmul, x, y, instr.Return(), arr) + case ssa.OpcodeSqmulRoundSat: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + m.lowerVecRRR(vecOpSqrdmulh, x, y, instr.Return(), arr) + case ssa.OpcodeVFdiv: + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + m.lowerVecRRR(vecOpFdiv, x, y, instr.Return(), arr) + case ssa.OpcodeVFcvtToSintSat, ssa.OpcodeVFcvtToUintSat: + x, lane := instr.ArgWithLane() + arr := ssaLaneToArrangement(lane) + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + m.lowerVfpuToInt(rd, rn, arr, op == ssa.OpcodeVFcvtToSintSat) + case ssa.OpcodeVFcvtFromSint, ssa.OpcodeVFcvtFromUint: + x, lane := instr.ArgWithLane() + arr := ssaLaneToArrangement(lane) + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + m.lowerVfpuFromInt(rd, rn, arr, op == ssa.OpcodeVFcvtFromSint) + case ssa.OpcodeSwidenLow, ssa.OpcodeUwidenLow: + x, lane := instr.ArgWithLane() + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + + var arr vecArrangement + switch lane { + case ssa.VecLaneI8x16: + arr = vecArrangement8B + case ssa.VecLaneI16x8: + arr = vecArrangement4H + case ssa.VecLaneI32x4: + arr = vecArrangement2S + } + + shll := m.allocateInstr() + if signed := op == ssa.OpcodeSwidenLow; signed { + shll.asVecShiftImm(vecOpSshll, rd, rn, operandShiftImm(0), arr) + } else { + shll.asVecShiftImm(vecOpUshll, rd, rn, operandShiftImm(0), arr) + } + m.insert(shll) + case ssa.OpcodeSwidenHigh, ssa.OpcodeUwidenHigh: + x, lane := instr.ArgWithLane() + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + + arr := ssaLaneToArrangement(lane) + + shll := m.allocateInstr() + if signed := op == ssa.OpcodeSwidenHigh; signed { + shll.asVecShiftImm(vecOpSshll, rd, rn, operandShiftImm(0), arr) + } else { + shll.asVecShiftImm(vecOpUshll, rd, rn, operandShiftImm(0), arr) + } + m.insert(shll) + + case ssa.OpcodeSnarrow, ssa.OpcodeUnarrow: + x, y, lane := instr.Arg2WithLane() + var arr, arr2 vecArrangement + switch lane { + case ssa.VecLaneI16x8: // I16x8 + arr = vecArrangement8B + arr2 = vecArrangement16B // Implies sqxtn2. + case ssa.VecLaneI32x4: + arr = vecArrangement4H + arr2 = vecArrangement8H // Implies sqxtn2. + default: + panic("unsupported lane " + lane.String()) + } + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + + tmp := m.compiler.AllocateVReg(ssa.TypeV128) + + loQxtn := m.allocateInstr() + hiQxtn := m.allocateInstr() + if signed := op == ssa.OpcodeSnarrow; signed { + // Narrow lanes on rn and write them into lower-half of rd. + loQxtn.asVecMisc(vecOpSqxtn, tmp, rn, arr) // low + // Narrow lanes on rm and write them into higher-half of rd. + hiQxtn.asVecMisc(vecOpSqxtn, tmp, rm, arr2) // high (sqxtn2) + } else { + // Narrow lanes on rn and write them into lower-half of rd. + loQxtn.asVecMisc(vecOpSqxtun, tmp, rn, arr) // low + // Narrow lanes on rm and write them into higher-half of rd. + hiQxtn.asVecMisc(vecOpSqxtun, tmp, rm, arr2) // high (sqxtn2) + } + m.insert(loQxtn) + m.insert(hiQxtn) + + mov := m.allocateInstr() + mov.asFpuMov128(rd, tmp) + m.insert(mov) + case ssa.OpcodeFvpromoteLow: + x, lane := instr.ArgWithLane() + if lane != ssa.VecLaneF32x4 { + panic("unsupported lane type " + lane.String()) + } + ins := m.allocateInstr() + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + ins.asVecMisc(vecOpFcvtl, rd, rn, vecArrangement2S) + m.insert(ins) + case ssa.OpcodeFvdemote: + x, lane := instr.ArgWithLane() + if lane != ssa.VecLaneF64x2 { + panic("unsupported lane type " + lane.String()) + } + ins := m.allocateInstr() + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + ins.asVecMisc(vecOpFcvtn, rd, rn, vecArrangement2S) + m.insert(ins) + case ssa.OpcodeExtractlane: + x, index, signed, lane := instr.ExtractlaneData() + + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + + mov := m.allocateInstr() + switch lane { + case ssa.VecLaneI8x16: + mov.asMovFromVec(rd, rn, vecArrangementB, vecIndex(index), signed) + case ssa.VecLaneI16x8: + mov.asMovFromVec(rd, rn, vecArrangementH, vecIndex(index), signed) + case ssa.VecLaneI32x4: + mov.asMovFromVec(rd, rn, vecArrangementS, vecIndex(index), signed) + case ssa.VecLaneI64x2: + mov.asMovFromVec(rd, rn, vecArrangementD, vecIndex(index), signed) + case ssa.VecLaneF32x4: + mov.asVecMovElement(rd, rn, vecArrangementS, vecIndex(0), vecIndex(index)) + case ssa.VecLaneF64x2: + mov.asVecMovElement(rd, rn, vecArrangementD, vecIndex(0), vecIndex(index)) + default: + panic("unsupported lane: " + lane.String()) + } + + m.insert(mov) + + case ssa.OpcodeInsertlane: + x, y, index, lane := instr.InsertlaneData() + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + tmpReg := m.compiler.AllocateVReg(ssa.TypeV128) + + // Initially mov rn to tmp. + mov1 := m.allocateInstr() + mov1.asFpuMov128(tmpReg, rn.nr()) + m.insert(mov1) + + // movToVec and vecMovElement do not clear the remaining bits to zero, + // thus, we can mov rm in-place to tmp. + mov2 := m.allocateInstr() + switch lane { + case ssa.VecLaneI8x16: + mov2.asMovToVec(tmpReg, rm, vecArrangementB, vecIndex(index)) + case ssa.VecLaneI16x8: + mov2.asMovToVec(tmpReg, rm, vecArrangementH, vecIndex(index)) + case ssa.VecLaneI32x4: + mov2.asMovToVec(tmpReg, rm, vecArrangementS, vecIndex(index)) + case ssa.VecLaneI64x2: + mov2.asMovToVec(tmpReg, rm, vecArrangementD, vecIndex(index)) + case ssa.VecLaneF32x4: + mov2.asVecMovElement(tmpReg, rm, vecArrangementS, vecIndex(index), vecIndex(0)) + case ssa.VecLaneF64x2: + mov2.asVecMovElement(tmpReg, rm, vecArrangementD, vecIndex(index), vecIndex(0)) + } + m.insert(mov2) + + // Finally mov tmp to rd. + mov3 := m.allocateInstr() + mov3.asFpuMov128(rd, tmpReg) + m.insert(mov3) + + case ssa.OpcodeSwizzle: + x, y, lane := instr.Arg2WithLane() + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + + arr := ssaLaneToArrangement(lane) + + // tbl ., { . }, . + tbl1 := m.allocateInstr() + tbl1.asVecTbl(1, rd, rn, rm, arr) + m.insert(tbl1) + + case ssa.OpcodeShuffle: + x, y, lane1, lane2 := instr.ShuffleData() + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + + m.lowerShuffle(rd, rn, rm, lane1, lane2) + + case ssa.OpcodeSplat: + x, lane := instr.ArgWithLane() + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + + dup := m.allocateInstr() + switch lane { + case ssa.VecLaneI8x16: + dup.asVecDup(rd, rn, vecArrangement16B) + case ssa.VecLaneI16x8: + dup.asVecDup(rd, rn, vecArrangement8H) + case ssa.VecLaneI32x4: + dup.asVecDup(rd, rn, vecArrangement4S) + case ssa.VecLaneI64x2: + dup.asVecDup(rd, rn, vecArrangement2D) + case ssa.VecLaneF32x4: + dup.asVecDupElement(rd, rn, vecArrangementS, vecIndex(0)) + case ssa.VecLaneF64x2: + dup.asVecDupElement(rd, rn, vecArrangementD, vecIndex(0)) + } + m.insert(dup) + + case ssa.OpcodeWideningPairwiseDotProductS: + x, y := instr.Arg2() + xx, yy := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone), + m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + tmp, tmp2 := operandNR(m.compiler.AllocateVReg(ssa.TypeV128)), operandNR(m.compiler.AllocateVReg(ssa.TypeV128)) + m.insert(m.allocateInstr().asVecRRR(vecOpSmull, tmp.nr(), xx, yy, vecArrangement8H)) + m.insert(m.allocateInstr().asVecRRR(vecOpSmull2, tmp2.nr(), xx, yy, vecArrangement8H)) + m.insert(m.allocateInstr().asVecRRR(vecOpAddp, tmp.nr(), tmp, tmp2, vecArrangement4S)) + + rd := m.compiler.VRegOf(instr.Return()) + m.insert(m.allocateInstr().asFpuMov128(rd, tmp.nr())) + + case ssa.OpcodeLoadSplat: + ptr, offset, lane := instr.LoadSplatData() + m.lowerLoadSplat(ptr, offset, lane, instr.Return()) + + case ssa.OpcodeAtomicRmw: + m.lowerAtomicRmw(instr) + + case ssa.OpcodeAtomicCas: + m.lowerAtomicCas(instr) + + case ssa.OpcodeAtomicLoad: + m.lowerAtomicLoad(instr) + + case ssa.OpcodeAtomicStore: + m.lowerAtomicStore(instr) + + case ssa.OpcodeFence: + instr := m.allocateInstr() + instr.asDMB() + m.insert(instr) + + case ssa.OpcodeTailCallReturnCall, ssa.OpcodeTailCallReturnCallIndirect: + m.lowerTailCall(instr) + + default: + panic("TODO: lowering " + op.String()) + } + m.FlushPendingInstructions() +} + +func (m *machine) lowerShuffle(rd regalloc.VReg, rn, rm operand, lane1, lane2 uint64) { + // `tbl2` requires 2 consecutive registers, so we arbitrarily pick v29, v30. + vReg, wReg := v29VReg, v30VReg + + // Initialize v29, v30 to rn, rm. + movv := m.allocateInstr() + movv.asFpuMov128(vReg, rn.nr()) + m.insert(movv) + + movw := m.allocateInstr() + movw.asFpuMov128(wReg, rm.nr()) + m.insert(movw) + + // `lane1`, `lane2` are already encoded as two u64s with the right layout: + // lane1 := lane[7]<<56 | ... | lane[1]<<8 | lane[0] + // lane2 := lane[15]<<56 | ... | lane[9]<<8 | lane[8] + // Thus, we can use loadFpuConst128. + tmp := operandNR(m.compiler.AllocateVReg(ssa.TypeV128)) + lfc := m.allocateInstr() + lfc.asLoadFpuConst128(tmp.nr(), lane1, lane2) + m.insert(lfc) + + // tbl .16b, { .16B, .16b }, .16b + tbl2 := m.allocateInstr() + tbl2.asVecTbl(2, rd, operandNR(vReg), tmp, vecArrangement16B) + m.insert(tbl2) +} + +func (m *machine) lowerVShift(op ssa.Opcode, rd regalloc.VReg, rn, rm operand, arr vecArrangement) { + var modulo byte + switch arr { + case vecArrangement16B: + modulo = 0x7 // Modulo 8. + case vecArrangement8H: + modulo = 0xf // Modulo 16. + case vecArrangement4S: + modulo = 0x1f // Modulo 32. + case vecArrangement2D: + modulo = 0x3f // Modulo 64. + default: + panic("unsupported arrangment " + arr.String()) + } + + rtmp := operandNR(m.compiler.AllocateVReg(ssa.TypeI64)) + vtmp := operandNR(m.compiler.AllocateVReg(ssa.TypeV128)) + + and := m.allocateInstr() + and.asALUBitmaskImm(aluOpAnd, rtmp.nr(), rm.nr(), uint64(modulo), true) + m.insert(and) + + if op != ssa.OpcodeVIshl { + // Negate the amount to make this as right shift. + neg := m.allocateInstr() + neg.asALU(aluOpSub, rtmp.nr(), operandNR(xzrVReg), rtmp, true) + m.insert(neg) + } + + // Copy the shift amount into a vector register as sshl/ushl requires it to be there. + dup := m.allocateInstr() + dup.asVecDup(vtmp.nr(), rtmp, arr) + m.insert(dup) + + if op == ssa.OpcodeVIshl || op == ssa.OpcodeVSshr { + sshl := m.allocateInstr() + sshl.asVecRRR(vecOpSshl, rd, rn, vtmp, arr) + m.insert(sshl) + } else { + ushl := m.allocateInstr() + ushl.asVecRRR(vecOpUshl, rd, rn, vtmp, arr) + m.insert(ushl) + } +} + +func (m *machine) lowerVcheckTrue(op ssa.Opcode, rm operand, rd regalloc.VReg, arr vecArrangement) { + tmp := operandNR(m.compiler.AllocateVReg(ssa.TypeV128)) + + // Special case VallTrue for i64x2. + if op == ssa.OpcodeVallTrue && arr == vecArrangement2D { + // cmeq v3?.2d, v2?.2d, #0 + // addp v3?.2d, v3?.2d, v3?.2d + // fcmp v3?, v3? + // cset dst, eq + + ins := m.allocateInstr() + ins.asVecMisc(vecOpCmeq0, tmp.nr(), rm, vecArrangement2D) + m.insert(ins) + + addp := m.allocateInstr() + addp.asVecRRR(vecOpAddp, tmp.nr(), tmp, tmp, vecArrangement2D) + m.insert(addp) + + fcmp := m.allocateInstr() + fcmp.asFpuCmp(tmp, tmp, true) + m.insert(fcmp) + + cset := m.allocateInstr() + cset.asCSet(rd, false, eq) + m.insert(cset) + + return + } + + // Create a scalar value with umaxp or uminv, then compare it against zero. + ins := m.allocateInstr() + if op == ssa.OpcodeVanyTrue { + // umaxp v4?.16b, v2?.16b, v2?.16b + ins.asVecRRR(vecOpUmaxp, tmp.nr(), rm, rm, vecArrangement16B) + } else { + // uminv d4?, v2?.4s + ins.asVecLanes(vecOpUminv, tmp.nr(), rm, arr) + } + m.insert(ins) + + // mov x3?, v4?.d[0] + // ccmp x3?, #0x0, #0x0, al + // cset x3?, ne + // mov x0, x3? + + movv := m.allocateInstr() + movv.asMovFromVec(rd, tmp, vecArrangementD, vecIndex(0), false) + m.insert(movv) + + fc := m.allocateInstr() + fc.asCCmpImm(operandNR(rd), uint64(0), al, 0, true) + m.insert(fc) + + cset := m.allocateInstr() + cset.asCSet(rd, false, ne) + m.insert(cset) +} + +func (m *machine) lowerVhighBits(rm operand, rd regalloc.VReg, arr vecArrangement) { + r0 := operandNR(m.compiler.AllocateVReg(ssa.TypeI64)) + v0 := operandNR(m.compiler.AllocateVReg(ssa.TypeV128)) + v1 := operandNR(m.compiler.AllocateVReg(ssa.TypeV128)) + + switch arr { + case vecArrangement16B: + // sshr v6?.16b, v2?.16b, #7 + // movz x4?, #0x201, lsl 0 + // movk x4?, #0x804, lsl 16 + // movk x4?, #0x2010, lsl 32 + // movk x4?, #0x8040, lsl 48 + // dup v5?.2d, x4? + // and v6?.16b, v6?.16b, v5?.16b + // ext v5?.16b, v6?.16b, v6?.16b, #8 + // zip1 v5?.16b, v6?.16b, v5?.16b + // addv s5?, v5?.8h + // umov s3?, v5?.h[0] + + // Right arithmetic shift on the original vector and store the result into v1. So we have: + // v1[i] = 0xff if vi<0, 0 otherwise. + sshr := m.allocateInstr() + sshr.asVecShiftImm(vecOpSshr, v1.nr(), rm, operandShiftImm(7), vecArrangement16B) + m.insert(sshr) + + // Load the bit mask into r0. + m.insertMOVZ(r0.nr(), 0x0201, 0, true) + m.insertMOVK(r0.nr(), 0x0804, 1, true) + m.insertMOVK(r0.nr(), 0x2010, 2, true) + m.insertMOVK(r0.nr(), 0x8040, 3, true) + + // dup r0 to v0. + dup := m.allocateInstr() + dup.asVecDup(v0.nr(), r0, vecArrangement2D) + m.insert(dup) + + // Lane-wise logical AND with the bit mask, meaning that we have + // v[i] = (1 << i) if vi<0, 0 otherwise. + // + // Below, we use the following notation: + // wi := (1 << i) if vi<0, 0 otherwise. + and := m.allocateInstr() + and.asVecRRR(vecOpAnd, v1.nr(), v1, v0, vecArrangement16B) + m.insert(and) + + // Swap the lower and higher 8 byte elements, and write it into v0, meaning that we have + // v0[i] = w(i+8) if i < 8, w(i-8) otherwise. + ext := m.allocateInstr() + ext.asVecExtract(v0.nr(), v1, v1, vecArrangement16B, uint32(8)) + m.insert(ext) + + // v = [w0, w8, ..., w7, w15] + zip1 := m.allocateInstr() + zip1.asVecPermute(vecOpZip1, v0.nr(), v1, v0, vecArrangement16B) + m.insert(zip1) + + // v.h[0] = w0 + ... + w15 + addv := m.allocateInstr() + addv.asVecLanes(vecOpAddv, v0.nr(), v0, vecArrangement8H) + m.insert(addv) + + // Extract the v.h[0] as the result. + movfv := m.allocateInstr() + movfv.asMovFromVec(rd, v0, vecArrangementH, vecIndex(0), false) + m.insert(movfv) + case vecArrangement8H: + // sshr v6?.8h, v2?.8h, #15 + // movz x4?, #0x1, lsl 0 + // movk x4?, #0x2, lsl 16 + // movk x4?, #0x4, lsl 32 + // movk x4?, #0x8, lsl 48 + // dup v5?.2d, x4? + // lsl x4?, x4?, 0x4 + // ins v5?.d[1], x4? + // and v5?.16b, v6?.16b, v5?.16b + // addv s5?, v5?.8h + // umov s3?, v5?.h[0] + + // Right arithmetic shift on the original vector and store the result into v1. So we have: + // v[i] = 0xffff if vi<0, 0 otherwise. + sshr := m.allocateInstr() + sshr.asVecShiftImm(vecOpSshr, v1.nr(), rm, operandShiftImm(15), vecArrangement8H) + m.insert(sshr) + + // Load the bit mask into r0. + m.lowerConstantI64(r0.nr(), 0x0008000400020001) + + // dup r0 to vector v0. + dup := m.allocateInstr() + dup.asVecDup(v0.nr(), r0, vecArrangement2D) + m.insert(dup) + + lsl := m.allocateInstr() + lsl.asALUShift(aluOpLsl, r0.nr(), r0, operandShiftImm(4), true) + m.insert(lsl) + + movv := m.allocateInstr() + movv.asMovToVec(v0.nr(), r0, vecArrangementD, vecIndex(1)) + m.insert(movv) + + // Lane-wise logical AND with the bitmask, meaning that we have + // v[i] = (1 << i) if vi<0, 0 otherwise for i=0..3 + // = (1 << (i+4)) if vi<0, 0 otherwise for i=3..7 + and := m.allocateInstr() + and.asVecRRR(vecOpAnd, v0.nr(), v1, v0, vecArrangement16B) + m.insert(and) + + addv := m.allocateInstr() + addv.asVecLanes(vecOpAddv, v0.nr(), v0, vecArrangement8H) + m.insert(addv) + + movfv := m.allocateInstr() + movfv.asMovFromVec(rd, v0, vecArrangementH, vecIndex(0), false) + m.insert(movfv) + case vecArrangement4S: + // sshr v6?.8h, v2?.8h, #15 + // movz x4?, #0x1, lsl 0 + // movk x4?, #0x2, lsl 16 + // movk x4?, #0x4, lsl 32 + // movk x4?, #0x8, lsl 48 + // dup v5?.2d, x4? + // lsl x4?, x4?, 0x4 + // ins v5?.d[1], x4? + // and v5?.16b, v6?.16b, v5?.16b + // addv s5?, v5?.8h + // umov s3?, v5?.h[0] + + // Right arithmetic shift on the original vector and store the result into v1. So we have: + // v[i] = 0xffffffff if vi<0, 0 otherwise. + sshr := m.allocateInstr() + sshr.asVecShiftImm(vecOpSshr, v1.nr(), rm, operandShiftImm(31), vecArrangement4S) + m.insert(sshr) + + // Load the bit mask into r0. + m.lowerConstantI64(r0.nr(), 0x0000000200000001) + + // dup r0 to vector v0. + dup := m.allocateInstr() + dup.asVecDup(v0.nr(), r0, vecArrangement2D) + m.insert(dup) + + lsl := m.allocateInstr() + lsl.asALUShift(aluOpLsl, r0.nr(), r0, operandShiftImm(2), true) + m.insert(lsl) + + movv := m.allocateInstr() + movv.asMovToVec(v0.nr(), r0, vecArrangementD, vecIndex(1)) + m.insert(movv) + + // Lane-wise logical AND with the bitmask, meaning that we have + // v[i] = (1 << i) if vi<0, 0 otherwise for i in [0, 1] + // = (1 << (i+4)) if vi<0, 0 otherwise for i in [2, 3] + and := m.allocateInstr() + and.asVecRRR(vecOpAnd, v0.nr(), v1, v0, vecArrangement16B) + m.insert(and) + + addv := m.allocateInstr() + addv.asVecLanes(vecOpAddv, v0.nr(), v0, vecArrangement4S) + m.insert(addv) + + movfv := m.allocateInstr() + movfv.asMovFromVec(rd, v0, vecArrangementS, vecIndex(0), false) + m.insert(movfv) + case vecArrangement2D: + // mov d3?, v2?.d[0] + // mov x4?, v2?.d[1] + // lsr x4?, x4?, 0x3f + // lsr d3?, d3?, 0x3f + // add s3?, s3?, w4?, lsl #1 + + // Move the lower 64-bit int into result. + movv0 := m.allocateInstr() + movv0.asMovFromVec(rd, rm, vecArrangementD, vecIndex(0), false) + m.insert(movv0) + + // Move the higher 64-bit int into r0. + movv1 := m.allocateInstr() + movv1.asMovFromVec(r0.nr(), rm, vecArrangementD, vecIndex(1), false) + m.insert(movv1) + + // Move the sign bit into the least significant bit. + lsr1 := m.allocateInstr() + lsr1.asALUShift(aluOpLsr, r0.nr(), r0, operandShiftImm(63), true) + m.insert(lsr1) + + lsr2 := m.allocateInstr() + lsr2.asALUShift(aluOpLsr, rd, operandNR(rd), operandShiftImm(63), true) + m.insert(lsr2) + + // rd = (r0<<1) | rd + lsl := m.allocateInstr() + lsl.asALU(aluOpAdd, rd, operandNR(rd), operandSR(r0.nr(), 1, shiftOpLSL), false) + m.insert(lsl) + default: + panic("Unsupported " + arr.String()) + } +} + +func (m *machine) lowerVecMisc(op vecOp, instr *ssa.Instruction) { + x, lane := instr.ArgWithLane() + arr := ssaLaneToArrangement(lane) + ins := m.allocateInstr() + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + ins.asVecMisc(op, rd, rn, arr) + m.insert(ins) +} + +func (m *machine) lowerVecRRR(op vecOp, x, y, ret ssa.Value, arr vecArrangement) { + ins := m.allocateInstr() + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + rd := m.compiler.VRegOf(ret) + ins.asVecRRR(op, rd, rn, rm, arr) + m.insert(ins) +} + +func (m *machine) lowerVIMul(rd regalloc.VReg, rn, rm operand, arr vecArrangement) { + if arr != vecArrangement2D { + mul := m.allocateInstr() + mul.asVecRRR(vecOpMul, rd, rn, rm, arr) + m.insert(mul) + } else { + tmp1 := m.compiler.AllocateVReg(ssa.TypeV128) + tmp2 := m.compiler.AllocateVReg(ssa.TypeV128) + tmp3 := m.compiler.AllocateVReg(ssa.TypeV128) + + tmpRes := m.compiler.AllocateVReg(ssa.TypeV128) + + // Following the algorithm in https://chromium-review.googlesource.com/c/v8/v8/+/1781696 + rev64 := m.allocateInstr() + rev64.asVecMisc(vecOpRev64, tmp2, rm, vecArrangement4S) + m.insert(rev64) + + mul := m.allocateInstr() + mul.asVecRRR(vecOpMul, tmp2, operandNR(tmp2), rn, vecArrangement4S) + m.insert(mul) + + xtn1 := m.allocateInstr() + xtn1.asVecMisc(vecOpXtn, tmp1, rn, vecArrangement2S) + m.insert(xtn1) + + addp := m.allocateInstr() + addp.asVecRRR(vecOpAddp, tmp2, operandNR(tmp2), operandNR(tmp2), vecArrangement4S) + m.insert(addp) + + xtn2 := m.allocateInstr() + xtn2.asVecMisc(vecOpXtn, tmp3, rm, vecArrangement2S) + m.insert(xtn2) + + // Note: do not write the result directly into result yet. This is the same reason as in bsl. + // In short, in UMLAL instruction, the result register is also one of the source register, and + // the value on the result register is significant. + shll := m.allocateInstr() + shll.asVecMisc(vecOpShll, tmpRes, operandNR(tmp2), vecArrangement2S) + m.insert(shll) + + umlal := m.allocateInstr() + umlal.asVecRRRRewrite(vecOpUmlal, tmpRes, operandNR(tmp3), operandNR(tmp1), vecArrangement2S) + m.insert(umlal) + + mov := m.allocateInstr() + mov.asFpuMov128(rd, tmpRes) + m.insert(mov) + } +} + +func (m *machine) lowerVMinMaxPseudo(instr *ssa.Instruction, max bool) { + x, y, lane := instr.Arg2WithLane() + arr := ssaLaneToArrangement(lane) + + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + + // Note: this usage of tmp is important. + // BSL modifies the destination register, so we need to use a temporary register so that + // the actual definition of the destination register happens *after* the BSL instruction. + // That way, we can force the spill instruction to be inserted after the BSL instruction. + tmp := m.compiler.AllocateVReg(ssa.TypeV128) + + fcmgt := m.allocateInstr() + if max { + fcmgt.asVecRRR(vecOpFcmgt, tmp, rm, rn, arr) + } else { + // If min, swap the args. + fcmgt.asVecRRR(vecOpFcmgt, tmp, rn, rm, arr) + } + m.insert(fcmgt) + + bsl := m.allocateInstr() + bsl.asVecRRRRewrite(vecOpBsl, tmp, rm, rn, vecArrangement16B) + m.insert(bsl) + + res := operandNR(m.compiler.VRegOf(instr.Return())) + mov2 := m.allocateInstr() + mov2.asFpuMov128(res.nr(), tmp) + m.insert(mov2) +} + +func (m *machine) lowerIRem(execCtxVReg regalloc.VReg, rd, rn regalloc.VReg, rm operand, _64bit, signed bool) { + div := m.allocateInstr() + + if signed { + div.asALU(aluOpSDiv, rd, operandNR(rn), rm, _64bit) + } else { + div.asALU(aluOpUDiv, rd, operandNR(rn), rm, _64bit) + } + m.insert(div) + + // Check if rm is zero: + m.exitIfNot(execCtxVReg, registerAsRegNotZeroCond(rm.nr()), _64bit, wazevoapi.ExitCodeIntegerDivisionByZero) + + // rd = rn-rd*rm by MSUB instruction. + msub := m.allocateInstr() + msub.asALURRRR(aluOpMSub, rd, operandNR(rd), rm, rn, _64bit) + m.insert(msub) +} + +func (m *machine) lowerIDiv(execCtxVReg, rd regalloc.VReg, rn, rm operand, _64bit, signed bool) { + div := m.allocateInstr() + + if signed { + div.asALU(aluOpSDiv, rd, rn, rm, _64bit) + } else { + div.asALU(aluOpUDiv, rd, rn, rm, _64bit) + } + m.insert(div) + + // Check if rm is zero: + m.exitIfNot(execCtxVReg, registerAsRegNotZeroCond(rm.nr()), _64bit, wazevoapi.ExitCodeIntegerDivisionByZero) + + if signed { + // We need to check the signed overflow which happens iff "math.MinInt{32,64} / -1" + minusOneCheck := m.allocateInstr() + // Sets eq condition if rm == -1. + minusOneCheck.asALU(aluOpAddS, xzrVReg, rm, operandImm12(1, 0), _64bit) + m.insert(minusOneCheck) + + ccmp := m.allocateInstr() + // If eq condition is set, sets the flag by the result based on "rn - 1", otherwise clears the flag. + ccmp.asCCmpImm(rn, 1, eq, 0, _64bit) + m.insert(ccmp) + + // Check the overflow flag. + m.exitIfNot(execCtxVReg, vs.invert().asCond(), false, wazevoapi.ExitCodeIntegerOverflow) + } +} + +// exitIfNot emits a conditional branch to exit if the condition is not met. +// If `c` (cond type) is a register, `cond64bit` must be chosen to indicate whether the register is 32-bit or 64-bit. +// Otherwise, `cond64bit` is ignored. +func (m *machine) exitIfNot(execCtxVReg regalloc.VReg, c cond, cond64bit bool, code wazevoapi.ExitCode) { + execCtxTmp := m.copyToTmp(execCtxVReg) + + cbr := m.allocateInstr() + m.insert(cbr) + m.lowerExitWithCode(execCtxTmp, code) + // Conditional branch target is after exit. + l := m.insertBrTargetLabel() + cbr.asCondBr(c, l, cond64bit) +} + +func (m *machine) lowerFcopysign(x, y, ret ssa.Value) { + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + var tmpI, tmpF regalloc.VReg + _64 := x.Type() == ssa.TypeF64 + if _64 { + tmpF = m.compiler.AllocateVReg(ssa.TypeF64) + tmpI = m.compiler.AllocateVReg(ssa.TypeI64) + } else { + tmpF = m.compiler.AllocateVReg(ssa.TypeF32) + tmpI = m.compiler.AllocateVReg(ssa.TypeI32) + } + rd := m.compiler.VRegOf(ret) + m.lowerFcopysignImpl(rd, rn, rm, tmpI, tmpF, _64) +} + +func (m *machine) lowerFcopysignImpl(rd regalloc.VReg, rn, rm operand, tmpI, tmpF regalloc.VReg, _64bit bool) { + // This is exactly the same code emitted by GCC for "__builtin_copysign": + // + // mov x0, -9223372036854775808 + // fmov d2, x0 + // vbit v0.8b, v1.8b, v2.8b + // + + setMSB := m.allocateInstr() + if _64bit { + m.lowerConstantI64(tmpI, math.MinInt64) + setMSB.asMovToVec(tmpF, operandNR(tmpI), vecArrangementD, vecIndex(0)) + } else { + m.lowerConstantI32(tmpI, math.MinInt32) + setMSB.asMovToVec(tmpF, operandNR(tmpI), vecArrangementS, vecIndex(0)) + } + m.insert(setMSB) + + tmpReg := m.compiler.AllocateVReg(ssa.TypeF64) + + mov := m.allocateInstr() + mov.asFpuMov64(tmpReg, rn.nr()) + m.insert(mov) + + vbit := m.allocateInstr() + vbit.asVecRRRRewrite(vecOpBit, tmpReg, rm, operandNR(tmpF), vecArrangement8B) + m.insert(vbit) + + movDst := m.allocateInstr() + movDst.asFpuMov64(rd, tmpReg) + m.insert(movDst) +} + +func (m *machine) lowerBitcast(instr *ssa.Instruction) { + v, dstType := instr.BitcastData() + srcType := v.Type() + rn := m.getOperand_NR(m.compiler.ValueDefinition(v), extModeNone) + rd := m.compiler.VRegOf(instr.Return()) + srcInt := srcType.IsInt() + dstInt := dstType.IsInt() + switch { + case srcInt && !dstInt: // Int to Float: + mov := m.allocateInstr() + var arr vecArrangement + if srcType.Bits() == 64 { + arr = vecArrangementD + } else { + arr = vecArrangementS + } + mov.asMovToVec(rd, rn, arr, vecIndex(0)) + m.insert(mov) + case !srcInt && dstInt: // Float to Int: + mov := m.allocateInstr() + var arr vecArrangement + if dstType.Bits() == 64 { + arr = vecArrangementD + } else { + arr = vecArrangementS + } + mov.asMovFromVec(rd, rn, arr, vecIndex(0), false) + m.insert(mov) + default: + panic("TODO?BUG?") + } +} + +func (m *machine) lowerFpuUniOp(op fpuUniOp, in, out ssa.Value) { + rn := m.getOperand_NR(m.compiler.ValueDefinition(in), extModeNone) + rd := m.compiler.VRegOf(out) + + neg := m.allocateInstr() + neg.asFpuRR(op, rd, rn, in.Type().Bits() == 64) + m.insert(neg) +} + +func (m *machine) lowerFpuToInt(rd regalloc.VReg, rn operand, ctx regalloc.VReg, signed, src64bit, dst64bit, nonTrapping bool) { + if !nonTrapping { + // First of all, we have to clear the FPU flags. + flagClear := m.allocateInstr() + flagClear.asMovToFPSR(xzrVReg) + m.insert(flagClear) + } + + // Then, do the conversion which doesn't trap inherently. + cvt := m.allocateInstr() + cvt.asFpuToInt(rd, rn, signed, src64bit, dst64bit) + m.insert(cvt) + + if !nonTrapping { + tmpReg := m.compiler.AllocateVReg(ssa.TypeI64) + + // After the conversion, check the FPU flags. + getFlag := m.allocateInstr() + getFlag.asMovFromFPSR(tmpReg) + m.insert(getFlag) + + execCtx := m.copyToTmp(ctx) + _rn := operandNR(m.copyToTmp(rn.nr())) + + // Check if the conversion was undefined by comparing the status with 1. + // See https://developer.arm.com/documentation/ddi0595/2020-12/AArch64-Registers/FPSR--Floating-point-Status-Register + alu := m.allocateInstr() + alu.asALU(aluOpSubS, xzrVReg, operandNR(tmpReg), operandImm12(1, 0), true) + m.insert(alu) + + // If it is not undefined, we can return the result. + ok := m.allocateInstr() + m.insert(ok) + + // Otherwise, we have to choose the status depending on it is overflow or NaN conversion. + + // Comparing itself to check if it is a NaN. + fpuCmp := m.allocateInstr() + fpuCmp.asFpuCmp(_rn, _rn, src64bit) + m.insert(fpuCmp) + // If the VC flag is not set (== VS flag is set), it is a NaN. + m.exitIfNot(execCtx, vc.asCond(), false, wazevoapi.ExitCodeInvalidConversionToInteger) + // Otherwise, it is an overflow. + m.lowerExitWithCode(execCtx, wazevoapi.ExitCodeIntegerOverflow) + + // Conditional branch target is after exit. + l := m.insertBrTargetLabel() + ok.asCondBr(ne.asCond(), l, false /* ignored */) + } +} + +func (m *machine) lowerIntToFpu(rd regalloc.VReg, rn operand, signed, src64bit, dst64bit bool) { + cvt := m.allocateInstr() + cvt.asIntToFpu(rd, rn, signed, src64bit, dst64bit) + m.insert(cvt) +} + +func (m *machine) lowerFpuBinOp(si *ssa.Instruction) { + instr := m.allocateInstr() + var op fpuBinOp + switch si.Opcode() { + case ssa.OpcodeFadd: + op = fpuBinOpAdd + case ssa.OpcodeFsub: + op = fpuBinOpSub + case ssa.OpcodeFmul: + op = fpuBinOpMul + case ssa.OpcodeFdiv: + op = fpuBinOpDiv + case ssa.OpcodeFmax: + op = fpuBinOpMax + case ssa.OpcodeFmin: + op = fpuBinOpMin + } + x, y := si.Arg2() + xDef, yDef := m.compiler.ValueDefinition(x), m.compiler.ValueDefinition(y) + rn := m.getOperand_NR(xDef, extModeNone) + rm := m.getOperand_NR(yDef, extModeNone) + rd := m.compiler.VRegOf(si.Return()) + instr.asFpuRRR(op, rd, rn, rm, x.Type().Bits() == 64) + m.insert(instr) +} + +func (m *machine) lowerSubOrAdd(si *ssa.Instruction, add bool) { + x, y := si.Arg2() + if !x.Type().IsInt() { + panic("BUG?") + } + + xDef, yDef := m.compiler.ValueDefinition(x), m.compiler.ValueDefinition(y) + rn := m.getOperand_NR(xDef, extModeNone) + rm, yNegated := m.getOperand_MaybeNegatedImm12_ER_SR_NR(yDef, extModeNone) + + var aop aluOp + switch { + case add && !yNegated: // rn+rm = x+y + aop = aluOpAdd + case add && yNegated: // rn-rm = x-(-y) = x+y + aop = aluOpSub + case !add && !yNegated: // rn-rm = x-y + aop = aluOpSub + case !add && yNegated: // rn+rm = x-(-y) = x-y + aop = aluOpAdd + } + rd := m.compiler.VRegOf(si.Return()) + alu := m.allocateInstr() + alu.asALU(aop, rd, rn, rm, x.Type().Bits() == 64) + m.insert(alu) +} + +// InsertMove implements backend.Machine. +func (m *machine) InsertMove(dst, src regalloc.VReg, typ ssa.Type) { + instr := m.allocateInstr() + switch typ { + case ssa.TypeI32, ssa.TypeI64: + instr.asMove64(dst, src) + case ssa.TypeF32, ssa.TypeF64: + instr.asFpuMov64(dst, src) + case ssa.TypeV128: + instr.asFpuMov128(dst, src) + default: + panic("TODO") + } + m.insert(instr) +} + +func (m *machine) lowerIcmp(si *ssa.Instruction) { + x, y, c := si.IcmpData() + flag := condFlagFromSSAIntegerCmpCond(c) + + in64bit := x.Type().Bits() == 64 + var ext extMode + if in64bit { + if c.Signed() { + ext = extModeSignExtend64 + } else { + ext = extModeZeroExtend64 + } + } else { + if c.Signed() { + ext = extModeSignExtend32 + } else { + ext = extModeZeroExtend32 + } + } + + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), ext) + rm := m.getOperand_Imm12_ER_SR_NR(m.compiler.ValueDefinition(y), ext) + alu := m.allocateInstr() + alu.asALU(aluOpSubS, xzrVReg, rn, rm, in64bit) + m.insert(alu) + + cset := m.allocateInstr() + cset.asCSet(m.compiler.VRegOf(si.Return()), false, flag) + m.insert(cset) +} + +func (m *machine) lowerVIcmp(si *ssa.Instruction) { + x, y, c, lane := si.VIcmpData() + flag := condFlagFromSSAIntegerCmpCond(c) + arr := ssaLaneToArrangement(lane) + + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + rd := m.compiler.VRegOf(si.Return()) + + switch flag { + case eq: + cmp := m.allocateInstr() + cmp.asVecRRR(vecOpCmeq, rd, rn, rm, arr) + m.insert(cmp) + case ne: + cmp := m.allocateInstr() + cmp.asVecRRR(vecOpCmeq, rd, rn, rm, arr) + m.insert(cmp) + not := m.allocateInstr() + not.asVecMisc(vecOpNot, rd, operandNR(rd), vecArrangement16B) + m.insert(not) + case ge: + cmp := m.allocateInstr() + cmp.asVecRRR(vecOpCmge, rd, rn, rm, arr) + m.insert(cmp) + case gt: + cmp := m.allocateInstr() + cmp.asVecRRR(vecOpCmgt, rd, rn, rm, arr) + m.insert(cmp) + case le: + cmp := m.allocateInstr() + cmp.asVecRRR(vecOpCmge, rd, rm, rn, arr) // rm, rn are swapped + m.insert(cmp) + case lt: + cmp := m.allocateInstr() + cmp.asVecRRR(vecOpCmgt, rd, rm, rn, arr) // rm, rn are swapped + m.insert(cmp) + case hs: + cmp := m.allocateInstr() + cmp.asVecRRR(vecOpCmhs, rd, rn, rm, arr) + m.insert(cmp) + case hi: + cmp := m.allocateInstr() + cmp.asVecRRR(vecOpCmhi, rd, rn, rm, arr) + m.insert(cmp) + case ls: + cmp := m.allocateInstr() + cmp.asVecRRR(vecOpCmhs, rd, rm, rn, arr) // rm, rn are swapped + m.insert(cmp) + case lo: + cmp := m.allocateInstr() + cmp.asVecRRR(vecOpCmhi, rd, rm, rn, arr) // rm, rn are swapped + m.insert(cmp) + } +} + +func (m *machine) lowerVFcmp(si *ssa.Instruction) { + x, y, c, lane := si.VFcmpData() + flag := condFlagFromSSAFloatCmpCond(c) + arr := ssaLaneToArrangement(lane) + + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + rd := m.compiler.VRegOf(si.Return()) + + switch flag { + case eq: + cmp := m.allocateInstr() + cmp.asVecRRR(vecOpFcmeq, rd, rn, rm, arr) + m.insert(cmp) + case ne: + cmp := m.allocateInstr() + cmp.asVecRRR(vecOpFcmeq, rd, rn, rm, arr) + m.insert(cmp) + not := m.allocateInstr() + not.asVecMisc(vecOpNot, rd, operandNR(rd), vecArrangement16B) + m.insert(not) + case ge: + cmp := m.allocateInstr() + cmp.asVecRRR(vecOpFcmge, rd, rn, rm, arr) + m.insert(cmp) + case gt: + cmp := m.allocateInstr() + cmp.asVecRRR(vecOpFcmgt, rd, rn, rm, arr) + m.insert(cmp) + case mi: + cmp := m.allocateInstr() + cmp.asVecRRR(vecOpFcmgt, rd, rm, rn, arr) // rm, rn are swapped + m.insert(cmp) + case ls: + cmp := m.allocateInstr() + cmp.asVecRRR(vecOpFcmge, rd, rm, rn, arr) // rm, rn are swapped + m.insert(cmp) + } +} + +func (m *machine) lowerVfpuToInt(rd regalloc.VReg, rn operand, arr vecArrangement, signed bool) { + cvt := m.allocateInstr() + if signed { + cvt.asVecMisc(vecOpFcvtzs, rd, rn, arr) + } else { + cvt.asVecMisc(vecOpFcvtzu, rd, rn, arr) + } + m.insert(cvt) + + if arr == vecArrangement2D { + narrow := m.allocateInstr() + if signed { + narrow.asVecMisc(vecOpSqxtn, rd, operandNR(rd), vecArrangement2S) + } else { + narrow.asVecMisc(vecOpUqxtn, rd, operandNR(rd), vecArrangement2S) + } + m.insert(narrow) + } +} + +func (m *machine) lowerVfpuFromInt(rd regalloc.VReg, rn operand, arr vecArrangement, signed bool) { + cvt := m.allocateInstr() + if signed { + cvt.asVecMisc(vecOpScvtf, rd, rn, arr) + } else { + cvt.asVecMisc(vecOpUcvtf, rd, rn, arr) + } + m.insert(cvt) +} + +func (m *machine) lowerShifts(si *ssa.Instruction, ext extMode, aluOp aluOp) { + x, amount := si.Arg2() + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), ext) + rm := m.getOperand_ShiftImm_NR(m.compiler.ValueDefinition(amount), ext, x.Type().Bits()) + rd := m.compiler.VRegOf(si.Return()) + + alu := m.allocateInstr() + alu.asALUShift(aluOp, rd, rn, rm, x.Type().Bits() == 64) + m.insert(alu) +} + +func (m *machine) lowerBitwiseAluOp(si *ssa.Instruction, op aluOp, ignoreResult bool) { + x, y := si.Arg2() + + xDef, yDef := m.compiler.ValueDefinition(x), m.compiler.ValueDefinition(y) + rn := m.getOperand_NR(xDef, extModeNone) + + var rd regalloc.VReg + if ignoreResult { + rd = xzrVReg + } else { + rd = m.compiler.VRegOf(si.Return()) + } + + _64 := x.Type().Bits() == 64 + alu := m.allocateInstr() + if instr := yDef.Instr; instr != nil && instr.Constant() { + c := instr.ConstantVal() + if isBitMaskImmediate(c, _64) { + // Constant bit wise operations can be lowered to a single instruction. + alu.asALUBitmaskImm(op, rd, rn.nr(), c, _64) + m.insert(alu) + return + } + } + + rm := m.getOperand_SR_NR(yDef, extModeNone) + alu.asALU(op, rd, rn, rm, _64) + m.insert(alu) +} + +func (m *machine) lowerRotl(si *ssa.Instruction) { + x, y := si.Arg2() + r := si.Return() + _64 := r.Type().Bits() == 64 + + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + var tmp regalloc.VReg + if _64 { + tmp = m.compiler.AllocateVReg(ssa.TypeI64) + } else { + tmp = m.compiler.AllocateVReg(ssa.TypeI32) + } + rd := m.compiler.VRegOf(r) + + // Encode rotl as neg + rotr: neg is a sub against the zero-reg. + m.lowerRotlImpl(rd, rn, rm, tmp, _64) +} + +func (m *machine) lowerRotlImpl(rd regalloc.VReg, rn, rm operand, tmp regalloc.VReg, is64bit bool) { + // Encode rotl as neg + rotr: neg is a sub against the zero-reg. + neg := m.allocateInstr() + neg.asALU(aluOpSub, tmp, operandNR(xzrVReg), rm, is64bit) + m.insert(neg) + alu := m.allocateInstr() + alu.asALU(aluOpRotR, rd, rn, operandNR(tmp), is64bit) + m.insert(alu) +} + +func (m *machine) lowerRotr(si *ssa.Instruction) { + x, y := si.Arg2() + + xDef, yDef := m.compiler.ValueDefinition(x), m.compiler.ValueDefinition(y) + rn := m.getOperand_NR(xDef, extModeNone) + rm := m.getOperand_NR(yDef, extModeNone) + rd := m.compiler.VRegOf(si.Return()) + + alu := m.allocateInstr() + alu.asALU(aluOpRotR, rd, rn, rm, si.Return().Type().Bits() == 64) + m.insert(alu) +} + +func (m *machine) lowerExtend(arg, ret ssa.Value, from, to byte, signed bool) { + rd := m.compiler.VRegOf(ret) + def := m.compiler.ValueDefinition(arg) + + if instr := def.Instr; !signed && from == 32 && instr != nil { + // We can optimize out the unsigned extend because: + // Writes to the W register set bits [63:32] of the X register to zero + // https://developer.arm.com/documentation/den0024/a/An-Introduction-to-the-ARMv8-Instruction-Sets/The-ARMv8-instruction-sets/Distinguishing-between-32-bit-and-64-bit-A64-instructions + switch instr.Opcode() { + case + ssa.OpcodeIadd, ssa.OpcodeIsub, ssa.OpcodeLoad, + ssa.OpcodeBand, ssa.OpcodeBor, ssa.OpcodeBnot, + ssa.OpcodeIshl, ssa.OpcodeUshr, ssa.OpcodeSshr, + ssa.OpcodeRotl, ssa.OpcodeRotr, + ssa.OpcodeUload8, ssa.OpcodeUload16, ssa.OpcodeUload32: + // So, if the argument is the result of a 32-bit operation, we can just copy the register. + // It is highly likely that this copy will be optimized out after register allocation. + rn := m.compiler.VRegOf(arg) + mov := m.allocateInstr() + // Note: do not use move32 as it will be lowered to a 32-bit move, which is not copy (that is actually the impl of UExtend). + mov.asMove64(rd, rn) + m.insert(mov) + return + default: + } + } + rn := m.getOperand_NR(def, extModeNone) + + ext := m.allocateInstr() + ext.asExtend(rd, rn.nr(), from, to, signed) + m.insert(ext) +} + +func (m *machine) lowerFcmp(x, y, result ssa.Value, c ssa.FloatCmpCond) { + rn, rm := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone), m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + + fc := m.allocateInstr() + fc.asFpuCmp(rn, rm, x.Type().Bits() == 64) + m.insert(fc) + + cset := m.allocateInstr() + cset.asCSet(m.compiler.VRegOf(result), false, condFlagFromSSAFloatCmpCond(c)) + m.insert(cset) +} + +func (m *machine) lowerImul(x, y, result ssa.Value) { + rd := m.compiler.VRegOf(result) + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + + // TODO: if this comes before Add/Sub, we could merge it by putting it into the place of xzrVReg. + + mul := m.allocateInstr() + mul.asALURRRR(aluOpMAdd, rd, rn, rm, xzrVReg, x.Type().Bits() == 64) + m.insert(mul) +} + +func (m *machine) lowerClz(x, result ssa.Value) { + rd := m.compiler.VRegOf(result) + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + clz := m.allocateInstr() + clz.asBitRR(bitOpClz, rd, rn.nr(), x.Type().Bits() == 64) + m.insert(clz) +} + +func (m *machine) lowerCtz(x, result ssa.Value) { + rd := m.compiler.VRegOf(result) + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rbit := m.allocateInstr() + _64 := x.Type().Bits() == 64 + var tmpReg regalloc.VReg + if _64 { + tmpReg = m.compiler.AllocateVReg(ssa.TypeI64) + } else { + tmpReg = m.compiler.AllocateVReg(ssa.TypeI32) + } + rbit.asBitRR(bitOpRbit, tmpReg, rn.nr(), _64) + m.insert(rbit) + + clz := m.allocateInstr() + clz.asBitRR(bitOpClz, rd, tmpReg, _64) + m.insert(clz) +} + +func (m *machine) lowerPopcnt(x, result ssa.Value) { + // arm64 doesn't have an instruction for population count on scalar register, + // so we use the vector instruction `cnt`. + // This is exactly what the official Go implements bits.OneCount. + // For example, "func () int { return bits.OneCount(10) }" is compiled as + // + // MOVD $10, R0 ;; Load 10. + // FMOVD R0, F0 + // VCNT V0.B8, V0.B8 + // UADDLV V0.B8, V0 + // + // In aarch64 asm, FMOVD is encoded as `ins`, VCNT is `cnt`, + // and the registers may use different names. In our encoding we use the following + // instructions: + // + // ins v0.d[0], x0 ;; mov from GPR to vec (FMOV above) is encoded as INS + // cnt v0.16b, v0.16b ;; we use vec arrangement 16b + // uaddlv h0, v0.8b ;; h0 is still v0 with the dest width specifier 'H', implied when src arrangement is 8b + // mov x5, v0.d[0] ;; finally we mov the result back to a GPR + // + + rd := m.compiler.VRegOf(result) + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + + rf1 := operandNR(m.compiler.AllocateVReg(ssa.TypeF64)) + ins := m.allocateInstr() + ins.asMovToVec(rf1.nr(), rn, vecArrangementD, vecIndex(0)) + m.insert(ins) + + rf2 := operandNR(m.compiler.AllocateVReg(ssa.TypeF64)) + cnt := m.allocateInstr() + cnt.asVecMisc(vecOpCnt, rf2.nr(), rf1, vecArrangement16B) + m.insert(cnt) + + rf3 := operandNR(m.compiler.AllocateVReg(ssa.TypeF64)) + uaddlv := m.allocateInstr() + uaddlv.asVecLanes(vecOpUaddlv, rf3.nr(), rf2, vecArrangement8B) + m.insert(uaddlv) + + mov := m.allocateInstr() + mov.asMovFromVec(rd, rf3, vecArrangementD, vecIndex(0), false) + m.insert(mov) +} + +// lowerExitWithCode lowers the lowerExitWithCode takes a context pointer as argument. +func (m *machine) lowerExitWithCode(execCtxVReg regalloc.VReg, code wazevoapi.ExitCode) { + tmpReg1 := m.compiler.AllocateVReg(ssa.TypeI32) + loadExitCodeConst := m.allocateInstr() + loadExitCodeConst.asMOVZ(tmpReg1, uint64(code), 0, true) + + setExitCode := m.allocateInstr() + mode := m.amodePool.Allocate() + *mode = addressMode{ + kind: addressModeKindRegUnsignedImm12, + rn: execCtxVReg, imm: wazevoapi.ExecutionContextOffsetExitCodeOffset.I64(), + } + setExitCode.asStore(operandNR(tmpReg1), mode, 32) + + // In order to unwind the stack, we also need to push the current stack pointer: + tmp2 := m.compiler.AllocateVReg(ssa.TypeI64) + movSpToTmp := m.allocateInstr() + movSpToTmp.asMove64(tmp2, spVReg) + strSpToExecCtx := m.allocateInstr() + mode2 := m.amodePool.Allocate() + *mode2 = addressMode{ + kind: addressModeKindRegUnsignedImm12, + rn: execCtxVReg, imm: wazevoapi.ExecutionContextOffsetStackPointerBeforeGoCall.I64(), + } + strSpToExecCtx.asStore(operandNR(tmp2), mode2, 64) + // Also the address of this exit. + tmp3 := m.compiler.AllocateVReg(ssa.TypeI64) + currentAddrToTmp := m.allocateInstr() + currentAddrToTmp.asAdr(tmp3, 0) + storeCurrentAddrToExecCtx := m.allocateInstr() + mode3 := m.amodePool.Allocate() + *mode3 = addressMode{ + kind: addressModeKindRegUnsignedImm12, + rn: execCtxVReg, imm: wazevoapi.ExecutionContextOffsetGoCallReturnAddress.I64(), + } + storeCurrentAddrToExecCtx.asStore(operandNR(tmp3), mode3, 64) + + exitSeq := m.allocateInstr() + exitSeq.asExitSequence(execCtxVReg) + + m.insert(loadExitCodeConst) + m.insert(setExitCode) + m.insert(movSpToTmp) + m.insert(strSpToExecCtx) + m.insert(currentAddrToTmp) + m.insert(storeCurrentAddrToExecCtx) + m.insert(exitSeq) +} + +func (m *machine) lowerIcmpToFlag(x, y ssa.Value, signed bool) { + if x.Type() != y.Type() { + panic( + fmt.Sprintf("TODO(maybe): support icmp with different types: v%d=%s != v%d=%s", + x.ID(), x.Type(), y.ID(), y.Type())) + } + + extMod := extModeOf(x.Type(), signed) + + // First operand must be in pure register form. + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extMod) + // Second operand can be in any of Imm12, ER, SR, or NR form supported by the SUBS instructions. + rm := m.getOperand_Imm12_ER_SR_NR(m.compiler.ValueDefinition(y), extMod) + + alu := m.allocateInstr() + // subs zr, rn, rm + alu.asALU( + aluOpSubS, + // We don't need the result, just need to set flags. + xzrVReg, + rn, + rm, + x.Type().Bits() == 64, + ) + m.insert(alu) +} + +func (m *machine) lowerFcmpToFlag(x, y ssa.Value) { + if x.Type() != y.Type() { + panic("TODO(maybe): support icmp with different types") + } + + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + cmp := m.allocateInstr() + cmp.asFpuCmp(rn, rm, x.Type().Bits() == 64) + m.insert(cmp) +} + +func (m *machine) lowerExitIfTrueWithCode(execCtxVReg regalloc.VReg, cond ssa.Value, code wazevoapi.ExitCode) { + condDef := m.compiler.ValueDefinition(cond) + if !m.compiler.MatchInstr(condDef, ssa.OpcodeIcmp) { + panic("TODO: OpcodeExitIfTrueWithCode must come after Icmp at the moment: " + condDef.Instr.Opcode().String()) + } + condDef.Instr.MarkLowered() + + cvalInstr := condDef.Instr + x, y, c := cvalInstr.IcmpData() + signed := c.Signed() + + if !m.tryLowerBandToFlag(x, y) { + m.lowerIcmpToFlag(x, y, signed) + } + + // We need to copy the execution context to a temp register, because if it's spilled, + // it might end up being reloaded inside the exiting branch. + execCtxTmp := m.copyToTmp(execCtxVReg) + + // We have to skip the entire exit sequence if the condition is false. + cbr := m.allocateInstr() + m.insert(cbr) + m.lowerExitWithCode(execCtxTmp, code) + // conditional branch target is after exit. + l := m.insertBrTargetLabel() + cbr.asCondBr(condFlagFromSSAIntegerCmpCond(c).invert().asCond(), l, false /* ignored */) +} + +func (m *machine) lowerSelect(c, x, y, result ssa.Value) { + cvalDef := m.compiler.ValueDefinition(c) + + var cc condFlag + switch { + case m.compiler.MatchInstr(cvalDef, ssa.OpcodeIcmp): // This case, we can use the ALU flag set by SUBS instruction. + cvalInstr := cvalDef.Instr + x, y, c := cvalInstr.IcmpData() + cc = condFlagFromSSAIntegerCmpCond(c) + m.lowerIcmpToFlag(x, y, c.Signed()) + cvalDef.Instr.MarkLowered() + case m.compiler.MatchInstr(cvalDef, ssa.OpcodeFcmp): // This case we can use the Fpu flag directly. + cvalInstr := cvalDef.Instr + x, y, c := cvalInstr.FcmpData() + cc = condFlagFromSSAFloatCmpCond(c) + m.lowerFcmpToFlag(x, y) + cvalDef.Instr.MarkLowered() + default: + rn := m.getOperand_NR(cvalDef, extModeNone) + if c.Type() != ssa.TypeI32 && c.Type() != ssa.TypeI64 { + panic("TODO?BUG?: support select with non-integer condition") + } + alu := m.allocateInstr() + // subs zr, rn, zr + alu.asALU( + aluOpSubS, + // We don't need the result, just need to set flags. + xzrVReg, + rn, + operandNR(xzrVReg), + c.Type().Bits() == 64, + ) + m.insert(alu) + cc = ne + } + + rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone) + rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone) + + rd := m.compiler.VRegOf(result) + switch x.Type() { + case ssa.TypeI32, ssa.TypeI64: + // csel rd, rn, rm, cc + csel := m.allocateInstr() + csel.asCSel(rd, rn, rm, cc, x.Type().Bits() == 64) + m.insert(csel) + case ssa.TypeF32, ssa.TypeF64: + // fcsel rd, rn, rm, cc + fcsel := m.allocateInstr() + fcsel.asFpuCSel(rd, rn, rm, cc, x.Type().Bits() == 64) + m.insert(fcsel) + default: + panic("BUG") + } +} + +func (m *machine) lowerSelectVec(rc, rn, rm operand, rd regalloc.VReg) { + // First check if `rc` is zero or not. + checkZero := m.allocateInstr() + checkZero.asALU(aluOpSubS, xzrVReg, rc, operandNR(xzrVReg), false) + m.insert(checkZero) + + // Then use CSETM to set all bits to one if `rc` is zero. + allOnesOrZero := m.compiler.AllocateVReg(ssa.TypeI64) + cset := m.allocateInstr() + cset.asCSet(allOnesOrZero, true, ne) + m.insert(cset) + + // Then move the bits to the result vector register. + tmp2 := m.compiler.AllocateVReg(ssa.TypeV128) + dup := m.allocateInstr() + dup.asVecDup(tmp2, operandNR(allOnesOrZero), vecArrangement2D) + m.insert(dup) + + // Now that `tmp2` has either all bits one or zero depending on `rc`, + // we can use bsl to select between `rn` and `rm`. + ins := m.allocateInstr() + ins.asVecRRRRewrite(vecOpBsl, tmp2, rn, rm, vecArrangement16B) + m.insert(ins) + + // Finally, move the result to the destination register. + mov2 := m.allocateInstr() + mov2.asFpuMov128(rd, tmp2) + m.insert(mov2) +} + +func (m *machine) lowerAtomicRmw(si *ssa.Instruction) { + ssaOp, size := si.AtomicRmwData() + + var op atomicRmwOp + var negateArg bool + var flipArg bool + switch ssaOp { + case ssa.AtomicRmwOpAdd: + op = atomicRmwOpAdd + case ssa.AtomicRmwOpSub: + op = atomicRmwOpAdd + negateArg = true + case ssa.AtomicRmwOpAnd: + op = atomicRmwOpClr + flipArg = true + case ssa.AtomicRmwOpOr: + op = atomicRmwOpSet + case ssa.AtomicRmwOpXor: + op = atomicRmwOpEor + case ssa.AtomicRmwOpXchg: + op = atomicRmwOpSwp + default: + panic(fmt.Sprintf("unknown ssa atomic rmw op: %s", ssaOp)) + } + + addr, val := si.Arg2() + addrDef, valDef := m.compiler.ValueDefinition(addr), m.compiler.ValueDefinition(val) + rn := m.getOperand_NR(addrDef, extModeNone) + rt := m.compiler.VRegOf(si.Return()) + rs := m.getOperand_NR(valDef, extModeNone) + + _64 := si.Return().Type().Bits() == 64 + var tmp regalloc.VReg + if _64 { + tmp = m.compiler.AllocateVReg(ssa.TypeI64) + } else { + tmp = m.compiler.AllocateVReg(ssa.TypeI32) + } + m.lowerAtomicRmwImpl(op, rn.nr(), rs.nr(), rt, tmp, size, negateArg, flipArg, _64) +} + +func (m *machine) lowerAtomicRmwImpl(op atomicRmwOp, rn, rs, rt, tmp regalloc.VReg, size uint64, negateArg, flipArg, dst64bit bool) { + switch { + case negateArg: + neg := m.allocateInstr() + neg.asALU(aluOpSub, tmp, operandNR(xzrVReg), operandNR(rs), dst64bit) + m.insert(neg) + case flipArg: + flip := m.allocateInstr() + flip.asALU(aluOpOrn, tmp, operandNR(xzrVReg), operandNR(rs), dst64bit) + m.insert(flip) + default: + tmp = rs + } + + rmw := m.allocateInstr() + rmw.asAtomicRmw(op, rn, tmp, rt, size) + m.insert(rmw) +} + +func (m *machine) lowerAtomicCas(si *ssa.Instruction) { + addr, exp, repl := si.Arg3() + size := si.AtomicTargetSize() + + addrDef, expDef, replDef := m.compiler.ValueDefinition(addr), m.compiler.ValueDefinition(exp), m.compiler.ValueDefinition(repl) + rn := m.getOperand_NR(addrDef, extModeNone) + rt := m.getOperand_NR(replDef, extModeNone) + rs := m.getOperand_NR(expDef, extModeNone) + tmp := m.compiler.AllocateVReg(si.Return().Type()) + + _64 := si.Return().Type().Bits() == 64 + // rs is overwritten by CAS, so we need to move it to the result register before the instruction + // in case when it is used somewhere else. + mov := m.allocateInstr() + if _64 { + mov.asMove64(tmp, rs.nr()) + } else { + mov.asMove32(tmp, rs.nr()) + } + m.insert(mov) + + m.lowerAtomicCasImpl(rn.nr(), tmp, rt.nr(), size) + + mov2 := m.allocateInstr() + rd := m.compiler.VRegOf(si.Return()) + if _64 { + mov2.asMove64(rd, tmp) + } else { + mov2.asMove32(rd, tmp) + } + m.insert(mov2) +} + +func (m *machine) lowerAtomicCasImpl(rn, rs, rt regalloc.VReg, size uint64) { + cas := m.allocateInstr() + cas.asAtomicCas(rn, rs, rt, size) + m.insert(cas) +} + +func (m *machine) lowerAtomicLoad(si *ssa.Instruction) { + addr := si.Arg() + size := si.AtomicTargetSize() + + addrDef := m.compiler.ValueDefinition(addr) + rn := m.getOperand_NR(addrDef, extModeNone) + rt := m.compiler.VRegOf(si.Return()) + + m.lowerAtomicLoadImpl(rn.nr(), rt, size) +} + +func (m *machine) lowerAtomicLoadImpl(rn, rt regalloc.VReg, size uint64) { + ld := m.allocateInstr() + ld.asAtomicLoad(rn, rt, size) + m.insert(ld) +} + +func (m *machine) lowerAtomicStore(si *ssa.Instruction) { + addr, val := si.Arg2() + size := si.AtomicTargetSize() + + addrDef := m.compiler.ValueDefinition(addr) + valDef := m.compiler.ValueDefinition(val) + rn := m.getOperand_NR(addrDef, extModeNone) + rt := m.getOperand_NR(valDef, extModeNone) + + m.lowerAtomicStoreImpl(rn, rt, size) +} + +func (m *machine) lowerAtomicStoreImpl(rn, rt operand, size uint64) { + ld := m.allocateInstr() + ld.asAtomicStore(rn, rt, size) + m.insert(ld) +} + +// copyToTmp copies the given regalloc.VReg to a temporary register. This is called before cbr to avoid the regalloc issue +// e.g. reload happening in the middle of the exit sequence which is not the path the normal path executes +func (m *machine) copyToTmp(v regalloc.VReg) regalloc.VReg { + typ := m.compiler.TypeOf(v) + mov := m.allocateInstr() + tmp := m.compiler.AllocateVReg(typ) + if typ.IsInt() { + mov.asMove64(tmp, v) + } else { + mov.asFpuMov128(tmp, v) + } + m.insert(mov) + return tmp +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/lower_instr_operands.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/lower_instr_operands.go new file mode 100644 index 00000000000..7a398c3d097 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/lower_instr_operands.go @@ -0,0 +1,340 @@ +package arm64 + +// This file contains the logic to "find and determine operands" for instructions. +// In order to finalize the form of an operand, we might end up merging/eliminating +// the source instructions into an operand whenever possible. + +import ( + "fmt" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" +) + +type ( + // operand represents an operand of an instruction whose type is determined by the kind. + operand struct { + kind operandKind + data, data2 uint64 + } + operandKind byte +) + +// Here's the list of operand kinds. We use the abbreviation of the kind name not only for these consts, +// but also names of functions which return the operand of the kind. +const ( + // operandKindNR represents "NormalRegister" (NR). This is literally the register without any special operation unlike others. + operandKindNR operandKind = iota + // operandKindSR represents "Shifted Register" (SR). This is a register which is shifted by a constant. + // Some of the arm64 instructions can take this kind of operand. + operandKindSR + // operandKindER represents "Extended Register (ER). This is a register which is sign/zero-extended to a larger size. + // Some of the arm64 instructions can take this kind of operand. + operandKindER + // operandKindImm12 represents "Immediate 12" (Imm12). This is a 12-bit immediate value which can be either shifted by 12 or not. + // See asImm12 function for detail. + operandKindImm12 + // operandKindShiftImm represents "Shifted Immediate" (ShiftImm) used by shift operations. + operandKindShiftImm +) + +// String implements fmt.Stringer for debugging. +func (o operand) format(size byte) string { + switch o.kind { + case operandKindNR: + return formatVRegSized(o.nr(), size) + case operandKindSR: + r, amt, sop := o.sr() + return fmt.Sprintf("%s, %s #%d", formatVRegSized(r, size), sop, amt) + case operandKindER: + r, eop, _ := o.er() + return fmt.Sprintf("%s %s", formatVRegSized(r, size), eop) + case operandKindImm12: + imm12, shiftBit := o.imm12() + if shiftBit == 1 { + return fmt.Sprintf("#%#x", uint64(imm12)<<12) + } else { + return fmt.Sprintf("#%#x", imm12) + } + default: + panic(fmt.Sprintf("unknown operand kind: %d", o.kind)) + } +} + +// operandNR encodes the given VReg as an operand of operandKindNR. +func operandNR(r regalloc.VReg) operand { + return operand{kind: operandKindNR, data: uint64(r)} +} + +// nr decodes the underlying VReg assuming the operand is of operandKindNR. +func (o operand) nr() regalloc.VReg { + return regalloc.VReg(o.data) +} + +// operandER encodes the given VReg as an operand of operandKindER. +func operandER(r regalloc.VReg, eop extendOp, to byte) operand { + if to < 32 { + panic("TODO?BUG?: when we need to extend to less than 32 bits?") + } + return operand{kind: operandKindER, data: uint64(r), data2: uint64(eop)<<32 | uint64(to)} +} + +// er decodes the underlying VReg, extend operation, and the target size assuming the operand is of operandKindER. +func (o operand) er() (r regalloc.VReg, eop extendOp, to byte) { + return regalloc.VReg(o.data), extendOp(o.data2>>32) & 0xff, byte(o.data2 & 0xff) +} + +// operandSR encodes the given VReg as an operand of operandKindSR. +func operandSR(r regalloc.VReg, amt byte, sop shiftOp) operand { + return operand{kind: operandKindSR, data: uint64(r), data2: uint64(amt)<<32 | uint64(sop)} +} + +// sr decodes the underlying VReg, shift amount, and shift operation assuming the operand is of operandKindSR. +func (o operand) sr() (r regalloc.VReg, amt byte, sop shiftOp) { + return regalloc.VReg(o.data), byte(o.data2>>32) & 0xff, shiftOp(o.data2) & 0xff +} + +// operandImm12 encodes the given imm12 as an operand of operandKindImm12. +func operandImm12(imm12 uint16, shiftBit byte) operand { + return operand{kind: operandKindImm12, data: uint64(imm12) | uint64(shiftBit)<<32} +} + +// imm12 decodes the underlying imm12 data assuming the operand is of operandKindImm12. +func (o operand) imm12() (v uint16, shiftBit byte) { + return uint16(o.data), byte(o.data >> 32) +} + +// operandShiftImm encodes the given amount as an operand of operandKindShiftImm. +func operandShiftImm(amount byte) operand { + return operand{kind: operandKindShiftImm, data: uint64(amount)} +} + +// shiftImm decodes the underlying shift amount data assuming the operand is of operandKindShiftImm. +func (o operand) shiftImm() byte { + return byte(o.data) +} + +// reg returns the register of the operand if applicable. +func (o operand) reg() regalloc.VReg { + switch o.kind { + case operandKindNR: + return o.nr() + case operandKindSR: + r, _, _ := o.sr() + return r + case operandKindER: + r, _, _ := o.er() + return r + case operandKindImm12: + // Does not have a register. + case operandKindShiftImm: + // Does not have a register. + default: + panic(o.kind) + } + return regalloc.VRegInvalid +} + +func (o operand) realReg() regalloc.RealReg { + return o.nr().RealReg() +} + +func (o operand) assignReg(v regalloc.VReg) operand { + switch o.kind { + case operandKindNR: + return operandNR(v) + case operandKindSR: + _, amt, sop := o.sr() + return operandSR(v, amt, sop) + case operandKindER: + _, eop, to := o.er() + return operandER(v, eop, to) + case operandKindImm12: + // Does not have a register. + case operandKindShiftImm: + // Does not have a register. + } + panic(o.kind) +} + +// ensureValueNR returns an operand of either operandKindER, operandKindSR, or operandKindNR from the given value (defined by `def). +// +// `mode` is used to extend the operand if the bit length is smaller than mode.bits(). +// If the operand can be expressed as operandKindImm12, `mode` is ignored. +func (m *machine) getOperand_Imm12_ER_SR_NR(def backend.SSAValueDefinition, mode extMode) (op operand) { + if !def.IsFromInstr() { + return operandNR(m.compiler.VRegOf(def.V)) + } + + instr := def.Instr + if instr.Opcode() == ssa.OpcodeIconst { + if imm12Op, ok := asImm12Operand(instr.ConstantVal()); ok { + instr.MarkLowered() + return imm12Op + } + } + return m.getOperand_ER_SR_NR(def, mode) +} + +// getOperand_MaybeNegatedImm12_ER_SR_NR is almost the same as getOperand_Imm12_ER_SR_NR, but this might negate the immediate value. +// If the immediate value is negated, the second return value is true, otherwise always false. +func (m *machine) getOperand_MaybeNegatedImm12_ER_SR_NR(def backend.SSAValueDefinition, mode extMode) (op operand, negatedImm12 bool) { + if !def.IsFromInstr() { + return operandNR(m.compiler.VRegOf(def.V)), false + } + + instr := def.Instr + if instr.Opcode() == ssa.OpcodeIconst { + c := instr.ConstantVal() + if imm12Op, ok := asImm12Operand(c); ok { + instr.MarkLowered() + return imm12Op, false + } + + signExtended := int64(c) + if def.V.Type().Bits() == 32 { + signExtended = (signExtended << 32) >> 32 + } + negatedWithoutSign := -signExtended + if imm12Op, ok := asImm12Operand(uint64(negatedWithoutSign)); ok { + instr.MarkLowered() + return imm12Op, true + } + } + return m.getOperand_ER_SR_NR(def, mode), false +} + +// ensureValueNR returns an operand of either operandKindER, operandKindSR, or operandKindNR from the given value (defined by `def). +// +// `mode` is used to extend the operand if the bit length is smaller than mode.bits(). +func (m *machine) getOperand_ER_SR_NR(def backend.SSAValueDefinition, mode extMode) (op operand) { + if !def.IsFromInstr() { + return operandNR(m.compiler.VRegOf(def.V)) + } + + if m.compiler.MatchInstr(def, ssa.OpcodeSExtend) || m.compiler.MatchInstr(def, ssa.OpcodeUExtend) { + extInstr := def.Instr + + signed := extInstr.Opcode() == ssa.OpcodeSExtend + innerExtFromBits, innerExtToBits := extInstr.ExtendFromToBits() + modeBits, modeSigned := mode.bits(), mode.signed() + if mode == extModeNone || innerExtToBits == modeBits { + eop := extendOpFrom(signed, innerExtFromBits) + extArg := m.getOperand_NR(m.compiler.ValueDefinition(extInstr.Arg()), extModeNone) + op = operandER(extArg.nr(), eop, innerExtToBits) + extInstr.MarkLowered() + return + } + + if innerExtToBits > modeBits { + panic("BUG?TODO?: need the results of inner extension to be larger than the mode") + } + + switch { + case (!signed && !modeSigned) || (signed && modeSigned): + // Two sign/zero extensions are equivalent to one sign/zero extension for the larger size. + eop := extendOpFrom(modeSigned, innerExtFromBits) + op = operandER(m.compiler.VRegOf(extInstr.Arg()), eop, modeBits) + extInstr.MarkLowered() + case (signed && !modeSigned) || (!signed && modeSigned): + // We need to {sign, zero}-extend the result of the {zero,sign} extension. + eop := extendOpFrom(modeSigned, innerExtToBits) + op = operandER(m.compiler.VRegOf(extInstr.Return()), eop, modeBits) + // Note that we failed to merge the inner extension instruction this case. + } + return + } + return m.getOperand_SR_NR(def, mode) +} + +// ensureValueNR returns an operand of either operandKindSR or operandKindNR from the given value (defined by `def). +// +// `mode` is used to extend the operand if the bit length is smaller than mode.bits(). +func (m *machine) getOperand_SR_NR(def backend.SSAValueDefinition, mode extMode) (op operand) { + if !def.IsFromInstr() { + return operandNR(m.compiler.VRegOf(def.V)) + } + + if m.compiler.MatchInstr(def, ssa.OpcodeIshl) { + // Check if the shift amount is constant instruction. + targetVal, amountVal := def.Instr.Arg2() + targetVReg := m.getOperand_NR(m.compiler.ValueDefinition(targetVal), extModeNone).nr() + amountDef := m.compiler.ValueDefinition(amountVal) + if amountDef.IsFromInstr() && amountDef.Instr.Constant() { + // If that is the case, we can use the shifted register operand (SR). + c := byte(amountDef.Instr.ConstantVal()) & (targetVal.Type().Bits() - 1) // Clears the unnecessary bits. + def.Instr.MarkLowered() + amountDef.Instr.MarkLowered() + return operandSR(targetVReg, c, shiftOpLSL) + } + } + return m.getOperand_NR(def, mode) +} + +// getOperand_ShiftImm_NR returns an operand of either operandKindShiftImm or operandKindNR from the given value (defined by `def). +func (m *machine) getOperand_ShiftImm_NR(def backend.SSAValueDefinition, mode extMode, shiftBitWidth byte) (op operand) { + if !def.IsFromInstr() { + return operandNR(m.compiler.VRegOf(def.V)) + } + + instr := def.Instr + if instr.Constant() { + amount := byte(instr.ConstantVal()) & (shiftBitWidth - 1) // Clears the unnecessary bits. + return operandShiftImm(amount) + } + return m.getOperand_NR(def, mode) +} + +// ensureValueNR returns an operand of operandKindNR from the given value (defined by `def). +// +// `mode` is used to extend the operand if the bit length is smaller than mode.bits(). +func (m *machine) getOperand_NR(def backend.SSAValueDefinition, mode extMode) (op operand) { + var v regalloc.VReg + if def.IsFromInstr() && def.Instr.Constant() { + // We inline all the constant instructions so that we could reduce the register usage. + v = m.lowerConstant(def.Instr) + def.Instr.MarkLowered() + } else { + v = m.compiler.VRegOf(def.V) + } + + r := v + switch inBits := def.V.Type().Bits(); { + case mode == extModeNone: + case inBits == 32 && (mode == extModeZeroExtend32 || mode == extModeSignExtend32): + case inBits == 32 && mode == extModeZeroExtend64: + extended := m.compiler.AllocateVReg(ssa.TypeI64) + ext := m.allocateInstr() + ext.asExtend(extended, v, 32, 64, false) + m.insert(ext) + r = extended + case inBits == 32 && mode == extModeSignExtend64: + extended := m.compiler.AllocateVReg(ssa.TypeI64) + ext := m.allocateInstr() + ext.asExtend(extended, v, 32, 64, true) + m.insert(ext) + r = extended + case inBits == 64 && (mode == extModeZeroExtend64 || mode == extModeSignExtend64): + } + return operandNR(r) +} + +func asImm12Operand(val uint64) (op operand, ok bool) { + v, shiftBit, ok := asImm12(val) + if !ok { + return operand{}, false + } + return operandImm12(v, shiftBit), true +} + +func asImm12(val uint64) (v uint16, shiftBit byte, ok bool) { + const mask1, mask2 uint64 = 0xfff, 0xfff_000 + if val&^mask1 == 0 { + return uint16(val), 0, true + } else if val&^mask2 == 0 { + return uint16(val >> 12), 1, true + } else { + return 0, 0, false + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/lower_mem.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/lower_mem.go new file mode 100644 index 00000000000..fd0760d723f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/lower_mem.go @@ -0,0 +1,451 @@ +package arm64 + +import ( + "fmt" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +type ( + // addressMode represents an ARM64 addressing mode. + // + // https://developer.arm.com/documentation/102374/0101/Loads-and-stores---addressing + // TODO: use the bit-packed layout like operand struct. + addressMode struct { + kind addressModeKind + rn, rm regalloc.VReg + extOp extendOp + imm int64 + } + + // addressModeKind represents the kind of ARM64 addressing mode. + addressModeKind byte +) + +func resetAddressMode(a *addressMode) { + a.kind = 0 + a.rn = 0 + a.rm = 0 + a.extOp = 0 + a.imm = 0 +} + +const ( + // addressModeKindRegExtended takes a base register and an index register. The index register is sign/zero-extended, + // and then scaled by bits(type)/8. + // + // e.g. + // - ldrh w1, [x2, w3, SXTW #1] ;; sign-extended and scaled by 2 (== LSL #1) + // - strh w1, [x2, w3, UXTW #1] ;; zero-extended and scaled by 2 (== LSL #1) + // - ldr w1, [x2, w3, SXTW #2] ;; sign-extended and scaled by 4 (== LSL #2) + // - str x1, [x2, w3, UXTW #3] ;; zero-extended and scaled by 8 (== LSL #3) + // + // See the following pages: + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDRH--register---Load-Register-Halfword--register-- + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDR--register---Load-Register--register-- + addressModeKindRegScaledExtended addressModeKind = iota + + // addressModeKindRegScaled is the same as addressModeKindRegScaledExtended, but without extension factor. + addressModeKindRegScaled + + // addressModeKindRegScaled is the same as addressModeKindRegScaledExtended, but without scale factor. + addressModeKindRegExtended + + // addressModeKindRegReg takes a base register and an index register. The index register is not either scaled or extended. + addressModeKindRegReg + + // addressModeKindRegSignedImm9 takes a base register and a 9-bit "signed" immediate offset (-256 to 255). + // The immediate will be sign-extended, and be added to the base register. + // This is a.k.a. "unscaled" since the immediate is not scaled. + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDUR--Load-Register--unscaled-- + addressModeKindRegSignedImm9 + + // addressModeKindRegUnsignedImm12 takes a base register and a 12-bit "unsigned" immediate offset. scaled by + // the size of the type. In other words, the actual offset will be imm12 * bits(type)/8. + // See "Unsigned offset" in the following pages: + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDRB--immediate---Load-Register-Byte--immediate-- + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDRH--immediate---Load-Register-Halfword--immediate-- + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDR--immediate---Load-Register--immediate-- + addressModeKindRegUnsignedImm12 + + // addressModePostIndex takes a base register and a 9-bit "signed" immediate offset. + // After the load/store, the base register will be updated by the offset. + // + // Note that when this is used for pair load/store, the offset will be 7-bit "signed" immediate offset. + // + // See "Post-index" in the following pages for examples: + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDRB--immediate---Load-Register-Byte--immediate-- + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDRH--immediate---Load-Register-Halfword--immediate-- + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDR--immediate---Load-Register--immediate-- + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDP--Load-Pair-of-Registers- + addressModeKindPostIndex + + // addressModePostIndex takes a base register and a 9-bit "signed" immediate offset. + // Before the load/store, the base register will be updated by the offset. + // + // Note that when this is used for pair load/store, the offset will be 7-bit "signed" immediate offset. + // + // See "Pre-index" in the following pages for examples: + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDRB--immediate---Load-Register-Byte--immediate-- + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDRH--immediate---Load-Register-Halfword--immediate-- + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDR--immediate---Load-Register--immediate-- + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDP--Load-Pair-of-Registers- + addressModeKindPreIndex + + // addressModeKindArgStackSpace is used to resolve the address of the argument stack space + // exiting right above the stack pointer. Since we don't know the exact stack space needed for a function + // at a compilation phase, this is used as a placeholder and further lowered to a real addressing mode like above. + addressModeKindArgStackSpace + + // addressModeKindResultStackSpace is used to resolve the address of the result stack space + // exiting right above the stack pointer. Since we don't know the exact stack space needed for a function + // at a compilation phase, this is used as a placeholder and further lowered to a real addressing mode like above. + addressModeKindResultStackSpace +) + +func (a addressMode) format(dstSizeBits byte) (ret string) { + base := formatVRegSized(a.rn, 64) + if rn := a.rn; rn.RegType() != regalloc.RegTypeInt { + panic("invalid base register type: " + a.rn.RegType().String()) + } else if rn.IsRealReg() && v0 <= a.rn.RealReg() && a.rn.RealReg() <= v30 { + panic("BUG: likely a bug in reg alloc or reset behavior") + } + + switch a.kind { + case addressModeKindRegScaledExtended: + amount := a.sizeInBitsToShiftAmount(dstSizeBits) + ret = fmt.Sprintf("[%s, %s, %s #%#x]", base, formatVRegSized(a.rm, a.indexRegBits()), a.extOp, amount) + case addressModeKindRegScaled: + amount := a.sizeInBitsToShiftAmount(dstSizeBits) + ret = fmt.Sprintf("[%s, %s, lsl #%#x]", base, formatVRegSized(a.rm, a.indexRegBits()), amount) + case addressModeKindRegExtended: + ret = fmt.Sprintf("[%s, %s, %s]", base, formatVRegSized(a.rm, a.indexRegBits()), a.extOp) + case addressModeKindRegReg: + ret = fmt.Sprintf("[%s, %s]", base, formatVRegSized(a.rm, a.indexRegBits())) + case addressModeKindRegSignedImm9: + if a.imm != 0 { + ret = fmt.Sprintf("[%s, #%#x]", base, a.imm) + } else { + ret = fmt.Sprintf("[%s]", base) + } + case addressModeKindRegUnsignedImm12: + if a.imm != 0 { + ret = fmt.Sprintf("[%s, #%#x]", base, a.imm) + } else { + ret = fmt.Sprintf("[%s]", base) + } + case addressModeKindPostIndex: + ret = fmt.Sprintf("[%s], #%#x", base, a.imm) + case addressModeKindPreIndex: + ret = fmt.Sprintf("[%s, #%#x]!", base, a.imm) + case addressModeKindArgStackSpace: + ret = fmt.Sprintf("[#arg_space, #%#x]", a.imm) + case addressModeKindResultStackSpace: + ret = fmt.Sprintf("[#ret_space, #%#x]", a.imm) + } + return +} + +func addressModePreOrPostIndex(m *machine, rn regalloc.VReg, imm int64, preIndex bool) *addressMode { + if !offsetFitsInAddressModeKindRegSignedImm9(imm) { + panic(fmt.Sprintf("BUG: offset %#x does not fit in addressModeKindRegSignedImm9", imm)) + } + mode := m.amodePool.Allocate() + if preIndex { + *mode = addressMode{kind: addressModeKindPreIndex, rn: rn, imm: imm} + } else { + *mode = addressMode{kind: addressModeKindPostIndex, rn: rn, imm: imm} + } + return mode +} + +func offsetFitsInAddressModeKindRegUnsignedImm12(dstSizeInBits byte, offset int64) bool { + divisor := int64(dstSizeInBits) / 8 + return 0 < offset && offset%divisor == 0 && offset/divisor < 4096 +} + +func offsetFitsInAddressModeKindRegSignedImm9(offset int64) bool { + return -256 <= offset && offset <= 255 +} + +func (a addressMode) indexRegBits() byte { + bits := a.extOp.srcBits() + if bits != 32 && bits != 64 { + panic("invalid index register for address mode. it must be either 32 or 64 bits") + } + return bits +} + +func (a addressMode) sizeInBitsToShiftAmount(sizeInBits byte) (lsl byte) { + switch sizeInBits { + case 8: + lsl = 0 + case 16: + lsl = 1 + case 32: + lsl = 2 + case 64: + lsl = 3 + } + return +} + +func extLoadSignSize(op ssa.Opcode) (size byte, signed bool) { + switch op { + case ssa.OpcodeUload8: + size, signed = 8, false + case ssa.OpcodeUload16: + size, signed = 16, false + case ssa.OpcodeUload32: + size, signed = 32, false + case ssa.OpcodeSload8: + size, signed = 8, true + case ssa.OpcodeSload16: + size, signed = 16, true + case ssa.OpcodeSload32: + size, signed = 32, true + default: + panic("BUG") + } + return +} + +func (m *machine) lowerExtLoad(op ssa.Opcode, ptr ssa.Value, offset uint32, ret regalloc.VReg) { + size, signed := extLoadSignSize(op) + amode := m.lowerToAddressMode(ptr, offset, size) + load := m.allocateInstr() + if signed { + load.asSLoad(ret, amode, size) + } else { + load.asULoad(ret, amode, size) + } + m.insert(load) +} + +func (m *machine) lowerLoad(ptr ssa.Value, offset uint32, typ ssa.Type, ret ssa.Value) { + amode := m.lowerToAddressMode(ptr, offset, typ.Bits()) + + dst := m.compiler.VRegOf(ret) + load := m.allocateInstr() + switch typ { + case ssa.TypeI32, ssa.TypeI64: + load.asULoad(dst, amode, typ.Bits()) + case ssa.TypeF32, ssa.TypeF64: + load.asFpuLoad(dst, amode, typ.Bits()) + case ssa.TypeV128: + load.asFpuLoad(dst, amode, 128) + default: + panic("TODO") + } + m.insert(load) +} + +func (m *machine) lowerLoadSplat(ptr ssa.Value, offset uint32, lane ssa.VecLane, ret ssa.Value) { + // vecLoad1R has offset address mode (base+imm) only for post index, so we simply add the offset to the base. + base := m.getOperand_NR(m.compiler.ValueDefinition(ptr), extModeNone).nr() + offsetReg := m.compiler.AllocateVReg(ssa.TypeI64) + m.lowerConstantI64(offsetReg, int64(offset)) + addedBase := m.addReg64ToReg64(base, offsetReg) + + rd := m.compiler.VRegOf(ret) + + ld1r := m.allocateInstr() + ld1r.asVecLoad1R(rd, operandNR(addedBase), ssaLaneToArrangement(lane)) + m.insert(ld1r) +} + +func (m *machine) lowerStore(si *ssa.Instruction) { + // TODO: merge consecutive stores into a single pair store instruction. + value, ptr, offset, storeSizeInBits := si.StoreData() + amode := m.lowerToAddressMode(ptr, offset, storeSizeInBits) + + valueOp := m.getOperand_NR(m.compiler.ValueDefinition(value), extModeNone) + store := m.allocateInstr() + store.asStore(valueOp, amode, storeSizeInBits) + m.insert(store) +} + +// lowerToAddressMode converts a pointer to an addressMode that can be used as an operand for load/store instructions. +func (m *machine) lowerToAddressMode(ptr ssa.Value, offsetBase uint32, size byte) (amode *addressMode) { + // TODO: currently the instruction selection logic doesn't support addressModeKindRegScaledExtended and + // addressModeKindRegScaled since collectAddends doesn't take ssa.OpcodeIshl into account. This should be fixed + // to support more efficient address resolution. + + a32s, a64s, offset := m.collectAddends(ptr) + offset += int64(offsetBase) + return m.lowerToAddressModeFromAddends(a32s, a64s, size, offset) +} + +// lowerToAddressModeFromAddends creates an addressMode from a list of addends collected by collectAddends. +// During the construction, this might emit additional instructions. +// +// Extracted as a separate function for easy testing. +func (m *machine) lowerToAddressModeFromAddends(a32s *wazevoapi.Queue[addend32], a64s *wazevoapi.Queue[regalloc.VReg], size byte, offset int64) (amode *addressMode) { + amode = m.amodePool.Allocate() + switch a64sExist, a32sExist := !a64s.Empty(), !a32s.Empty(); { + case a64sExist && a32sExist: + var base regalloc.VReg + base = a64s.Dequeue() + var a32 addend32 + a32 = a32s.Dequeue() + *amode = addressMode{kind: addressModeKindRegExtended, rn: base, rm: a32.r, extOp: a32.ext} + case a64sExist && offsetFitsInAddressModeKindRegUnsignedImm12(size, offset): + var base regalloc.VReg + base = a64s.Dequeue() + *amode = addressMode{kind: addressModeKindRegUnsignedImm12, rn: base, imm: offset} + offset = 0 + case a64sExist && offsetFitsInAddressModeKindRegSignedImm9(offset): + var base regalloc.VReg + base = a64s.Dequeue() + *amode = addressMode{kind: addressModeKindRegSignedImm9, rn: base, imm: offset} + offset = 0 + case a64sExist: + var base regalloc.VReg + base = a64s.Dequeue() + if !a64s.Empty() { + index := a64s.Dequeue() + *amode = addressMode{kind: addressModeKindRegReg, rn: base, rm: index, extOp: extendOpUXTX /* indicates index reg is 64-bit */} + } else { + *amode = addressMode{kind: addressModeKindRegUnsignedImm12, rn: base, imm: 0} + } + case a32sExist: + base32 := a32s.Dequeue() + + // First we need 64-bit base. + base := m.compiler.AllocateVReg(ssa.TypeI64) + baseExt := m.allocateInstr() + var signed bool + if base32.ext == extendOpSXTW { + signed = true + } + baseExt.asExtend(base, base32.r, 32, 64, signed) + m.insert(baseExt) + + if !a32s.Empty() { + index := a32s.Dequeue() + *amode = addressMode{kind: addressModeKindRegExtended, rn: base, rm: index.r, extOp: index.ext} + } else { + *amode = addressMode{kind: addressModeKindRegUnsignedImm12, rn: base, imm: 0} + } + default: // Only static offsets. + tmpReg := m.compiler.AllocateVReg(ssa.TypeI64) + m.lowerConstantI64(tmpReg, offset) + *amode = addressMode{kind: addressModeKindRegUnsignedImm12, rn: tmpReg, imm: 0} + offset = 0 + } + + baseReg := amode.rn + if offset > 0 { + baseReg = m.addConstToReg64(baseReg, offset) // baseReg += offset + } + + for !a64s.Empty() { + a64 := a64s.Dequeue() + baseReg = m.addReg64ToReg64(baseReg, a64) // baseReg += a64 + } + + for !a32s.Empty() { + a32 := a32s.Dequeue() + baseReg = m.addRegToReg64Ext(baseReg, a32.r, a32.ext) // baseReg += (a32 extended to 64-bit) + } + amode.rn = baseReg + return +} + +var addendsMatchOpcodes = [4]ssa.Opcode{ssa.OpcodeUExtend, ssa.OpcodeSExtend, ssa.OpcodeIadd, ssa.OpcodeIconst} + +func (m *machine) collectAddends(ptr ssa.Value) (addends32 *wazevoapi.Queue[addend32], addends64 *wazevoapi.Queue[regalloc.VReg], offset int64) { + m.addendsWorkQueue.Reset() + m.addends32.Reset() + m.addends64.Reset() + m.addendsWorkQueue.Enqueue(ptr) + + for !m.addendsWorkQueue.Empty() { + v := m.addendsWorkQueue.Dequeue() + + def := m.compiler.ValueDefinition(v) + switch op := m.compiler.MatchInstrOneOf(def, addendsMatchOpcodes[:]); op { + case ssa.OpcodeIadd: + // If the addend is an add, we recursively collect its operands. + x, y := def.Instr.Arg2() + m.addendsWorkQueue.Enqueue(x) + m.addendsWorkQueue.Enqueue(y) + def.Instr.MarkLowered() + case ssa.OpcodeIconst: + // If the addend is constant, we just statically merge it into the offset. + ic := def.Instr + u64 := ic.ConstantVal() + if ic.Return().Type().Bits() == 32 { + offset += int64(int32(u64)) // sign-extend. + } else { + offset += int64(u64) + } + def.Instr.MarkLowered() + case ssa.OpcodeUExtend, ssa.OpcodeSExtend: + input := def.Instr.Arg() + if input.Type().Bits() != 32 { + panic("illegal size: " + input.Type().String()) + } + + var ext extendOp + if op == ssa.OpcodeUExtend { + ext = extendOpUXTW + } else { + ext = extendOpSXTW + } + + inputDef := m.compiler.ValueDefinition(input) + constInst := inputDef.IsFromInstr() && inputDef.Instr.Constant() + switch { + case constInst && ext == extendOpUXTW: + // Zero-extension of a 32-bit constant can be merged into the offset. + offset += int64(uint32(inputDef.Instr.ConstantVal())) + case constInst && ext == extendOpSXTW: + // Sign-extension of a 32-bit constant can be merged into the offset. + offset += int64(int32(inputDef.Instr.ConstantVal())) // sign-extend! + default: + m.addends32.Enqueue(addend32{r: m.getOperand_NR(inputDef, extModeNone).nr(), ext: ext}) + } + def.Instr.MarkLowered() + continue + default: + // If the addend is not one of them, we simply use it as-is (without merging!), optionally zero-extending it. + m.addends64.Enqueue(m.getOperand_NR(def, extModeZeroExtend64 /* optional zero ext */).nr()) + } + } + return &m.addends32, &m.addends64, offset +} + +func (m *machine) addConstToReg64(r regalloc.VReg, c int64) (rd regalloc.VReg) { + rd = m.compiler.AllocateVReg(ssa.TypeI64) + alu := m.allocateInstr() + if imm12Op, ok := asImm12Operand(uint64(c)); ok { + alu.asALU(aluOpAdd, rd, operandNR(r), imm12Op, true) + } else if imm12Op, ok = asImm12Operand(uint64(-c)); ok { + alu.asALU(aluOpSub, rd, operandNR(r), imm12Op, true) + } else { + tmp := m.compiler.AllocateVReg(ssa.TypeI64) + m.load64bitConst(c, tmp) + alu.asALU(aluOpAdd, rd, operandNR(r), operandNR(tmp), true) + } + m.insert(alu) + return +} + +func (m *machine) addReg64ToReg64(rn, rm regalloc.VReg) (rd regalloc.VReg) { + rd = m.compiler.AllocateVReg(ssa.TypeI64) + alu := m.allocateInstr() + alu.asALU(aluOpAdd, rd, operandNR(rn), operandNR(rm), true) + m.insert(alu) + return +} + +func (m *machine) addRegToReg64Ext(rn, rm regalloc.VReg, ext extendOp) (rd regalloc.VReg) { + rd = m.compiler.AllocateVReg(ssa.TypeI64) + alu := m.allocateInstr() + alu.asALU(aluOpAdd, rd, operandNR(rn), operandER(rm, ext, 64), true) + m.insert(alu) + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine.go new file mode 100644 index 00000000000..00e6b238f95 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine.go @@ -0,0 +1,631 @@ +package arm64 + +import ( + "context" + "fmt" + "math" + "strings" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +type ( + // machine implements backend.Machine. + machine struct { + compiler backend.Compiler + currentABI *backend.FunctionABI + instrPool wazevoapi.Pool[instruction] + // labelPositionPool is the pool of labelPosition. The id is the label where + // if the label is less than the maxSSABlockID, it's the ssa.BasicBlockID. + labelPositionPool wazevoapi.IDedPool[labelPosition] + + // nextLabel is the next label to be allocated. The first free label comes after maxSSABlockID + // so that we can have an identical label for the SSA block ID, which is useful for debugging. + nextLabel label + // rootInstr is the first instruction of the function. + rootInstr *instruction + // currentLabelPos is the currently-compiled ssa.BasicBlock's labelPosition. + currentLabelPos *labelPosition + // orderedSSABlockLabelPos is the ordered list of labelPosition in the generated code for each ssa.BasicBlock. + orderedSSABlockLabelPos []*labelPosition + // returnLabelPos is the labelPosition for the return block. + returnLabelPos labelPosition + // perBlockHead and perBlockEnd are the head and tail of the instruction list per currently-compiled ssa.BasicBlock. + perBlockHead, perBlockEnd *instruction + // pendingInstructions are the instructions which are not yet emitted into the instruction list. + pendingInstructions []*instruction + // maxSSABlockID is the maximum ssa.BasicBlockID in the current function. + maxSSABlockID label + + regAlloc regalloc.Allocator[*instruction, *labelPosition, *regAllocFn] + regAllocFn regAllocFn + + amodePool wazevoapi.Pool[addressMode] + + // addendsWorkQueue is used during address lowering, defined here for reuse. + addendsWorkQueue wazevoapi.Queue[ssa.Value] + addends32 wazevoapi.Queue[addend32] + // addends64 is used during address lowering, defined here for reuse. + addends64 wazevoapi.Queue[regalloc.VReg] + unresolvedAddressModes []*instruction + + // condBrRelocs holds the conditional branches which need offset relocation. + condBrRelocs []condBrReloc + + // jmpTableTargets holds the labels of the jump table targets. + jmpTableTargets [][]uint32 + // jmpTableTargetNext is the index to the jmpTableTargets slice to be used for the next jump table. + jmpTableTargetsNext int + + // spillSlotSize is the size of the stack slot in bytes used for spilling registers. + // During the execution of the function, the stack looks like: + // + // + // (high address) + // +-----------------+ + // | ....... | + // | ret Y | + // | ....... | + // | ret 0 | + // | arg X | + // | ....... | + // | arg 1 | + // | arg 0 | + // | xxxxx | + // | ReturnAddress | + // +-----------------+ <<-| + // | ........... | | + // | spill slot M | | <--- spillSlotSize + // | ............ | | + // | spill slot 2 | | + // | spill slot 1 | <<-+ + // | clobbered N | + // | ........... | + // | clobbered 1 | + // | clobbered 0 | + // SP---> +-----------------+ + // (low address) + // + // and it represents the size of the space between FP and the first spilled slot. This must be a multiple of 16. + // Also note that this is only known after register allocation. + spillSlotSize int64 + spillSlots map[regalloc.VRegID]int64 // regalloc.VRegID to offset. + // clobberedRegs holds real-register backed VRegs saved at the function prologue, and restored at the epilogue. + clobberedRegs []regalloc.VReg + + maxRequiredStackSizeForCalls int64 + stackBoundsCheckDisabled bool + + regAllocStarted bool + } + + addend32 struct { + r regalloc.VReg + ext extendOp + } + + condBrReloc struct { + cbr *instruction + // currentLabelPos is the labelPosition within which condBr is defined. + currentLabelPos *labelPosition + // Next block's labelPosition. + nextLabel label + offset int64 + } +) + +type ( + // label represents a position in the generated code which is either + // a real instruction or the constant InstructionPool (e.g. jump tables). + // + // This is exactly the same as the traditional "label" in assembly code. + label uint32 + + // labelPosition represents the regions of the generated code which the label represents. + // This implements regalloc.Block. + labelPosition struct { + // sb is not nil if this corresponds to a ssa.BasicBlock. + sb ssa.BasicBlock + // cur is used to walk through the instructions in the block during the register allocation. + cur, + // begin and end are the first and last instructions of the block. + begin, end *instruction + // binaryOffset is the offset in the binary where the label is located. + binaryOffset int64 + } +) + +const ( + labelReturn label = math.MaxUint32 + labelInvalid = labelReturn - 1 +) + +// String implements backend.Machine. +func (l label) String() string { + return fmt.Sprintf("L%d", l) +} + +func resetLabelPosition(l *labelPosition) { + *l = labelPosition{} +} + +// NewBackend returns a new backend for arm64. +func NewBackend() backend.Machine { + m := &machine{ + spillSlots: make(map[regalloc.VRegID]int64), + regAlloc: regalloc.NewAllocator[*instruction, *labelPosition, *regAllocFn](regInfo), + amodePool: wazevoapi.NewPool[addressMode](resetAddressMode), + instrPool: wazevoapi.NewPool[instruction](resetInstruction), + labelPositionPool: wazevoapi.NewIDedPool[labelPosition](resetLabelPosition), + } + m.regAllocFn.m = m + return m +} + +func ssaBlockLabel(sb ssa.BasicBlock) label { + if sb.ReturnBlock() { + return labelReturn + } + return label(sb.ID()) +} + +// getOrAllocateSSABlockLabelPosition returns the labelPosition for the given basic block. +func (m *machine) getOrAllocateSSABlockLabelPosition(sb ssa.BasicBlock) *labelPosition { + if sb.ReturnBlock() { + m.returnLabelPos.sb = sb + return &m.returnLabelPos + } + + l := ssaBlockLabel(sb) + pos := m.labelPositionPool.GetOrAllocate(int(l)) + pos.sb = sb + return pos +} + +// LinkAdjacentBlocks implements backend.Machine. +func (m *machine) LinkAdjacentBlocks(prev, next ssa.BasicBlock) { + prevPos, nextPos := m.getOrAllocateSSABlockLabelPosition(prev), m.getOrAllocateSSABlockLabelPosition(next) + prevPos.end.next = nextPos.begin +} + +// StartBlock implements backend.Machine. +func (m *machine) StartBlock(blk ssa.BasicBlock) { + m.currentLabelPos = m.getOrAllocateSSABlockLabelPosition(blk) + labelPos := m.currentLabelPos + end := m.allocateNop() + m.perBlockHead, m.perBlockEnd = end, end + labelPos.begin, labelPos.end = end, end + m.orderedSSABlockLabelPos = append(m.orderedSSABlockLabelPos, labelPos) +} + +// EndBlock implements ExecutableContext. +func (m *machine) EndBlock() { + // Insert nop0 as the head of the block for convenience to simplify the logic of inserting instructions. + m.insertAtPerBlockHead(m.allocateNop()) + + m.currentLabelPos.begin = m.perBlockHead + + if m.currentLabelPos.sb.EntryBlock() { + m.rootInstr = m.perBlockHead + } +} + +func (m *machine) insertAtPerBlockHead(i *instruction) { + if m.perBlockHead == nil { + m.perBlockHead = i + m.perBlockEnd = i + return + } + + i.next = m.perBlockHead + m.perBlockHead.prev = i + m.perBlockHead = i +} + +// FlushPendingInstructions implements backend.Machine. +func (m *machine) FlushPendingInstructions() { + l := len(m.pendingInstructions) + if l == 0 { + return + } + for i := l - 1; i >= 0; i-- { // reverse because we lower instructions in reverse order. + m.insertAtPerBlockHead(m.pendingInstructions[i]) + } + m.pendingInstructions = m.pendingInstructions[:0] +} + +// RegAlloc implements backend.Machine Function. +func (m *machine) RegAlloc() { + m.regAllocStarted = true + m.regAlloc.DoAllocation(&m.regAllocFn) + // Now that we know the final spill slot size, we must align spillSlotSize to 16 bytes. + m.spillSlotSize = (m.spillSlotSize + 15) &^ 15 +} + +// Reset implements backend.Machine. +func (m *machine) Reset() { + m.clobberedRegs = m.clobberedRegs[:0] + for key := range m.spillSlots { + m.clobberedRegs = append(m.clobberedRegs, regalloc.VReg(key)) + } + for _, key := range m.clobberedRegs { + delete(m.spillSlots, regalloc.VRegID(key)) + } + m.clobberedRegs = m.clobberedRegs[:0] + m.regAllocStarted = false + m.regAlloc.Reset() + m.spillSlotSize = 0 + m.unresolvedAddressModes = m.unresolvedAddressModes[:0] + m.maxRequiredStackSizeForCalls = 0 + m.jmpTableTargetsNext = 0 + m.amodePool.Reset() + m.instrPool.Reset() + m.labelPositionPool.Reset() + m.pendingInstructions = m.pendingInstructions[:0] + m.perBlockHead, m.perBlockEnd, m.rootInstr = nil, nil, nil + m.orderedSSABlockLabelPos = m.orderedSSABlockLabelPos[:0] +} + +// StartLoweringFunction implements backend.Machine StartLoweringFunction. +func (m *machine) StartLoweringFunction(maxBlockID ssa.BasicBlockID) { + m.maxSSABlockID = label(maxBlockID) + m.nextLabel = label(maxBlockID) + 1 +} + +// SetCurrentABI implements backend.Machine SetCurrentABI. +func (m *machine) SetCurrentABI(abi *backend.FunctionABI) { + m.currentABI = abi +} + +// DisableStackCheck implements backend.Machine DisableStackCheck. +func (m *machine) DisableStackCheck() { + m.stackBoundsCheckDisabled = true +} + +// SetCompiler implements backend.Machine. +func (m *machine) SetCompiler(ctx backend.Compiler) { + m.compiler = ctx + m.regAllocFn.ssaB = ctx.SSABuilder() +} + +func (m *machine) insert(i *instruction) { + m.pendingInstructions = append(m.pendingInstructions, i) +} + +func (m *machine) insertBrTargetLabel() label { + nop, l := m.allocateBrTarget() + m.insert(nop) + return l +} + +func (m *machine) allocateBrTarget() (nop *instruction, l label) { + l = m.nextLabel + m.nextLabel++ + nop = m.allocateInstr() + nop.asNop0WithLabel(l) + pos := m.labelPositionPool.GetOrAllocate(int(l)) + pos.begin, pos.end = nop, nop + return +} + +// allocateInstr allocates an instruction. +func (m *machine) allocateInstr() *instruction { + instr := m.instrPool.Allocate() + if !m.regAllocStarted { + instr.addedBeforeRegAlloc = true + } + return instr +} + +func resetInstruction(i *instruction) { + *i = instruction{} +} + +func (m *machine) allocateNop() *instruction { + instr := m.allocateInstr() + instr.asNop0() + return instr +} + +func (m *machine) resolveAddressingMode(arg0offset, ret0offset int64, i *instruction) { + amode := i.getAmode() + switch amode.kind { + case addressModeKindResultStackSpace: + amode.imm += ret0offset + case addressModeKindArgStackSpace: + amode.imm += arg0offset + default: + panic("BUG") + } + + var sizeInBits byte + switch i.kind { + case store8, uLoad8: + sizeInBits = 8 + case store16, uLoad16: + sizeInBits = 16 + case store32, fpuStore32, uLoad32, fpuLoad32: + sizeInBits = 32 + case store64, fpuStore64, uLoad64, fpuLoad64: + sizeInBits = 64 + case fpuStore128, fpuLoad128: + sizeInBits = 128 + default: + panic("BUG") + } + + if offsetFitsInAddressModeKindRegUnsignedImm12(sizeInBits, amode.imm) { + amode.kind = addressModeKindRegUnsignedImm12 + } else { + // This case, we load the offset into the temporary register, + // and then use it as the index register. + newPrev := m.lowerConstantI64AndInsert(i.prev, tmpRegVReg, amode.imm) + linkInstr(newPrev, i) + *amode = addressMode{kind: addressModeKindRegReg, rn: amode.rn, rm: tmpRegVReg, extOp: extendOpUXTX /* indicates rm reg is 64-bit */} + } +} + +// resolveRelativeAddresses resolves the relative addresses before encoding. +func (m *machine) resolveRelativeAddresses(ctx context.Context) { + for { + if len(m.unresolvedAddressModes) > 0 { + arg0offset, ret0offset := m.arg0OffsetFromSP(), m.ret0OffsetFromSP() + for _, i := range m.unresolvedAddressModes { + m.resolveAddressingMode(arg0offset, ret0offset, i) + } + } + + // Reuse the slice to gather the unresolved conditional branches. + m.condBrRelocs = m.condBrRelocs[:0] + + var fn string + var fnIndex int + var labelPosToLabel map[*labelPosition]label + if wazevoapi.PerfMapEnabled { + labelPosToLabel = make(map[*labelPosition]label) + for i := 0; i <= m.labelPositionPool.MaxIDEncountered(); i++ { + labelPosToLabel[m.labelPositionPool.Get(i)] = label(i) + } + + fn = wazevoapi.GetCurrentFunctionName(ctx) + fnIndex = wazevoapi.GetCurrentFunctionIndex(ctx) + } + + // Next, in order to determine the offsets of relative jumps, we have to calculate the size of each label. + var offset int64 + for i, pos := range m.orderedSSABlockLabelPos { + pos.binaryOffset = offset + var size int64 + for cur := pos.begin; ; cur = cur.next { + switch cur.kind { + case nop0: + l := cur.nop0Label() + if pos := m.labelPositionPool.Get(int(l)); pos != nil { + pos.binaryOffset = offset + size + } + case condBr: + if !cur.condBrOffsetResolved() { + var nextLabel label + if i < len(m.orderedSSABlockLabelPos)-1 { + // Note: this is only used when the block ends with fallthrough, + // therefore can be safely assumed that the next block exists when it's needed. + nextLabel = ssaBlockLabel(m.orderedSSABlockLabelPos[i+1].sb) + } + m.condBrRelocs = append(m.condBrRelocs, condBrReloc{ + cbr: cur, currentLabelPos: pos, offset: offset + size, + nextLabel: nextLabel, + }) + } + } + size += cur.size() + if cur == pos.end { + break + } + } + + if wazevoapi.PerfMapEnabled { + if size > 0 { + wazevoapi.PerfMap.AddModuleEntry(fnIndex, offset, uint64(size), fmt.Sprintf("%s:::::%s", fn, labelPosToLabel[pos])) + } + } + offset += size + } + + // Before resolving any offsets, we need to check if all the conditional branches can be resolved. + var needRerun bool + for i := range m.condBrRelocs { + reloc := &m.condBrRelocs[i] + cbr := reloc.cbr + offset := reloc.offset + + target := cbr.condBrLabel() + offsetOfTarget := m.labelPositionPool.Get(int(target)).binaryOffset + diff := offsetOfTarget - offset + if divided := diff >> 2; divided < minSignedInt19 || divided > maxSignedInt19 { + // This case the conditional branch is too huge. We place the trampoline instructions at the end of the current block, + // and jump to it. + m.insertConditionalJumpTrampoline(cbr, reloc.currentLabelPos, reloc.nextLabel) + // Then, we need to recall this function to fix up the label offsets + // as they have changed after the trampoline is inserted. + needRerun = true + } + } + if needRerun { + if wazevoapi.PerfMapEnabled { + wazevoapi.PerfMap.Clear() + } + } else { + break + } + } + + var currentOffset int64 + for cur := m.rootInstr; cur != nil; cur = cur.next { + switch cur.kind { + case br: + target := cur.brLabel() + offsetOfTarget := m.labelPositionPool.Get(int(target)).binaryOffset + diff := offsetOfTarget - currentOffset + divided := diff >> 2 + if divided < minSignedInt26 || divided > maxSignedInt26 { + // This means the currently compiled single function is extremely large. + panic("too large function that requires branch relocation of large unconditional branch larger than 26-bit range") + } + cur.brOffsetResolve(diff) + case condBr: + if !cur.condBrOffsetResolved() { + target := cur.condBrLabel() + offsetOfTarget := m.labelPositionPool.Get(int(target)).binaryOffset + diff := offsetOfTarget - currentOffset + if divided := diff >> 2; divided < minSignedInt19 || divided > maxSignedInt19 { + panic("BUG: branch relocation for large conditional branch larger than 19-bit range must be handled properly") + } + cur.condBrOffsetResolve(diff) + } + case brTableSequence: + tableIndex := cur.u1 + targets := m.jmpTableTargets[tableIndex] + for i := range targets { + l := label(targets[i]) + offsetOfTarget := m.labelPositionPool.Get(int(l)).binaryOffset + diff := offsetOfTarget - (currentOffset + brTableSequenceOffsetTableBegin) + targets[i] = uint32(diff) + } + cur.brTableSequenceOffsetsResolved() + case emitSourceOffsetInfo: + m.compiler.AddSourceOffsetInfo(currentOffset, cur.sourceOffsetInfo()) + } + currentOffset += cur.size() + } +} + +const ( + maxSignedInt26 = 1<<25 - 1 + minSignedInt26 = -(1 << 25) + + maxSignedInt19 = 1<<18 - 1 + minSignedInt19 = -(1 << 18) +) + +func (m *machine) insertConditionalJumpTrampoline(cbr *instruction, currentBlk *labelPosition, nextLabel label) { + cur := currentBlk.end + originalTarget := cbr.condBrLabel() + endNext := cur.next + + if cur.kind != br { + // If the current block ends with a conditional branch, we can just insert the trampoline after it. + // Otherwise, we need to insert "skip" instruction to skip the trampoline instructions. + skip := m.allocateInstr() + skip.asBr(nextLabel) + cur = linkInstr(cur, skip) + } + + cbrNewTargetInstr, cbrNewTargetLabel := m.allocateBrTarget() + cbr.setCondBrTargets(cbrNewTargetLabel) + cur = linkInstr(cur, cbrNewTargetInstr) + + // Then insert the unconditional branch to the original, which should be possible to get encoded + // as 26-bit offset should be enough for any practical application. + br := m.allocateInstr() + br.asBr(originalTarget) + cur = linkInstr(cur, br) + + // Update the end of the current block. + currentBlk.end = cur + + linkInstr(cur, endNext) +} + +// Format implements backend.Machine. +func (m *machine) Format() string { + begins := map[*instruction]label{} + for l := label(0); l < m.nextLabel; l++ { + pos := m.labelPositionPool.Get(int(l)) + if pos != nil { + begins[pos.begin] = l + } + } + + var lines []string + for cur := m.rootInstr; cur != nil; cur = cur.next { + if l, ok := begins[cur]; ok { + var labelStr string + if l <= m.maxSSABlockID { + labelStr = fmt.Sprintf("%s (SSA Block: blk%d):", l, int(l)) + } else { + labelStr = fmt.Sprintf("%s:", l) + } + lines = append(lines, labelStr) + } + if cur.kind == nop0 { + continue + } + lines = append(lines, "\t"+cur.String()) + } + return "\n" + strings.Join(lines, "\n") + "\n" +} + +// InsertReturn implements backend.Machine. +func (m *machine) InsertReturn() { + i := m.allocateInstr() + i.asRet() + m.insert(i) +} + +func (m *machine) getVRegSpillSlotOffsetFromSP(id regalloc.VRegID, size byte) int64 { + offset, ok := m.spillSlots[id] + if !ok { + offset = m.spillSlotSize + // TODO: this should be aligned depending on the `size` to use Imm12 offset load/store as much as possible. + m.spillSlots[id] = offset + m.spillSlotSize += int64(size) + } + return offset + 16 // spill slot starts above the clobbered registers and the frame size. +} + +func (m *machine) clobberedRegSlotSize() int64 { + return int64(len(m.clobberedRegs) * 16) +} + +func (m *machine) arg0OffsetFromSP() int64 { + return m.frameSize() + + 16 + // 16-byte aligned return address + 16 // frame size saved below the clobbered registers. +} + +func (m *machine) ret0OffsetFromSP() int64 { + return m.arg0OffsetFromSP() + m.currentABI.ArgStackSize +} + +func (m *machine) requiredStackSize() int64 { + return m.maxRequiredStackSizeForCalls + + m.frameSize() + + 16 + // 16-byte aligned return address. + 16 // frame size saved below the clobbered registers. +} + +func (m *machine) frameSize() int64 { + s := m.clobberedRegSlotSize() + m.spillSlotSize + if s&0xf != 0 { + panic(fmt.Errorf("BUG: frame size %d is not 16-byte aligned", s)) + } + return s +} + +func (m *machine) addJmpTableTarget(targets ssa.Values) (index int) { + if m.jmpTableTargetsNext == len(m.jmpTableTargets) { + m.jmpTableTargets = append(m.jmpTableTargets, make([]uint32, 0, len(targets.View()))) + } + + index = m.jmpTableTargetsNext + m.jmpTableTargetsNext++ + m.jmpTableTargets[index] = m.jmpTableTargets[index][:0] + for _, targetBlockID := range targets.View() { + target := m.compiler.SSABuilder().BasicBlock(ssa.BasicBlockID(targetBlockID)) + m.jmpTableTargets[index] = append(m.jmpTableTargets[index], uint32(target.ID())) + } + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue.go new file mode 100644 index 00000000000..16d0746e581 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue.go @@ -0,0 +1,486 @@ +package arm64 + +import ( + "fmt" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +// PostRegAlloc implements backend.Machine. +func (m *machine) PostRegAlloc() { + m.setupPrologue() + m.postRegAlloc() +} + +// setupPrologue initializes the prologue of the function. +func (m *machine) setupPrologue() { + cur := m.rootInstr + prevInitInst := cur.next + + // + // (high address) (high address) + // SP----> +-----------------+ +------------------+ <----+ + // | ....... | | ....... | | + // | ret Y | | ret Y | | + // | ....... | | ....... | | + // | ret 0 | | ret 0 | | + // | arg X | | arg X | | size_of_arg_ret. + // | ....... | ====> | ....... | | + // | arg 1 | | arg 1 | | + // | arg 0 | | arg 0 | <----+ + // |-----------------| | size_of_arg_ret | + // | return address | + // +------------------+ <---- SP + // (low address) (low address) + + // Saves the return address (lr) and the size_of_arg_ret below the SP. + // size_of_arg_ret is used for stack unwinding. + cur = m.createReturnAddrAndSizeOfArgRetSlot(cur) + + if !m.stackBoundsCheckDisabled { + cur = m.insertStackBoundsCheck(m.requiredStackSize(), cur) + } + + // Decrement SP if spillSlotSize > 0. + if m.spillSlotSize == 0 && len(m.spillSlots) != 0 { + panic(fmt.Sprintf("BUG: spillSlotSize=%d, spillSlots=%v\n", m.spillSlotSize, m.spillSlots)) + } + + if regs := m.clobberedRegs; len(regs) > 0 { + // + // (high address) (high address) + // +-----------------+ +-----------------+ + // | ....... | | ....... | + // | ret Y | | ret Y | + // | ....... | | ....... | + // | ret 0 | | ret 0 | + // | arg X | | arg X | + // | ....... | | ....... | + // | arg 1 | | arg 1 | + // | arg 0 | | arg 0 | + // | size_of_arg_ret | | size_of_arg_ret | + // | ReturnAddress | | ReturnAddress | + // SP----> +-----------------+ ====> +-----------------+ + // (low address) | clobbered M | + // | ............ | + // | clobbered 0 | + // +-----------------+ <----- SP + // (low address) + // + _amode := addressModePreOrPostIndex(m, spVReg, + -16, // stack pointer must be 16-byte aligned. + true, // Decrement before store. + ) + for _, vr := range regs { + // TODO: pair stores to reduce the number of instructions. + store := m.allocateInstr() + store.asStore(operandNR(vr), _amode, regTypeToRegisterSizeInBits(vr.RegType())) + cur = linkInstr(cur, store) + } + } + + if size := m.spillSlotSize; size > 0 { + // Check if size is 16-byte aligned. + if size&0xf != 0 { + panic(fmt.Errorf("BUG: spill slot size %d is not 16-byte aligned", size)) + } + + cur = m.addsAddOrSubStackPointer(cur, spVReg, size, false) + + // At this point, the stack looks like: + // + // (high address) + // +------------------+ + // | ....... | + // | ret Y | + // | ....... | + // | ret 0 | + // | arg X | + // | ....... | + // | arg 1 | + // | arg 0 | + // | size_of_arg_ret | + // | ReturnAddress | + // +------------------+ + // | clobbered M | + // | ............ | + // | clobbered 0 | + // | spill slot N | + // | ............ | + // | spill slot 2 | + // | spill slot 0 | + // SP----> +------------------+ + // (low address) + } + + // We push the frame size into the stack to make it possible to unwind stack: + // + // + // (high address) (high address) + // +-----------------+ +-----------------+ + // | ....... | | ....... | + // | ret Y | | ret Y | + // | ....... | | ....... | + // | ret 0 | | ret 0 | + // | arg X | | arg X | + // | ....... | | ....... | + // | arg 1 | | arg 1 | + // | arg 0 | | arg 0 | + // | size_of_arg_ret | | size_of_arg_ret | + // | ReturnAddress | | ReturnAddress | + // +-----------------+ ==> +-----------------+ <----+ + // | clobbered M | | clobbered M | | + // | ............ | | ............ | | + // | clobbered 2 | | clobbered 2 | | + // | clobbered 1 | | clobbered 1 | | frame size + // | clobbered 0 | | clobbered 0 | | + // | spill slot N | | spill slot N | | + // | ............ | | ............ | | + // | spill slot 0 | | spill slot 0 | <----+ + // SP---> +-----------------+ | xxxxxx | ;; unused space to make it 16-byte aligned. + // | frame_size | + // +-----------------+ <---- SP + // (low address) + // + cur = m.createFrameSizeSlot(cur, m.frameSize()) + + linkInstr(cur, prevInitInst) +} + +func (m *machine) createReturnAddrAndSizeOfArgRetSlot(cur *instruction) *instruction { + // First we decrement the stack pointer to point the arg0 slot. + var sizeOfArgRetReg regalloc.VReg + s := int64(m.currentABI.AlignedArgResultStackSlotSize()) + if s > 0 { + cur = m.lowerConstantI64AndInsert(cur, tmpRegVReg, s) + sizeOfArgRetReg = tmpRegVReg + + subSp := m.allocateInstr() + subSp.asALU(aluOpSub, spVReg, operandNR(spVReg), operandNR(sizeOfArgRetReg), true) + cur = linkInstr(cur, subSp) + } else { + sizeOfArgRetReg = xzrVReg + } + + // Saves the return address (lr) and the size_of_arg_ret below the SP. + // size_of_arg_ret is used for stack unwinding. + pstr := m.allocateInstr() + amode := addressModePreOrPostIndex(m, spVReg, -16, true /* decrement before store */) + pstr.asStorePair64(lrVReg, sizeOfArgRetReg, amode) + cur = linkInstr(cur, pstr) + return cur +} + +func (m *machine) createFrameSizeSlot(cur *instruction, s int64) *instruction { + var frameSizeReg regalloc.VReg + if s > 0 { + cur = m.lowerConstantI64AndInsert(cur, tmpRegVReg, s) + frameSizeReg = tmpRegVReg + } else { + frameSizeReg = xzrVReg + } + _amode := addressModePreOrPostIndex(m, spVReg, + -16, // stack pointer must be 16-byte aligned. + true, // Decrement before store. + ) + store := m.allocateInstr() + store.asStore(operandNR(frameSizeReg), _amode, 64) + cur = linkInstr(cur, store) + return cur +} + +// postRegAlloc does multiple things while walking through the instructions: +// 1. Removes the redundant copy instruction. +// 2. Inserts the epilogue. +func (m *machine) postRegAlloc() { + for cur := m.rootInstr; cur != nil; cur = cur.next { + switch cur.kind { + case ret: + m.setupEpilogueAfter(cur.prev) + case tailCall, tailCallInd: + m.setupEpilogueAfter(cur.prev) + // If this has been encoded as a proper tail call, we can remove the trailing instructions. + // For details, see internal/engine/RATIONALE.md + m.removeUntilRet(cur.next) + case loadConstBlockArg: + lc := cur + next := lc.next + m.pendingInstructions = m.pendingInstructions[:0] + m.lowerLoadConstantBlockArgAfterRegAlloc(lc) + for _, instr := range m.pendingInstructions { + cur = linkInstr(cur, instr) + } + linkInstr(cur, next) + m.pendingInstructions = m.pendingInstructions[:0] + default: + // Removes the redundant copy instruction. + if cur.IsCopy() && cur.rn.realReg() == cur.rd.RealReg() { + prev, next := cur.prev, cur.next + // Remove the copy instruction. + prev.next = next + if next != nil { + next.prev = prev + } + } + } + } +} + +func (m *machine) setupEpilogueAfter(cur *instruction) { + prevNext := cur.next + + // We've stored the frame size in the prologue, and now that we are about to return from this function, we won't need it anymore. + cur = m.addsAddOrSubStackPointer(cur, spVReg, 16, true) + + if s := m.spillSlotSize; s > 0 { + // Adjust SP to the original value: + // + // (high address) (high address) + // +-----------------+ +-----------------+ + // | ....... | | ....... | + // | ret Y | | ret Y | + // | ....... | | ....... | + // | ret 0 | | ret 0 | + // | arg X | | arg X | + // | ....... | | ....... | + // | arg 1 | | arg 1 | + // | arg 0 | | arg 0 | + // | xxxxx | | xxxxx | + // | ReturnAddress | | ReturnAddress | + // +-----------------+ ====> +-----------------+ + // | clobbered M | | clobbered M | + // | ............ | | ............ | + // | clobbered 1 | | clobbered 1 | + // | clobbered 0 | | clobbered 0 | + // | spill slot N | +-----------------+ <---- SP + // | ............ | + // | spill slot 0 | + // SP---> +-----------------+ + // (low address) + // + cur = m.addsAddOrSubStackPointer(cur, spVReg, s, true) + } + + // First we need to restore the clobbered registers. + if len(m.clobberedRegs) > 0 { + // (high address) + // +-----------------+ +-----------------+ + // | ....... | | ....... | + // | ret Y | | ret Y | + // | ....... | | ....... | + // | ret 0 | | ret 0 | + // | arg X | | arg X | + // | ....... | | ....... | + // | arg 1 | | arg 1 | + // | arg 0 | | arg 0 | + // | xxxxx | | xxxxx | + // | ReturnAddress | | ReturnAddress | + // +-----------------+ ========> +-----------------+ <---- SP + // | clobbered M | + // | ........... | + // | clobbered 1 | + // | clobbered 0 | + // SP---> +-----------------+ + // (low address) + + l := len(m.clobberedRegs) - 1 + for i := range m.clobberedRegs { + vr := m.clobberedRegs[l-i] // reverse order to restore. + load := m.allocateInstr() + amode := addressModePreOrPostIndex(m, spVReg, + 16, // stack pointer must be 16-byte aligned. + false, // Increment after store. + ) + // TODO: pair loads to reduce the number of instructions. + switch regTypeToRegisterSizeInBits(vr.RegType()) { + case 64: // save int reg. + load.asULoad(vr, amode, 64) + case 128: // save vector reg. + load.asFpuLoad(vr, amode, 128) + } + cur = linkInstr(cur, load) + } + } + + // Reload the return address (lr). + // + // +-----------------+ +-----------------+ + // | ....... | | ....... | + // | ret Y | | ret Y | + // | ....... | | ....... | + // | ret 0 | | ret 0 | + // | arg X | | arg X | + // | ....... | ===> | ....... | + // | arg 1 | | arg 1 | + // | arg 0 | | arg 0 | + // | xxxxx | +-----------------+ <---- SP + // | ReturnAddress | + // SP----> +-----------------+ + + ldr := m.allocateInstr() + ldr.asULoad(lrVReg, + addressModePreOrPostIndex(m, spVReg, 16 /* stack pointer must be 16-byte aligned. */, false /* increment after loads */), 64) + cur = linkInstr(cur, ldr) + + if s := int64(m.currentABI.AlignedArgResultStackSlotSize()); s > 0 { + cur = m.addsAddOrSubStackPointer(cur, spVReg, s, true) + } + + linkInstr(cur, prevNext) +} + +// removeUntilRet removes the instructions starting from `cur` until the first `ret` instruction. +func (m *machine) removeUntilRet(cur *instruction) { + for ; cur != nil; cur = cur.next { + prev, next := cur.prev, cur.next + prev.next = next + if next != nil { + next.prev = prev + } + if cur.kind == ret { + return + } + } +} + +// saveRequiredRegs is the set of registers that must be saved/restored during growing stack when there's insufficient +// stack space left. Basically this is the combination of CalleeSavedRegisters plus argument registers execpt for x0, +// which always points to the execution context whenever the native code is entered from Go. +var saveRequiredRegs = []regalloc.VReg{ + x1VReg, x2VReg, x3VReg, x4VReg, x5VReg, x6VReg, x7VReg, + x19VReg, x20VReg, x21VReg, x22VReg, x23VReg, x24VReg, x25VReg, x26VReg, x28VReg, lrVReg, + v0VReg, v1VReg, v2VReg, v3VReg, v4VReg, v5VReg, v6VReg, v7VReg, + v18VReg, v19VReg, v20VReg, v21VReg, v22VReg, v23VReg, v24VReg, v25VReg, v26VReg, v27VReg, v28VReg, v29VReg, v30VReg, v31VReg, +} + +// insertStackBoundsCheck will insert the instructions after `cur` to check the +// stack bounds, and if there's no sufficient spaces required for the function, +// exit the execution and try growing it in Go world. +// +// TODO: we should be able to share the instructions across all the functions to reduce the size of compiled executable. +func (m *machine) insertStackBoundsCheck(requiredStackSize int64, cur *instruction) *instruction { + if requiredStackSize%16 != 0 { + panic("BUG") + } + + if immm12op, ok := asImm12Operand(uint64(requiredStackSize)); ok { + // sub tmp, sp, #requiredStackSize + sub := m.allocateInstr() + sub.asALU(aluOpSub, tmpRegVReg, operandNR(spVReg), immm12op, true) + cur = linkInstr(cur, sub) + } else { + // This case, we first load the requiredStackSize into the temporary register, + cur = m.lowerConstantI64AndInsert(cur, tmpRegVReg, requiredStackSize) + // Then subtract it. + sub := m.allocateInstr() + sub.asALU(aluOpSub, tmpRegVReg, operandNR(spVReg), operandNR(tmpRegVReg), true) + cur = linkInstr(cur, sub) + } + + tmp2 := x11VReg // Caller save, so it is safe to use it here in the prologue. + + // ldr tmp2, [executionContext #StackBottomPtr] + ldr := m.allocateInstr() + amode := m.amodePool.Allocate() + *amode = addressMode{ + kind: addressModeKindRegUnsignedImm12, + rn: x0VReg, // execution context is always the first argument. + imm: wazevoapi.ExecutionContextOffsetStackBottomPtr.I64(), + } + ldr.asULoad(tmp2, amode, 64) + cur = linkInstr(cur, ldr) + + // subs xzr, tmp, tmp2 + subs := m.allocateInstr() + subs.asALU(aluOpSubS, xzrVReg, operandNR(tmpRegVReg), operandNR(tmp2), true) + cur = linkInstr(cur, subs) + + // b.ge #imm + cbr := m.allocateInstr() + cbr.asCondBr(ge.asCond(), labelInvalid, false /* ignored */) + cur = linkInstr(cur, cbr) + + // Set the required stack size and set it to the exec context. + { + // First load the requiredStackSize into the temporary register, + cur = m.lowerConstantI64AndInsert(cur, tmpRegVReg, requiredStackSize) + setRequiredStackSize := m.allocateInstr() + amode := m.amodePool.Allocate() + *amode = addressMode{ + kind: addressModeKindRegUnsignedImm12, + // Execution context is always the first argument. + rn: x0VReg, imm: wazevoapi.ExecutionContextOffsetStackGrowRequiredSize.I64(), + } + setRequiredStackSize.asStore(operandNR(tmpRegVReg), amode, 64) + + cur = linkInstr(cur, setRequiredStackSize) + } + + ldrAddress := m.allocateInstr() + amode2 := m.amodePool.Allocate() + *amode2 = addressMode{ + kind: addressModeKindRegUnsignedImm12, + rn: x0VReg, // execution context is always the first argument + imm: wazevoapi.ExecutionContextOffsetStackGrowCallTrampolineAddress.I64(), + } + ldrAddress.asULoad(tmpRegVReg, amode2, 64) + cur = linkInstr(cur, ldrAddress) + + // Then jumps to the stack grow call sequence's address, meaning + // transferring the control to the code compiled by CompileStackGrowCallSequence. + bl := m.allocateInstr() + bl.asCallIndirect(tmpRegVReg, nil) + cur = linkInstr(cur, bl) + + // Now that we know the entire code, we can finalize how many bytes + // we have to skip when the stack size is sufficient. + var cbrOffset int64 + for _cur := cbr; ; _cur = _cur.next { + cbrOffset += _cur.size() + if _cur == cur { + break + } + } + cbr.condBrOffsetResolve(cbrOffset) + return cur +} + +// CompileStackGrowCallSequence implements backend.Machine. +func (m *machine) CompileStackGrowCallSequence() []byte { + cur := m.allocateInstr() + cur.asNop0() + m.rootInstr = cur + + // Save the callee saved and argument registers. + cur = m.saveRegistersInExecutionContext(cur, saveRequiredRegs) + + // Save the current stack pointer. + cur = m.saveCurrentStackPointer(cur, x0VReg) + + // Set the exit status on the execution context. + cur = m.setExitCode(cur, x0VReg, wazevoapi.ExitCodeGrowStack) + + // Exit the execution. + cur = m.storeReturnAddressAndExit(cur) + + // After the exit, restore the saved registers. + cur = m.restoreRegistersInExecutionContext(cur, saveRequiredRegs) + + // Then goes back the original address of this stack grow call. + ret := m.allocateInstr() + ret.asRet() + linkInstr(cur, ret) + + m.encode(m.rootInstr) + return m.compiler.Buf() +} + +func (m *machine) addsAddOrSubStackPointer(cur *instruction, rd regalloc.VReg, diff int64, add bool) *instruction { + m.pendingInstructions = m.pendingInstructions[:0] + m.insertAddOrSubStackPointer(rd, diff, add) + for _, inserted := range m.pendingInstructions { + cur = linkInstr(cur, inserted) + } + return cur +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine_regalloc.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine_regalloc.go new file mode 100644 index 00000000000..f2ed53ae559 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine_regalloc.go @@ -0,0 +1,351 @@ +package arm64 + +// This file implements the interfaces required for register allocations. See backend.RegAllocFunctionMachine. + +import ( + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" +) + +// regAllocFn implements regalloc.Function. +type regAllocFn struct { + ssaB ssa.Builder + m *machine + loopNestingForestRoots []ssa.BasicBlock + blockIter int +} + +// PostOrderBlockIteratorBegin implements regalloc.Function. +func (f *regAllocFn) PostOrderBlockIteratorBegin() *labelPosition { + f.blockIter = len(f.m.orderedSSABlockLabelPos) - 1 + return f.PostOrderBlockIteratorNext() +} + +// PostOrderBlockIteratorNext implements regalloc.Function. +func (f *regAllocFn) PostOrderBlockIteratorNext() *labelPosition { + if f.blockIter < 0 { + return nil + } + b := f.m.orderedSSABlockLabelPos[f.blockIter] + f.blockIter-- + return b +} + +// ReversePostOrderBlockIteratorBegin implements regalloc.Function. +func (f *regAllocFn) ReversePostOrderBlockIteratorBegin() *labelPosition { + f.blockIter = 0 + return f.ReversePostOrderBlockIteratorNext() +} + +// ReversePostOrderBlockIteratorNext implements regalloc.Function. +func (f *regAllocFn) ReversePostOrderBlockIteratorNext() *labelPosition { + if f.blockIter >= len(f.m.orderedSSABlockLabelPos) { + return nil + } + b := f.m.orderedSSABlockLabelPos[f.blockIter] + f.blockIter++ + return b +} + +// ClobberedRegisters implements regalloc.Function. +func (f *regAllocFn) ClobberedRegisters(regs []regalloc.VReg) { + f.m.clobberedRegs = append(f.m.clobberedRegs[:0], regs...) +} + +// LoopNestingForestRoots implements regalloc.Function. +func (f *regAllocFn) LoopNestingForestRoots() int { + f.loopNestingForestRoots = f.ssaB.LoopNestingForestRoots() + return len(f.loopNestingForestRoots) +} + +// LoopNestingForestRoot implements regalloc.Function. +func (f *regAllocFn) LoopNestingForestRoot(i int) *labelPosition { + root := f.loopNestingForestRoots[i] + pos := f.m.getOrAllocateSSABlockLabelPosition(root) + return pos +} + +// LowestCommonAncestor implements regalloc.Function. +func (f *regAllocFn) LowestCommonAncestor(blk1, blk2 *labelPosition) *labelPosition { + sb := f.ssaB.LowestCommonAncestor(blk1.sb, blk2.sb) + pos := f.m.getOrAllocateSSABlockLabelPosition(sb) + return pos +} + +// Idom implements regalloc.Function. +func (f *regAllocFn) Idom(blk *labelPosition) *labelPosition { + sb := f.ssaB.Idom(blk.sb) + pos := f.m.getOrAllocateSSABlockLabelPosition(sb) + return pos +} + +// SwapBefore implements regalloc.Function. +func (f *regAllocFn) SwapBefore(x1, x2, tmp regalloc.VReg, instr *instruction) { + f.m.swap(instr.prev, x1, x2, tmp) +} + +// StoreRegisterBefore implements regalloc.Function. +func (f *regAllocFn) StoreRegisterBefore(v regalloc.VReg, instr *instruction) { + m := f.m + m.insertStoreRegisterAt(v, instr, false) +} + +// StoreRegisterAfter implements regalloc.Function. +func (f *regAllocFn) StoreRegisterAfter(v regalloc.VReg, instr *instruction) { + m := f.m + m.insertStoreRegisterAt(v, instr, true) +} + +// ReloadRegisterBefore implements regalloc.Function. +func (f *regAllocFn) ReloadRegisterBefore(v regalloc.VReg, instr *instruction) { + m := f.m + m.insertReloadRegisterAt(v, instr, false) +} + +// ReloadRegisterAfter implements regalloc.Function. +func (f *regAllocFn) ReloadRegisterAfter(v regalloc.VReg, instr *instruction) { + m := f.m + m.insertReloadRegisterAt(v, instr, true) +} + +// InsertMoveBefore implements regalloc.Function. +func (f *regAllocFn) InsertMoveBefore(dst, src regalloc.VReg, instr *instruction) { + f.m.insertMoveBefore(dst, src, instr) +} + +// LoopNestingForestChild implements regalloc.Function. +func (f *regAllocFn) LoopNestingForestChild(pos *labelPosition, i int) *labelPosition { + childSB := pos.sb.LoopNestingForestChildren()[i] + return f.m.getOrAllocateSSABlockLabelPosition(childSB) +} + +// Succ implements regalloc.Block. +func (f *regAllocFn) Succ(pos *labelPosition, i int) *labelPosition { + succSB := pos.sb.Succ(i) + if succSB.ReturnBlock() { + return nil + } + return f.m.getOrAllocateSSABlockLabelPosition(succSB) +} + +// Pred implements regalloc.Block. +func (f *regAllocFn) Pred(pos *labelPosition, i int) *labelPosition { + predSB := pos.sb.Pred(i) + return f.m.getOrAllocateSSABlockLabelPosition(predSB) +} + +// BlockParams implements regalloc.Function. +func (f *regAllocFn) BlockParams(pos *labelPosition, regs *[]regalloc.VReg) []regalloc.VReg { + c := f.m.compiler + *regs = (*regs)[:0] + for i := 0; i < pos.sb.Params(); i++ { + v := c.VRegOf(pos.sb.Param(i)) + *regs = append(*regs, v) + } + return *regs +} + +// ID implements regalloc.Block. +func (pos *labelPosition) ID() int32 { + return int32(pos.sb.ID()) +} + +// InstrIteratorBegin implements regalloc.Block. +func (pos *labelPosition) InstrIteratorBegin() *instruction { + ret := pos.begin + pos.cur = ret + return ret +} + +// InstrIteratorNext implements regalloc.Block. +func (pos *labelPosition) InstrIteratorNext() *instruction { + for { + if pos.cur == pos.end { + return nil + } + instr := pos.cur.next + pos.cur = instr + if instr == nil { + return nil + } else if instr.addedBeforeRegAlloc { + // Only concerned about the instruction added before regalloc. + return instr + } + } +} + +// InstrRevIteratorBegin implements regalloc.Block. +func (pos *labelPosition) InstrRevIteratorBegin() *instruction { + pos.cur = pos.end + return pos.cur +} + +// InstrRevIteratorNext implements regalloc.Block. +func (pos *labelPosition) InstrRevIteratorNext() *instruction { + for { + if pos.cur == pos.begin { + return nil + } + instr := pos.cur.prev + pos.cur = instr + if instr == nil { + return nil + } else if instr.addedBeforeRegAlloc { + // Only concerned about the instruction added before regalloc. + return instr + } + } +} + +// FirstInstr implements regalloc.Block. +func (pos *labelPosition) FirstInstr() *instruction { return pos.begin } + +// LastInstrForInsertion implements regalloc.Block. +func (pos *labelPosition) LastInstrForInsertion() *instruction { + return lastInstrForInsertion(pos.begin, pos.end) +} + +// Preds implements regalloc.Block. +func (pos *labelPosition) Preds() int { return pos.sb.Preds() } + +// Entry implements regalloc.Block. +func (pos *labelPosition) Entry() bool { return pos.sb.EntryBlock() } + +// Succs implements regalloc.Block. +func (pos *labelPosition) Succs() int { return pos.sb.Succs() } + +// LoopHeader implements regalloc.Block. +func (pos *labelPosition) LoopHeader() bool { return pos.sb.LoopHeader() } + +// LoopNestingForestChildren implements regalloc.Block. +func (pos *labelPosition) LoopNestingForestChildren() int { + return len(pos.sb.LoopNestingForestChildren()) +} + +func (m *machine) swap(cur *instruction, x1, x2, tmp regalloc.VReg) { + prevNext := cur.next + var mov1, mov2, mov3 *instruction + if x1.RegType() == regalloc.RegTypeInt { + if !tmp.Valid() { + tmp = tmpRegVReg + } + mov1 = m.allocateInstr().asMove64(tmp, x1) + mov2 = m.allocateInstr().asMove64(x1, x2) + mov3 = m.allocateInstr().asMove64(x2, tmp) + cur = linkInstr(cur, mov1) + cur = linkInstr(cur, mov2) + cur = linkInstr(cur, mov3) + linkInstr(cur, prevNext) + } else { + if !tmp.Valid() { + r2 := x2.RealReg() + // Temporarily spill x1 to stack. + cur = m.insertStoreRegisterAt(x1, cur, true).prev + // Then move x2 to x1. + cur = linkInstr(cur, m.allocateInstr().asFpuMov128(x1, x2)) + linkInstr(cur, prevNext) + // Then reload the original value on x1 from stack to r2. + m.insertReloadRegisterAt(x1.SetRealReg(r2), cur, true) + } else { + mov1 = m.allocateInstr().asFpuMov128(tmp, x1) + mov2 = m.allocateInstr().asFpuMov128(x1, x2) + mov3 = m.allocateInstr().asFpuMov128(x2, tmp) + cur = linkInstr(cur, mov1) + cur = linkInstr(cur, mov2) + cur = linkInstr(cur, mov3) + linkInstr(cur, prevNext) + } + } +} + +func (m *machine) insertMoveBefore(dst, src regalloc.VReg, instr *instruction) { + typ := src.RegType() + if typ != dst.RegType() { + panic("BUG: src and dst must have the same type") + } + + mov := m.allocateInstr() + if typ == regalloc.RegTypeInt { + mov.asMove64(dst, src) + } else { + mov.asFpuMov128(dst, src) + } + + cur := instr.prev + prevNext := cur.next + cur = linkInstr(cur, mov) + linkInstr(cur, prevNext) +} + +func (m *machine) insertStoreRegisterAt(v regalloc.VReg, instr *instruction, after bool) *instruction { + if !v.IsRealReg() { + panic("BUG: VReg must be backed by real reg to be stored") + } + + typ := m.compiler.TypeOf(v) + + var prevNext, cur *instruction + if after { + cur, prevNext = instr, instr.next + } else { + cur, prevNext = instr.prev, instr + } + + offsetFromSP := m.getVRegSpillSlotOffsetFromSP(v.ID(), typ.Size()) + var amode *addressMode + cur, amode = m.resolveAddressModeForOffsetAndInsert(cur, offsetFromSP, typ.Bits(), spVReg, true) + store := m.allocateInstr() + store.asStore(operandNR(v), amode, typ.Bits()) + + cur = linkInstr(cur, store) + return linkInstr(cur, prevNext) +} + +func (m *machine) insertReloadRegisterAt(v regalloc.VReg, instr *instruction, after bool) *instruction { + if !v.IsRealReg() { + panic("BUG: VReg must be backed by real reg to be stored") + } + + typ := m.compiler.TypeOf(v) + + var prevNext, cur *instruction + if after { + cur, prevNext = instr, instr.next + } else { + cur, prevNext = instr.prev, instr + } + + offsetFromSP := m.getVRegSpillSlotOffsetFromSP(v.ID(), typ.Size()) + var amode *addressMode + cur, amode = m.resolveAddressModeForOffsetAndInsert(cur, offsetFromSP, typ.Bits(), spVReg, true) + load := m.allocateInstr() + switch typ { + case ssa.TypeI32, ssa.TypeI64: + load.asULoad(v, amode, typ.Bits()) + case ssa.TypeF32, ssa.TypeF64: + load.asFpuLoad(v, amode, typ.Bits()) + case ssa.TypeV128: + load.asFpuLoad(v, amode, 128) + default: + panic("TODO") + } + + cur = linkInstr(cur, load) + return linkInstr(cur, prevNext) +} + +func lastInstrForInsertion(begin, end *instruction) *instruction { + cur := end + for cur.kind == nop0 { + cur = cur.prev + if cur == begin { + return end + } + } + switch cur.kind { + case br: + return cur + default: + return end + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine_relocation.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine_relocation.go new file mode 100644 index 00000000000..9bb4dee15ce --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine_relocation.go @@ -0,0 +1,128 @@ +package arm64 + +import ( + "encoding/binary" + "fmt" + "math" + "sort" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" +) + +const ( + // trampolineCallSize is the size of the trampoline instruction sequence for each function in an island. + trampolineCallSize = 4*4 + 4 // Four instructions + 32-bit immediate. + + // Unconditional branch offset is encoded as divided by 4 in imm26. + // https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/BL--Branch-with-Link-?lang=en + + maxUnconditionalBranchOffset = maxSignedInt26 * 4 + minUnconditionalBranchOffset = minSignedInt26 * 4 + + // trampolineIslandInterval is the range of the trampoline island. + // Half of the range is used for the trampoline island, and the other half is used for the function. + trampolineIslandInterval = (maxUnconditionalBranchOffset - 1) / 2 + + // maxNumFunctions explicitly specifies the maximum number of functions that can be allowed in a single executable. + maxNumFunctions = trampolineIslandInterval >> 6 + + // maxFunctionExecutableSize is the maximum size of a function that can exist in a trampoline island. + // Conservatively set to 1/4 of the trampoline island interval. + maxFunctionExecutableSize = trampolineIslandInterval >> 2 +) + +// CallTrampolineIslandInfo implements backend.Machine CallTrampolineIslandInfo. +func (m *machine) CallTrampolineIslandInfo(numFunctions int) (interval, size int, err error) { + if numFunctions > maxNumFunctions { + return 0, 0, fmt.Errorf("too many functions: %d > %d", numFunctions, maxNumFunctions) + } + return trampolineIslandInterval, trampolineCallSize * numFunctions, nil +} + +// ResolveRelocations implements backend.Machine ResolveRelocations. +func (m *machine) ResolveRelocations( + refToBinaryOffset []int, + importedFns int, + executable []byte, + relocations []backend.RelocationInfo, + callTrampolineIslandOffsets []int, +) { + for _, islandOffset := range callTrampolineIslandOffsets { + encodeCallTrampolineIsland(refToBinaryOffset, importedFns, islandOffset, executable) + } + + for _, r := range relocations { + instrOffset := r.Offset + calleeFnOffset := refToBinaryOffset[r.FuncRef] + diff := int64(calleeFnOffset) - (instrOffset) + // Check if the diff is within the range of the branch instruction. + if diff < minUnconditionalBranchOffset || diff > maxUnconditionalBranchOffset { + // Find the near trampoline island from callTrampolineIslandOffsets. + islandOffset := searchTrampolineIsland(callTrampolineIslandOffsets, int(instrOffset)) + // Imported functions don't need trampolines, so we ignore them when we compute the offset + // (see also encodeCallTrampolineIsland) + funcOffset := int(r.FuncRef) - importedFns + islandTargetOffset := islandOffset + trampolineCallSize*funcOffset + diff = int64(islandTargetOffset) - (instrOffset) + if diff < minUnconditionalBranchOffset || diff > maxUnconditionalBranchOffset { + panic("BUG in trampoline placement") + } + } + // The unconditional branch instruction is usually encoded as a branch-and-link (BL), + // because it is a function call. However, if the instruction is a tail call, + // we encode it as a plain unconditional branch (B), so we won't overwrite the link register. + binary.LittleEndian.PutUint32(executable[instrOffset:instrOffset+4], encodeUnconditionalBranch(!r.IsTailCall, diff)) + } +} + +// encodeCallTrampolineIsland encodes a trampoline island for the given functions. +// Each island consists of a trampoline instruction sequence for each function. +// Each trampoline instruction sequence consists of 4 instructions + 32-bit immediate. +func encodeCallTrampolineIsland(refToBinaryOffset []int, importedFns int, islandOffset int, executable []byte) { + // We skip the imported functions: they don't need trampolines + // and are not accounted for. + binaryOffsets := refToBinaryOffset[importedFns:] + + for i := 0; i < len(binaryOffsets); i++ { + trampolineOffset := islandOffset + trampolineCallSize*i + + fnOffset := binaryOffsets[i] + diff := fnOffset - (trampolineOffset + 16) + if diff > math.MaxInt32 || diff < math.MinInt32 { + // This case even amd64 can't handle. 4GB is too big. + panic("too big binary") + } + + // The tmpReg, tmpReg2 is safe to overwrite (in fact any caller-saved register is safe to use). + tmpReg, tmpReg2 := regNumberInEncoding[tmpRegVReg.RealReg()], regNumberInEncoding[x11] + + // adr tmpReg, PC+16: load the address of #diff into tmpReg. + binary.LittleEndian.PutUint32(executable[trampolineOffset:], encodeAdr(tmpReg, 16)) + // ldrsw tmpReg2, [tmpReg]: Load #diff into tmpReg2. + binary.LittleEndian.PutUint32(executable[trampolineOffset+4:], + encodeLoadOrStore(sLoad32, tmpReg2, addressMode{kind: addressModeKindRegUnsignedImm12, rn: tmpRegVReg})) + // add tmpReg, tmpReg2, tmpReg: add #diff to the address of #diff, getting the absolute address of the function. + binary.LittleEndian.PutUint32(executable[trampolineOffset+8:], + encodeAluRRR(aluOpAdd, tmpReg, tmpReg, tmpReg2, true, false)) + // br tmpReg: branch to the function without overwriting the link register. + binary.LittleEndian.PutUint32(executable[trampolineOffset+12:], encodeUnconditionalBranchReg(tmpReg, false)) + // #diff + binary.LittleEndian.PutUint32(executable[trampolineOffset+16:], uint32(diff)) + } +} + +// searchTrampolineIsland finds the nearest trampoline island from callTrampolineIslandOffsets. +// Note that even if the offset is in the middle of two islands, it returns the latter one. +// That is ok because the island is always placed in the middle of the range. +// +// precondition: callTrampolineIslandOffsets is sorted in ascending order. +func searchTrampolineIsland(callTrampolineIslandOffsets []int, offset int) int { + l := len(callTrampolineIslandOffsets) + n := sort.Search(l, func(i int) bool { + return callTrampolineIslandOffsets[i] >= offset + }) + if n == l { + n = l - 1 + } + return callTrampolineIslandOffsets[n] +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/reg.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/reg.go new file mode 100644 index 00000000000..9cbac0af78c --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/reg.go @@ -0,0 +1,397 @@ +package arm64 + +import ( + "fmt" + "strconv" + "strings" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" +) + +// Arm64-specific registers. +// +// See https://developer.arm.com/documentation/dui0801/a/Overview-of-AArch64-state/Predeclared-core-register-names-in-AArch64-state + +const ( + // General purpose registers. Note that we do not distinguish wn and xn registers + // because they are the same from the perspective of register allocator, and + // the size can be determined by the type of the instruction. + + x0 = regalloc.RealRegInvalid + 1 + iota + x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + x12 + x13 + x14 + x15 + x16 + x17 + x18 + x19 + x20 + x21 + x22 + x23 + x24 + x25 + x26 + x27 + x28 + x29 + x30 + + // Vector registers. Note that we do not distinguish vn and dn, ... registers + // because they are the same from the perspective of register allocator, and + // the size can be determined by the type of the instruction. + + v0 + v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10 + v11 + v12 + v13 + v14 + v15 + v16 + v17 + v18 + v19 + v20 + v21 + v22 + v23 + v24 + v25 + v26 + v27 + v28 + v29 + v30 + v31 + + // Special registers + + xzr + sp + lr = x30 + fp = x29 + tmp = x27 +) + +var ( + x0VReg = regalloc.FromRealReg(x0, regalloc.RegTypeInt) + x1VReg = regalloc.FromRealReg(x1, regalloc.RegTypeInt) + x2VReg = regalloc.FromRealReg(x2, regalloc.RegTypeInt) + x3VReg = regalloc.FromRealReg(x3, regalloc.RegTypeInt) + x4VReg = regalloc.FromRealReg(x4, regalloc.RegTypeInt) + x5VReg = regalloc.FromRealReg(x5, regalloc.RegTypeInt) + x6VReg = regalloc.FromRealReg(x6, regalloc.RegTypeInt) + x7VReg = regalloc.FromRealReg(x7, regalloc.RegTypeInt) + x8VReg = regalloc.FromRealReg(x8, regalloc.RegTypeInt) + x9VReg = regalloc.FromRealReg(x9, regalloc.RegTypeInt) + x10VReg = regalloc.FromRealReg(x10, regalloc.RegTypeInt) + x11VReg = regalloc.FromRealReg(x11, regalloc.RegTypeInt) + x12VReg = regalloc.FromRealReg(x12, regalloc.RegTypeInt) + x13VReg = regalloc.FromRealReg(x13, regalloc.RegTypeInt) + x14VReg = regalloc.FromRealReg(x14, regalloc.RegTypeInt) + x15VReg = regalloc.FromRealReg(x15, regalloc.RegTypeInt) + x16VReg = regalloc.FromRealReg(x16, regalloc.RegTypeInt) + x17VReg = regalloc.FromRealReg(x17, regalloc.RegTypeInt) + x18VReg = regalloc.FromRealReg(x18, regalloc.RegTypeInt) + x19VReg = regalloc.FromRealReg(x19, regalloc.RegTypeInt) + x20VReg = regalloc.FromRealReg(x20, regalloc.RegTypeInt) + x21VReg = regalloc.FromRealReg(x21, regalloc.RegTypeInt) + x22VReg = regalloc.FromRealReg(x22, regalloc.RegTypeInt) + x23VReg = regalloc.FromRealReg(x23, regalloc.RegTypeInt) + x24VReg = regalloc.FromRealReg(x24, regalloc.RegTypeInt) + x25VReg = regalloc.FromRealReg(x25, regalloc.RegTypeInt) + x26VReg = regalloc.FromRealReg(x26, regalloc.RegTypeInt) + x27VReg = regalloc.FromRealReg(x27, regalloc.RegTypeInt) + x28VReg = regalloc.FromRealReg(x28, regalloc.RegTypeInt) + x29VReg = regalloc.FromRealReg(x29, regalloc.RegTypeInt) + x30VReg = regalloc.FromRealReg(x30, regalloc.RegTypeInt) + v0VReg = regalloc.FromRealReg(v0, regalloc.RegTypeFloat) + v1VReg = regalloc.FromRealReg(v1, regalloc.RegTypeFloat) + v2VReg = regalloc.FromRealReg(v2, regalloc.RegTypeFloat) + v3VReg = regalloc.FromRealReg(v3, regalloc.RegTypeFloat) + v4VReg = regalloc.FromRealReg(v4, regalloc.RegTypeFloat) + v5VReg = regalloc.FromRealReg(v5, regalloc.RegTypeFloat) + v6VReg = regalloc.FromRealReg(v6, regalloc.RegTypeFloat) + v7VReg = regalloc.FromRealReg(v7, regalloc.RegTypeFloat) + v8VReg = regalloc.FromRealReg(v8, regalloc.RegTypeFloat) + v9VReg = regalloc.FromRealReg(v9, regalloc.RegTypeFloat) + v10VReg = regalloc.FromRealReg(v10, regalloc.RegTypeFloat) + v11VReg = regalloc.FromRealReg(v11, regalloc.RegTypeFloat) + v12VReg = regalloc.FromRealReg(v12, regalloc.RegTypeFloat) + v13VReg = regalloc.FromRealReg(v13, regalloc.RegTypeFloat) + v14VReg = regalloc.FromRealReg(v14, regalloc.RegTypeFloat) + v15VReg = regalloc.FromRealReg(v15, regalloc.RegTypeFloat) + v16VReg = regalloc.FromRealReg(v16, regalloc.RegTypeFloat) + v17VReg = regalloc.FromRealReg(v17, regalloc.RegTypeFloat) + v18VReg = regalloc.FromRealReg(v18, regalloc.RegTypeFloat) + v19VReg = regalloc.FromRealReg(v19, regalloc.RegTypeFloat) + v20VReg = regalloc.FromRealReg(v20, regalloc.RegTypeFloat) + v21VReg = regalloc.FromRealReg(v21, regalloc.RegTypeFloat) + v22VReg = regalloc.FromRealReg(v22, regalloc.RegTypeFloat) + v23VReg = regalloc.FromRealReg(v23, regalloc.RegTypeFloat) + v24VReg = regalloc.FromRealReg(v24, regalloc.RegTypeFloat) + v25VReg = regalloc.FromRealReg(v25, regalloc.RegTypeFloat) + v26VReg = regalloc.FromRealReg(v26, regalloc.RegTypeFloat) + v27VReg = regalloc.FromRealReg(v27, regalloc.RegTypeFloat) + // lr (link register) holds the return address at the function entry. + lrVReg = x30VReg + // tmpReg is used to perform spill/load on large stack offsets, and load large constants. + // Therefore, be cautious to use this register in the middle of the compilation, especially before the register allocation. + // This is the same as golang/go, but it's only described in the source code: + // https://github.com/golang/go/blob/go1.24.0/src/cmd/compile/internal/ssa/_gen/ARM64Ops.go#L59 + // https://github.com/golang/go/blob/go1.24.0/src/cmd/compile/internal/ssa/_gen/ARM64Ops.go#L13-L15 + tmpRegVReg = regalloc.FromRealReg(tmp, regalloc.RegTypeInt) + v28VReg = regalloc.FromRealReg(v28, regalloc.RegTypeFloat) + v29VReg = regalloc.FromRealReg(v29, regalloc.RegTypeFloat) + v30VReg = regalloc.FromRealReg(v30, regalloc.RegTypeFloat) + v31VReg = regalloc.FromRealReg(v31, regalloc.RegTypeFloat) + xzrVReg = regalloc.FromRealReg(xzr, regalloc.RegTypeInt) + spVReg = regalloc.FromRealReg(sp, regalloc.RegTypeInt) + fpVReg = regalloc.FromRealReg(fp, regalloc.RegTypeInt) +) + +var regNames = [...]string{ + x0: "x0", + x1: "x1", + x2: "x2", + x3: "x3", + x4: "x4", + x5: "x5", + x6: "x6", + x7: "x7", + x8: "x8", + x9: "x9", + x10: "x10", + x11: "x11", + x12: "x12", + x13: "x13", + x14: "x14", + x15: "x15", + x16: "x16", + x17: "x17", + x18: "x18", + x19: "x19", + x20: "x20", + x21: "x21", + x22: "x22", + x23: "x23", + x24: "x24", + x25: "x25", + x26: "x26", + x27: "x27", + x28: "x28", + x29: "x29", + x30: "x30", + xzr: "xzr", + sp: "sp", + v0: "v0", + v1: "v1", + v2: "v2", + v3: "v3", + v4: "v4", + v5: "v5", + v6: "v6", + v7: "v7", + v8: "v8", + v9: "v9", + v10: "v10", + v11: "v11", + v12: "v12", + v13: "v13", + v14: "v14", + v15: "v15", + v16: "v16", + v17: "v17", + v18: "v18", + v19: "v19", + v20: "v20", + v21: "v21", + v22: "v22", + v23: "v23", + v24: "v24", + v25: "v25", + v26: "v26", + v27: "v27", + v28: "v28", + v29: "v29", + v30: "v30", + v31: "v31", +} + +func formatVRegSized(r regalloc.VReg, size byte) (ret string) { + if r.IsRealReg() { + ret = regNames[r.RealReg()] + switch ret[0] { + case 'x': + switch size { + case 32: + ret = strings.Replace(ret, "x", "w", 1) + case 64: + default: + panic("BUG: invalid register size: " + strconv.Itoa(int(size))) + } + case 'v': + switch size { + case 32: + ret = strings.Replace(ret, "v", "s", 1) + case 64: + ret = strings.Replace(ret, "v", "d", 1) + case 128: + ret = strings.Replace(ret, "v", "q", 1) + default: + panic("BUG: invalid register size") + } + } + } else { + switch r.RegType() { + case regalloc.RegTypeInt: + switch size { + case 32: + ret = fmt.Sprintf("w%d?", r.ID()) + case 64: + ret = fmt.Sprintf("x%d?", r.ID()) + default: + panic("BUG: invalid register size: " + strconv.Itoa(int(size))) + } + case regalloc.RegTypeFloat: + switch size { + case 32: + ret = fmt.Sprintf("s%d?", r.ID()) + case 64: + ret = fmt.Sprintf("d%d?", r.ID()) + case 128: + ret = fmt.Sprintf("q%d?", r.ID()) + default: + panic("BUG: invalid register size") + } + default: + panic(fmt.Sprintf("BUG: invalid register type: %d for %s", r.RegType(), r)) + } + } + return +} + +func formatVRegWidthVec(r regalloc.VReg, width vecArrangement) (ret string) { + var id string + wspec := strings.ToLower(width.String()) + if r.IsRealReg() { + id = regNames[r.RealReg()][1:] + } else { + id = fmt.Sprintf("%d?", r.ID()) + } + ret = fmt.Sprintf("%s%s", wspec, id) + return +} + +func formatVRegVec(r regalloc.VReg, arr vecArrangement, index vecIndex) (ret string) { + id := fmt.Sprintf("v%d?", r.ID()) + if r.IsRealReg() { + id = regNames[r.RealReg()] + } + ret = fmt.Sprintf("%s.%s", id, strings.ToLower(arr.String())) + if index != vecIndexNone { + ret += fmt.Sprintf("[%d]", index) + } + return +} + +func regTypeToRegisterSizeInBits(r regalloc.RegType) byte { + switch r { + case regalloc.RegTypeInt: + return 64 + case regalloc.RegTypeFloat: + return 128 + default: + panic("BUG: invalid register type") + } +} + +var regNumberInEncoding = [...]uint32{ + x0: 0, + x1: 1, + x2: 2, + x3: 3, + x4: 4, + x5: 5, + x6: 6, + x7: 7, + x8: 8, + x9: 9, + x10: 10, + x11: 11, + x12: 12, + x13: 13, + x14: 14, + x15: 15, + x16: 16, + x17: 17, + x18: 18, + x19: 19, + x20: 20, + x21: 21, + x22: 22, + x23: 23, + x24: 24, + x25: 25, + x26: 26, + x27: 27, + x28: 28, + x29: 29, + x30: 30, + xzr: 31, + sp: 31, + v0: 0, + v1: 1, + v2: 2, + v3: 3, + v4: 4, + v5: 5, + v6: 6, + v7: 7, + v8: 8, + v9: 9, + v10: 10, + v11: 11, + v12: 12, + v13: 13, + v14: 14, + v15: 15, + v16: 16, + v17: 17, + v18: 18, + v19: 19, + v20: 20, + v21: 21, + v22: 22, + v23: 23, + v24: 24, + v25: 25, + v26: 26, + v27: 27, + v28: 28, + v29: 29, + v30: 30, + v31: 31, +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/unwind_stack.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/unwind_stack.go new file mode 100644 index 00000000000..a72b86f6bff --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/unwind_stack.go @@ -0,0 +1,84 @@ +package arm64 + +import ( + "encoding/binary" + "reflect" + "unsafe" + + "github.com/tetratelabs/wazero/internal/wasmdebug" +) + +// UnwindStack implements wazevo.unwindStack. +func UnwindStack(sp, _, top uintptr, returnAddresses []uintptr) []uintptr { + l := int(top - sp) + + var stackBuf []byte + { + //nolint:staticcheck + hdr := (*reflect.SliceHeader)(unsafe.Pointer(&stackBuf)) + hdr.Data = sp + hdr.Len = l + hdr.Cap = l + } + + for i := uint64(0); i < uint64(l); { + // (high address) + // +-----------------+ + // | ....... | + // | ret Y | <----+ + // | ....... | | + // | ret 0 | | + // | arg X | | size_of_arg_ret + // | ....... | | + // | arg 1 | | + // | arg 0 | <----+ + // | size_of_arg_ret | + // | ReturnAddress | + // +-----------------+ <----+ + // | ........... | | + // | spill slot M | | + // | ............ | | + // | spill slot 2 | | + // | spill slot 1 | | frame size + // | spill slot 1 | | + // | clobbered N | | + // | ............ | | + // | clobbered 0 | <----+ + // | xxxxxx | ;; unused space to make it 16-byte aligned. + // | frame_size | + // +-----------------+ <---- SP + // (low address) + + frameSize := binary.LittleEndian.Uint64(stackBuf[i:]) + i += frameSize + + 16 // frame size + aligned space. + retAddr := binary.LittleEndian.Uint64(stackBuf[i:]) + i += 8 // ret addr. + sizeOfArgRet := binary.LittleEndian.Uint64(stackBuf[i:]) + i += 8 + sizeOfArgRet + returnAddresses = append(returnAddresses, uintptr(retAddr)) + if len(returnAddresses) == wasmdebug.MaxFrames { + break + } + } + return returnAddresses +} + +// GoCallStackView implements wazevo.goCallStackView. +func GoCallStackView(stackPointerBeforeGoCall *uint64) []uint64 { + // (high address) + // +-----------------+ <----+ + // | xxxxxxxxxxx | | ;; optional unused space to make it 16-byte aligned. + // ^ | arg[N]/ret[M] | | + // sliceSize | | ............ | | sliceSize + // | | arg[1]/ret[1] | | + // v | arg[0]/ret[0] | <----+ + // | sliceSize | + // | frame_size | + // +-----------------+ <---- stackPointerBeforeGoCall + // (low address) + ptr := unsafe.Pointer(stackPointerBeforeGoCall) + data := (*uint64)(unsafe.Add(ptr, 16)) // skips the (frame_size, sliceSize). + size := *(*uint64)(unsafe.Add(ptr, 8)) + return unsafe.Slice(data, size) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/machine.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/machine.go new file mode 100644 index 00000000000..3a29e7cd644 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/machine.go @@ -0,0 +1,119 @@ +package backend + +import ( + "context" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +type ( + // Machine is a backend for a specific ISA machine. + Machine interface { + // StartLoweringFunction is called when the compilation of the given function is started. + // The maxBlockID is the maximum ssa.BasicBlockID in the function. + StartLoweringFunction(maxBlockID ssa.BasicBlockID) + + // LinkAdjacentBlocks is called after finished lowering all blocks in order to create one single instruction list. + LinkAdjacentBlocks(prev, next ssa.BasicBlock) + + // StartBlock is called when the compilation of the given block is started. + // The order of this being called is the reverse post order of the ssa.BasicBlock(s) as we iterate with + // ssa.Builder BlockIteratorReversePostOrderBegin and BlockIteratorReversePostOrderEnd. + StartBlock(ssa.BasicBlock) + + // EndBlock is called when the compilation of the current block is finished. + EndBlock() + + // FlushPendingInstructions flushes the pending instructions to the buffer. + // This will be called after the lowering of each SSA Instruction. + FlushPendingInstructions() + + // DisableStackCheck disables the stack check for the current compilation for debugging/testing. + DisableStackCheck() + + // SetCurrentABI initializes the FunctionABI for the given signature. + SetCurrentABI(abi *FunctionABI) + + // SetCompiler sets the compilation context used for the lifetime of Machine. + // This is only called once per Machine, i.e. before the first compilation. + SetCompiler(Compiler) + + // LowerSingleBranch is called when the compilation of the given single branch is started. + LowerSingleBranch(b *ssa.Instruction) + + // LowerConditionalBranch is called when the compilation of the given conditional branch is started. + LowerConditionalBranch(b *ssa.Instruction) + + // LowerInstr is called for each instruction in the given block except for the ones marked as already lowered + // via Compiler.MarkLowered. The order is reverse, i.e. from the last instruction to the first one. + // + // Note: this can lower multiple instructions (which produce the inputs) at once whenever it's possible + // for optimization. + LowerInstr(*ssa.Instruction) + + // Reset resets the machine state for the next compilation. + Reset() + + // InsertMove inserts a move instruction from src to dst whose type is typ. + InsertMove(dst, src regalloc.VReg, typ ssa.Type) + + // InsertReturn inserts the return instruction to return from the current function. + InsertReturn() + + // InsertLoadConstantBlockArg inserts the instruction(s) to load the constant value into the given regalloc.VReg. + InsertLoadConstantBlockArg(instr *ssa.Instruction, vr regalloc.VReg) + + // Format returns the string representation of the currently compiled machine code. + // This is only for testing purpose. + Format() string + + // RegAlloc does the register allocation after lowering. + RegAlloc() + + // PostRegAlloc does the post register allocation, e.g. setting up prologue/epilogue, redundant move elimination, etc. + PostRegAlloc() + + // ResolveRelocations resolves the relocations after emitting machine code. + // * refToBinaryOffset: the map from the function reference (ssa.FuncRef) to the executable offset. + // * importedFns: the max index of the imported functions at the beginning of refToBinaryOffset + // * executable: the binary to resolve the relocations. + // * relocations: the relocations to resolve. + // * callTrampolineIslandOffsets: the offsets of the trampoline islands in the executable. + ResolveRelocations( + refToBinaryOffset []int, + importedFns int, + executable []byte, + relocations []RelocationInfo, + callTrampolineIslandOffsets []int, + ) + + // Encode encodes the machine instructions to the Compiler. + Encode(ctx context.Context) error + + // CompileGoFunctionTrampoline compiles the trampoline function to call a Go function of the given exit code and signature. + CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *ssa.Signature, needModuleContextPtr bool) []byte + + // CompileStackGrowCallSequence returns the sequence of instructions shared by all functions to + // call the stack grow builtin function. + CompileStackGrowCallSequence() []byte + + // CompileEntryPreamble returns the sequence of instructions shared by multiple functions to + // enter the function from Go. + CompileEntryPreamble(signature *ssa.Signature) []byte + + // LowerParams lowers the given parameters. + LowerParams(params []ssa.Value) + + // LowerReturns lowers the given returns. + LowerReturns(returns []ssa.Value) + + // ArgsResultsRegs returns the registers used for arguments and return values. + ArgsResultsRegs() (argResultInts, argResultFloats []regalloc.RealReg) + + // CallTrampolineIslandInfo returns the interval of the offset where the trampoline island is placed, and + // the size of the trampoline island. If islandSize is zero, the trampoline island is not used on this machine. + CallTrampolineIslandInfo(numFunctions int) (interval, islandSize int, err error) + } +) diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc/api.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc/api.go new file mode 100644 index 00000000000..5d15bd9dc19 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc/api.go @@ -0,0 +1,124 @@ +package regalloc + +import "fmt" + +// These interfaces are implemented by ISA-specific backends to abstract away the details, and allow the register +// allocators to work on any ISA. + +type ( + // Function is the top-level interface to do register allocation, which corresponds to a CFG containing + // Blocks(s). + // + // I is the type of the instruction, and B is the type of the basic block. + Function[I Instr, B Block[I]] interface { + // PostOrderBlockIteratorBegin returns the first block in the post-order traversal of the CFG. + // In other words, the last blocks in the CFG will be returned first. + PostOrderBlockIteratorBegin() B + // PostOrderBlockIteratorNext returns the next block in the post-order traversal of the CFG. + PostOrderBlockIteratorNext() B + // ReversePostOrderBlockIteratorBegin returns the first block in the reverse post-order traversal of the CFG. + // In other words, the first blocks in the CFG will be returned first. + ReversePostOrderBlockIteratorBegin() B + // ReversePostOrderBlockIteratorNext returns the next block in the reverse post-order traversal of the CFG. + ReversePostOrderBlockIteratorNext() B + // ClobberedRegisters tell the clobbered registers by this function. + ClobberedRegisters([]VReg) + // LoopNestingForestRoots returns the number of roots of the loop nesting forest in a function. + LoopNestingForestRoots() int + // LoopNestingForestRoot returns the i-th root of the loop nesting forest in a function. + LoopNestingForestRoot(i int) B + // LowestCommonAncestor returns the lowest common ancestor of two blocks in the dominator tree. + LowestCommonAncestor(blk1, blk2 B) B + // Idom returns the immediate dominator of the given block. + Idom(blk B) B + + // LoopNestingForestChild returns the i-th child of the block in the loop nesting forest. + LoopNestingForestChild(b B, i int) B + // Pred returns the i-th predecessor of the block in the CFG. + Pred(b B, i int) B + // Succ returns the i-th successor of the block in the CFG. + Succ(b B, i int) B + // BlockParams returns the virtual registers used as the parameters of this block. + BlockParams(B, *[]VReg) []VReg + + // Followings are for rewriting the function. + + // SwapBefore swaps the two virtual registers at the end of the given block. + SwapBefore(x1, x2, tmp VReg, instr I) + // StoreRegisterBefore inserts store instruction(s) before the given instruction for the given virtual register. + StoreRegisterBefore(v VReg, instr I) + // StoreRegisterAfter inserts store instruction(s) after the given instruction for the given virtual register. + StoreRegisterAfter(v VReg, instr I) + // ReloadRegisterBefore inserts reload instruction(s) before the given instruction for the given virtual register. + ReloadRegisterBefore(v VReg, instr I) + // ReloadRegisterAfter inserts reload instruction(s) after the given instruction for the given virtual register. + ReloadRegisterAfter(v VReg, instr I) + // InsertMoveBefore inserts move instruction(s) before the given instruction for the given virtual registers. + InsertMoveBefore(dst, src VReg, instr I) + } + + // Block is a basic block in the CFG of a function, and it consists of multiple instructions, and predecessor Block(s). + // Right now, this corresponds to a ssa.BasicBlock lowered to the machine level. + Block[I Instr] interface { + comparable + // ID returns the unique identifier of this block which is ordered in the reverse post-order traversal of the CFG. + ID() int32 + // InstrIteratorBegin returns the first instruction in this block. Instructions added after lowering must be skipped. + // Note: multiple Instr(s) will not be held at the same time, so it's safe to use the same impl for the return Instr. + InstrIteratorBegin() I + // InstrIteratorNext returns the next instruction in this block. Instructions added after lowering must be skipped. + // Note: multiple Instr(s) will not be held at the same time, so it's safe to use the same impl for the return Instr. + InstrIteratorNext() I + // InstrRevIteratorBegin is the same as InstrIteratorBegin, but in the reverse order. + InstrRevIteratorBegin() I + // InstrRevIteratorNext is the same as InstrIteratorNext, but in the reverse order. + InstrRevIteratorNext() I + // FirstInstr returns the fist instruction in this block where instructions will be inserted after it. + FirstInstr() I + // LastInstrForInsertion returns the last instruction in this block where instructions will be inserted before it. + // Such insertions only happen when we need to insert spill/reload instructions to adjust the merge edges. + // At the time of register allocation, all the critical edges are already split, so there is no need + // to worry about the case where branching instruction has multiple successors. + // Therefore, usually, it is the nop instruction, but if the block ends with an unconditional branching, then it returns + // the unconditional branch, not the nop. In other words it is either nop or unconditional branch. + LastInstrForInsertion() I + // Preds returns the number of predecessors of this block in the CFG. + Preds() int + // Entry returns true if the block is for the entry block. + Entry() bool + // Succs returns the number of successors of this block in the CFG. + Succs() int + // LoopHeader returns true if this block is a loop header. + LoopHeader() bool + // LoopNestingForestChildren returns the number of children of this block in the loop nesting forest. + LoopNestingForestChildren() int + } + + // Instr is an instruction in a block, abstracting away the underlying ISA. + Instr interface { + comparable + fmt.Stringer + // Defs returns the virtual registers defined by this instruction. + Defs(*[]VReg) []VReg + // Uses returns the virtual registers used by this instruction. + // Note: multiple returned []VReg will not be held at the same time, so it's safe to use the same slice for this. + Uses(*[]VReg) []VReg + // AssignUse assigns the RealReg-allocated virtual register used by this instruction at the given index. + AssignUse(index int, v VReg) + // AssignDef assigns a RealReg-allocated virtual register defined by this instruction. + // This only accepts one register because we don't allocate registers for multi-def instructions (i.e. call instruction) + AssignDef(VReg) + // IsCopy returns true if this instruction is a move instruction between two registers. + // If true, the instruction is of the form of dst = src, and if the src and dst do not interfere with each other, + // we could coalesce them, and hence the copy can be eliminated from the final code. + IsCopy() bool + // IsCall returns true if this instruction is a call instruction. The result is used to insert + // caller saved register spills and restores. + IsCall() bool + // IsIndirectCall returns true if this instruction is an indirect call instruction which calls a function pointer. + // The result is used to insert caller saved register spills and restores. + IsIndirectCall() bool + // IsReturn returns true if this instruction is a return instruction. + IsReturn() bool + } +) diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc/reg.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc/reg.go new file mode 100644 index 00000000000..46df807e6fe --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc/reg.go @@ -0,0 +1,123 @@ +package regalloc + +import ( + "fmt" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" +) + +// VReg represents a register which is assigned to an SSA value. This is used to represent a register in the backend. +// A VReg may or may not be a physical register, and the info of physical register can be obtained by RealReg. +type VReg uint64 + +// VRegID is the lower 32bit of VReg, which is the pure identifier of VReg without RealReg info. +type VRegID uint32 + +// RealReg returns the RealReg of this VReg. +func (v VReg) RealReg() RealReg { + return RealReg(v >> 32) +} + +// IsRealReg returns true if this VReg is backed by a physical register. +func (v VReg) IsRealReg() bool { + return v.RealReg() != RealRegInvalid +} + +// FromRealReg returns a VReg from the given RealReg and RegType. +// This is used to represent a specific pre-colored register in the backend. +func FromRealReg(r RealReg, typ RegType) VReg { + rid := VRegID(r) + if rid > vRegIDReservedForRealNum { + panic(fmt.Sprintf("invalid real reg %d", r)) + } + return VReg(r).SetRealReg(r).SetRegType(typ) +} + +// SetRealReg sets the RealReg of this VReg and returns the updated VReg. +func (v VReg) SetRealReg(r RealReg) VReg { + return VReg(r)<<32 | (v & 0xff_00_ffffffff) +} + +// RegType returns the RegType of this VReg. +func (v VReg) RegType() RegType { + return RegType(v >> 40) +} + +// SetRegType sets the RegType of this VReg and returns the updated VReg. +func (v VReg) SetRegType(t RegType) VReg { + return VReg(t)<<40 | (v & 0x00_ff_ffffffff) +} + +// ID returns the VRegID of this VReg. +func (v VReg) ID() VRegID { + return VRegID(v & 0xffffffff) +} + +// Valid returns true if this VReg is Valid. +func (v VReg) Valid() bool { + return v.ID() != vRegIDInvalid && v.RegType() != RegTypeInvalid +} + +// RealReg represents a physical register. +type RealReg byte + +const RealRegInvalid RealReg = 0 + +const ( + vRegIDInvalid VRegID = 1 << 31 + VRegIDNonReservedBegin = vRegIDReservedForRealNum + vRegIDReservedForRealNum VRegID = 128 + VRegInvalid = VReg(vRegIDInvalid) +) + +// String implements fmt.Stringer. +func (r RealReg) String() string { + switch r { + case RealRegInvalid: + return "invalid" + default: + return fmt.Sprintf("r%d", r) + } +} + +// String implements fmt.Stringer. +func (v VReg) String() string { + if v.IsRealReg() { + return fmt.Sprintf("r%d", v.ID()) + } + return fmt.Sprintf("v%d?", v.ID()) +} + +// RegType represents the type of a register. +type RegType byte + +const ( + RegTypeInvalid RegType = iota + RegTypeInt + RegTypeFloat + NumRegType +) + +// String implements fmt.Stringer. +func (r RegType) String() string { + switch r { + case RegTypeInt: + return "int" + case RegTypeFloat: + return "float" + default: + return "invalid" + } +} + +// RegTypeOf returns the RegType of the given ssa.Type. +func RegTypeOf(p ssa.Type) RegType { + switch p { + case ssa.TypeI32, ssa.TypeI64: + return RegTypeInt + case ssa.TypeF32, ssa.TypeF64, ssa.TypeV128: + return RegTypeFloat + default: + panic("invalid type") + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc/regalloc.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc/regalloc.go new file mode 100644 index 00000000000..a5857f4f265 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc/regalloc.go @@ -0,0 +1,1189 @@ +// Package regalloc performs register allocation. The algorithm can work on any ISA by implementing the interfaces in +// api.go. +// +// References: +// - https://web.stanford.edu/class/archive/cs/cs143/cs143.1128/lectures/17/Slides17.pdf +// - https://en.wikipedia.org/wiki/Chaitin%27s_algorithm +// - https://llvm.org/ProjectsWithLLVM/2004-Fall-CS426-LS.pdf +// - https://pfalcon.github.io/ssabook/latest/book-full.pdf: Chapter 9. for liveness analysis. +// - https://github.com/golang/go/blob/release-branch.go1.21/src/cmd/compile/internal/ssa/regalloc.go +package regalloc + +import ( + "fmt" + "math" + "strings" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +// NewAllocator returns a new Allocator. +func NewAllocator[I Instr, B Block[I], F Function[I, B]](allocatableRegs *RegisterInfo) Allocator[I, B, F] { + a := Allocator[I, B, F]{ + regInfo: allocatableRegs, + phiDefInstListPool: wazevoapi.NewPool[phiDefInstList[I]](resetPhiDefInstList[I]), + blockStates: wazevoapi.NewIDedPool[blockState[I, B, F]](resetBlockState[I, B, F]), + } + a.state.vrStates = wazevoapi.NewIDedPool[vrState[I, B, F]](resetVrState[I, B, F]) + a.state.reset() + for _, regs := range allocatableRegs.AllocatableRegisters { + for _, r := range regs { + a.allocatableSet = a.allocatableSet.add(r) + } + } + return a +} + +type ( + // RegisterInfo holds the statically-known ISA-specific register information. + RegisterInfo struct { + // AllocatableRegisters is a 2D array of allocatable RealReg, indexed by regTypeNum and regNum. + // The order matters: the first element is the most preferred one when allocating. + AllocatableRegisters [NumRegType][]RealReg + CalleeSavedRegisters RegSet + CallerSavedRegisters RegSet + RealRegToVReg []VReg + // RealRegName returns the name of the given RealReg for debugging. + RealRegName func(r RealReg) string + RealRegType func(r RealReg) RegType + } + + // Allocator is a register allocator. + Allocator[I Instr, B Block[I], F Function[I, B]] struct { + // regInfo is static per ABI/ISA, and is initialized by the machine during Machine.PrepareRegisterAllocator. + regInfo *RegisterInfo + // allocatableSet is a set of allocatable RealReg derived from regInfo. Static per ABI/ISA. + allocatableSet RegSet + allocatedCalleeSavedRegs []VReg + vs []VReg + ss []*vrState[I, B, F] + copies []_copy[I, B, F] + phiDefInstListPool wazevoapi.Pool[phiDefInstList[I]] + + // Followings are re-used during various places. + blks []B + reals []RealReg + + // Following two fields are updated while iterating the blocks in the reverse postorder. + state state[I, B, F] + blockStates wazevoapi.IDedPool[blockState[I, B, F]] + } + + // _copy represents a source and destination pair of a copy instruction. + _copy[I Instr, B Block[I], F Function[I, B]] struct { + src *vrState[I, B, F] + dstID VRegID + } + + // programCounter represents an opaque index into the program which is used to represents a LiveInterval of a VReg. + programCounter int32 + + state[I Instr, B Block[I], F Function[I, B]] struct { + argRealRegs []VReg + regsInUse regInUseSet[I, B, F] + vrStates wazevoapi.IDedPool[vrState[I, B, F]] + + currentBlockID int32 + + // allocatedRegSet is a set of RealReg that are allocated during the allocation phase. This is reset per function. + allocatedRegSet RegSet + } + + blockState[I Instr, B Block[I], F Function[I, B]] struct { + // liveIns is a list of VReg that are live at the beginning of the block. + liveIns []*vrState[I, B, F] + // seen is true if the block is visited during the liveness analysis. + seen bool + // visited is true if the block is visited during the allocation phase. + visited bool + startFromPredIndex int + // startRegs is a list of RealReg that are used at the beginning of the block. This is used to fix the merge edges. + startRegs regInUseSet[I, B, F] + // endRegs is a list of RealReg that are used at the end of the block. This is used to fix the merge edges. + endRegs regInUseSet[I, B, F] + } + + vrState[I Instr, B Block[I], f Function[I, B]] struct { + v VReg + r RealReg + // defInstr is the instruction that defines this value. If this is the phi value and not the entry block, this is nil. + defInstr I + // defBlk is the block that defines this value. If this is the phi value, this is the block whose arguments contain this value. + defBlk B + // lca = lowest common ancestor. This is the block that is the lowest common ancestor of all the blocks that + // reloads this value. This is used to determine the spill location. Only valid if spilled=true. + lca B + // lastUse is the program counter of the last use of this value. This changes while iterating the block, and + // should not be used across the blocks as it becomes invalid. To check the validity, use lastUseUpdatedAtBlockID. + lastUse programCounter + lastUseUpdatedAtBlockID int32 + // spilled is true if this value is spilled i.e. the value is reload from the stack somewhere in the program. + // + // Note that this field is used during liveness analysis for different purpose. This is used to determine the + // value is live-in or not. + spilled bool + // isPhi is true if this is a phi value. + isPhi bool + desiredLoc desiredLoc + // phiDefInstList is a list of instructions that defines this phi value. + // This is used to determine the spill location, and only valid if isPhi=true. + *phiDefInstList[I] + } + + // phiDefInstList is a linked list of instructions that defines a phi value. + phiDefInstList[I Instr] struct { + instr I + v VReg + next *phiDefInstList[I] + } + + // desiredLoc represents a desired location for a VReg. + desiredLoc uint16 + // desiredLocKind is a kind of desired location for a VReg. + desiredLocKind uint16 +) + +const ( + // desiredLocKindUnspecified is a kind of desired location for a VReg that is not specified. + desiredLocKindUnspecified desiredLocKind = iota + // desiredLocKindStack is a kind of desired location for a VReg that is on the stack, only used for the phi values. + desiredLocKindStack + // desiredLocKindReg is a kind of desired location for a VReg that is in a register. + desiredLocKindReg + desiredLocUnspecified = desiredLoc(desiredLocKindUnspecified) + desiredLocStack = desiredLoc(desiredLocKindStack) +) + +func newDesiredLocReg(r RealReg) desiredLoc { + return desiredLoc(desiredLocKindReg) | desiredLoc(r<<2) +} + +func (d desiredLoc) realReg() RealReg { + return RealReg(d >> 2) +} + +func (d desiredLoc) stack() bool { + return d&3 == desiredLoc(desiredLocKindStack) +} + +func resetPhiDefInstList[I Instr](l *phiDefInstList[I]) { + var nilInstr I + l.instr = nilInstr + l.next = nil + l.v = VRegInvalid +} + +func (s *state[I, B, F]) dump(info *RegisterInfo) { //nolint:unused + fmt.Println("\t\tstate:") + fmt.Println("\t\t\targRealRegs:", s.argRealRegs) + fmt.Println("\t\t\tregsInUse", s.regsInUse.format(info)) + fmt.Println("\t\t\tallocatedRegSet:", s.allocatedRegSet.format(info)) + fmt.Println("\t\t\tused:", s.regsInUse.format(info)) + var strs []string + for i := 0; i <= s.vrStates.MaxIDEncountered(); i++ { + vs := s.vrStates.Get(i) + if vs == nil { + continue + } + if vs.r != RealRegInvalid { + strs = append(strs, fmt.Sprintf("(v%d: %s)", vs.v.ID(), info.RealRegName(vs.r))) + } + } + fmt.Println("\t\t\tvrStates:", strings.Join(strs, ", ")) +} + +func (s *state[I, B, F]) reset() { + s.argRealRegs = s.argRealRegs[:0] + s.vrStates.Reset() + s.allocatedRegSet = RegSet(0) + s.regsInUse.reset() + s.currentBlockID = -1 +} + +func resetVrState[I Instr, B Block[I], F Function[I, B]](vs *vrState[I, B, F]) { + vs.v = VRegInvalid + vs.r = RealRegInvalid + var nilInstr I + vs.defInstr = nilInstr + var nilBlk B + vs.defBlk = nilBlk + vs.spilled = false + vs.lastUse = -1 + vs.lastUseUpdatedAtBlockID = -1 + vs.lca = nilBlk + vs.isPhi = false + vs.phiDefInstList = nil + vs.desiredLoc = desiredLocUnspecified +} + +func (s *state[I, B, F]) getOrAllocateVRegState(v VReg) *vrState[I, B, F] { + st := s.vrStates.GetOrAllocate(int(v.ID())) + if st.v == VRegInvalid { + st.v = v + } + return st +} + +func (s *state[I, B, F]) getVRegState(v VRegID) *vrState[I, B, F] { + return s.vrStates.Get(int(v)) +} + +func (s *state[I, B, F]) useRealReg(r RealReg, vr *vrState[I, B, F]) { + s.regsInUse.add(r, vr) + vr.r = r + s.allocatedRegSet = s.allocatedRegSet.add(r) +} + +func (s *state[I, B, F]) releaseRealReg(r RealReg) { + current := s.regsInUse.get(r) + if current != nil { + s.regsInUse.remove(r) + current.r = RealRegInvalid + } +} + +// recordReload records that the given VReg is reloaded in the given block. +// This is used to determine the spill location by tracking the lowest common ancestor of all the blocks that reloads the value. +func (vs *vrState[I, B, F]) recordReload(f F, blk B) { + vs.spilled = true + var nilBlk B + if lca := vs.lca; lca == nilBlk { + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("\t\tv%d is reloaded in blk%d,\n", vs.v.ID(), blk.ID()) + } + vs.lca = blk + } else if lca != blk { + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("\t\tv%d is reloaded in blk%d, lca=%d\n", vs.v.ID(), blk.ID(), vs.lca.ID()) + } + vs.lca = f.LowestCommonAncestor(lca, blk) + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("updated lca=%d\n", vs.lca.ID()) + } + } +} + +func (a *Allocator[I, B, F]) findOrSpillAllocatable(s *state[I, B, F], allocatable []RealReg, forbiddenMask RegSet, preferred RealReg) (r RealReg) { + r = RealRegInvalid + // First, check if the preferredMask has any allocatable register. + if preferred != RealRegInvalid && !forbiddenMask.has(preferred) && !s.regsInUse.has(preferred) { + return preferred + } + + var lastUseAt programCounter + var spillVReg VReg + for _, candidateReal := range allocatable { + if forbiddenMask.has(candidateReal) { + continue + } + + using := s.regsInUse.get(candidateReal) + if using == nil { + // This is not used at this point. + return candidateReal + } + + // Real registers in use should not be spilled, so we skip them. + // For example, if the register is used as an argument register, and it might be + // spilled and not reloaded when it ends up being used as a temporary to pass + // stack based argument. + if using.v.IsRealReg() { + continue + } + + isPreferred := candidateReal == preferred + + // last == -1 means the value won't be used anymore. + if last := using.lastUse; r == RealRegInvalid || isPreferred || last == -1 || (lastUseAt != -1 && last > lastUseAt) { + lastUseAt = last + r = candidateReal + spillVReg = using.v + if isPreferred { + break + } + } + } + + if r == RealRegInvalid { + panic("not found any allocatable register") + } + + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("\tspilling v%d when lastUseAt=%d and regsInUse=%s\n", spillVReg.ID(), lastUseAt, s.regsInUse.format(a.regInfo)) + } + s.releaseRealReg(r) + return r +} + +func (s *state[I, B, F]) findAllocatable(allocatable []RealReg, forbiddenMask RegSet) RealReg { + for _, r := range allocatable { + if !s.regsInUse.has(r) && !forbiddenMask.has(r) { + return r + } + } + return RealRegInvalid +} + +func (s *state[I, B, F]) resetAt(bs *blockState[I, B, F]) { + s.regsInUse.range_(func(_ RealReg, vs *vrState[I, B, F]) { + vs.r = RealRegInvalid + }) + s.regsInUse.reset() + bs.endRegs.range_(func(r RealReg, vs *vrState[I, B, F]) { + if vs.lastUseUpdatedAtBlockID == s.currentBlockID && vs.lastUse == programCounterLiveIn { + s.regsInUse.add(r, vs) + vs.r = r + } + }) +} + +func resetBlockState[I Instr, B Block[I], F Function[I, B]](b *blockState[I, B, F]) { + b.seen = false + b.visited = false + b.endRegs.reset() + b.startRegs.reset() + b.startFromPredIndex = -1 + b.liveIns = b.liveIns[:0] +} + +func (b *blockState[I, B, F]) dump(a *RegisterInfo) { + fmt.Println("\t\tblockState:") + fmt.Println("\t\t\tstartRegs:", b.startRegs.format(a)) + fmt.Println("\t\t\tendRegs:", b.endRegs.format(a)) + fmt.Println("\t\t\tstartFromPredIndex:", b.startFromPredIndex) + fmt.Println("\t\t\tvisited:", b.visited) +} + +// DoAllocation performs register allocation on the given Function. +func (a *Allocator[I, B, F]) DoAllocation(f F) { + a.livenessAnalysis(f) + a.alloc(f) + a.determineCalleeSavedRealRegs(f) +} + +func (a *Allocator[I, B, F]) determineCalleeSavedRealRegs(f F) { + a.allocatedCalleeSavedRegs = a.allocatedCalleeSavedRegs[:0] + a.state.allocatedRegSet.Range(func(allocatedRealReg RealReg) { + if a.regInfo.CalleeSavedRegisters.has(allocatedRealReg) { + a.allocatedCalleeSavedRegs = append(a.allocatedCalleeSavedRegs, a.regInfo.RealRegToVReg[allocatedRealReg]) + } + }) + f.ClobberedRegisters(a.allocatedCalleeSavedRegs) +} + +func (a *Allocator[I, B, F]) getOrAllocateBlockState(blockID int32) *blockState[I, B, F] { + return a.blockStates.GetOrAllocate(int(blockID)) +} + +// phiBlk returns the block that defines the given phi value, nil otherwise. +func (vs *vrState[I, B, F]) phiBlk() B { + if vs.isPhi { + return vs.defBlk + } + var nilBlk B + return nilBlk +} + +const ( + programCounterLiveIn = math.MinInt32 + programCounterLiveOut = math.MaxInt32 +) + +// liveAnalysis constructs Allocator.blockLivenessData. +// The algorithm here is described in https://pfalcon.github.io/ssabook/latest/book-full.pdf Chapter 9.2. +func (a *Allocator[I, B, F]) livenessAnalysis(f F) { + s := &a.state + + for i := VRegID(0); i < vRegIDReservedForRealNum; i++ { + s.getOrAllocateVRegState(VReg(i).SetRealReg(RealReg(i))) + } + + var nilBlk B + var nilInstr I + for blk := f.PostOrderBlockIteratorBegin(); blk != nilBlk; blk = f.PostOrderBlockIteratorNext() { + // We should gather phi value data. + for _, p := range f.BlockParams(blk, &a.vs) { + vs := s.getOrAllocateVRegState(p) + vs.isPhi = true + vs.defBlk = blk + } + + blkID := blk.ID() + info := a.getOrAllocateBlockState(blkID) + + a.ss = a.ss[:0] + const ( + flagDeleted = false + flagLive = true + ) + ns := blk.Succs() + for i := 0; i < ns; i++ { + succ := f.Succ(blk, i) + if succ == nilBlk { + continue + } + + succID := succ.ID() + succInfo := a.getOrAllocateBlockState(succID) + if !succInfo.seen { // This means the back edge. + continue + } + + for _, st := range succInfo.liveIns { + if st.phiBlk() != succ && st.spilled != flagLive { //nolint:gosimple + // We use .spilled field to store the flag. + st.spilled = flagLive + a.ss = append(a.ss, st) + } + } + } + + for instr := blk.InstrRevIteratorBegin(); instr != nilInstr; instr = blk.InstrRevIteratorNext() { + + var use, def VReg + var defIsPhi bool + for _, def = range instr.Defs(&a.vs) { + if !def.IsRealReg() { + st := s.getOrAllocateVRegState(def) + defIsPhi = st.isPhi + // Note: We use .spilled field to store the flag. + st.spilled = flagDeleted + } + } + for _, use = range instr.Uses(&a.vs) { + if !use.IsRealReg() { + st := s.getOrAllocateVRegState(use) + // Note: We use .spilled field to store the flag. + if st.spilled != flagLive { //nolint:gosimple + st.spilled = flagLive + a.ss = append(a.ss, st) + } + } + } + + if defIsPhi { + if use.Valid() && use.IsRealReg() { + // If the destination is a phi value, and the source is a real register, this is the beginning of the function. + a.state.argRealRegs = append(a.state.argRealRegs, use) + } + } + } + + for _, st := range a.ss { + // We use .spilled field to store the flag. + if st.spilled == flagLive { //nolint:gosimple + info.liveIns = append(info.liveIns, st) + st.spilled = false + } + } + + info.seen = true + } + + nrs := f.LoopNestingForestRoots() + for i := 0; i < nrs; i++ { + root := f.LoopNestingForestRoot(i) + a.loopTreeDFS(f, root) + } +} + +// loopTreeDFS implements the Algorithm 9.3 in the book in an iterative way. +func (a *Allocator[I, B, F]) loopTreeDFS(f F, entry B) { + a.blks = a.blks[:0] + a.blks = append(a.blks, entry) + + for len(a.blks) > 0 { + tail := len(a.blks) - 1 + loop := a.blks[tail] + a.blks = a.blks[:tail] + a.ss = a.ss[:0] + const ( + flagDone = false + flagPending = true + ) + info := a.getOrAllocateBlockState(loop.ID()) + for _, st := range info.liveIns { + if st.phiBlk() != loop { + a.ss = append(a.ss, st) + // We use .spilled field to store the flag. + st.spilled = flagPending + } + } + + var siblingAddedView []*vrState[I, B, F] + cn := loop.LoopNestingForestChildren() + for i := 0; i < cn; i++ { + child := f.LoopNestingForestChild(loop, i) + childID := child.ID() + childInfo := a.getOrAllocateBlockState(childID) + + if i == 0 { + begin := len(childInfo.liveIns) + for _, st := range a.ss { + // We use .spilled field to store the flag. + if st.spilled == flagPending { //nolint:gosimple + st.spilled = flagDone + // TODO: deduplicate, though I don't think it has much impact. + childInfo.liveIns = append(childInfo.liveIns, st) + } + } + siblingAddedView = childInfo.liveIns[begin:] + } else { + // TODO: deduplicate, though I don't think it has much impact. + childInfo.liveIns = append(childInfo.liveIns, siblingAddedView...) + } + + if child.LoopHeader() { + a.blks = append(a.blks, child) + } + } + + if cn == 0 { + // If there's no forest child, we haven't cleared the .spilled field at this point. + for _, st := range a.ss { + st.spilled = false + } + } + } +} + +// alloc allocates registers for the given function by iterating the blocks in the reverse postorder. +// The algorithm here is derived from the Go compiler's allocator https://github.com/golang/go/blob/release-branch.go1.21/src/cmd/compile/internal/ssa/regalloc.go +// In short, this is a simply linear scan register allocation where each block inherits the register allocation state from +// one of its predecessors. Each block inherits the selected state and starts allocation from there. +// If there's a discrepancy in the end states between predecessors, the adjustments are made to ensure consistency after allocation is done (which we call "fixing merge state"). +// The spill instructions (store into the dedicated slots) are inserted after all the allocations and fixing merge states. That is because +// at the point, we all know where the reloads happen, and therefore we can know the best place to spill the values. More precisely, +// the spill happens in the block that is the lowest common ancestor of all the blocks that reloads the value. +// +// All of these logics are almost the same as Go's compiler which has a dedicated description in the source file ^^. +func (a *Allocator[I, B, F]) alloc(f F) { + // First we allocate each block in the reverse postorder (at least one predecessor should be allocated for each block). + var nilBlk B + for blk := f.ReversePostOrderBlockIteratorBegin(); blk != nilBlk; blk = f.ReversePostOrderBlockIteratorNext() { + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("========== allocating blk%d ========\n", blk.ID()) + } + if blk.Entry() { + a.finalizeStartReg(f, blk) + } + a.allocBlock(f, blk) + } + // After the allocation, we all know the start and end state of each block. So we can fix the merge states. + for blk := f.ReversePostOrderBlockIteratorBegin(); blk != nilBlk; blk = f.ReversePostOrderBlockIteratorNext() { + a.fixMergeState(f, blk) + } + // Finally, we insert the spill instructions as we know all the places where the reloads happen. + a.scheduleSpills(f) +} + +func (a *Allocator[I, B, F]) updateLiveInVRState(liveness *blockState[I, B, F]) { + currentBlockID := a.state.currentBlockID + for _, vs := range liveness.liveIns { + vs.lastUse = programCounterLiveIn + vs.lastUseUpdatedAtBlockID = currentBlockID + } +} + +func (a *Allocator[I, B, F]) finalizeStartReg(f F, blk B) { + bID := blk.ID() + s := &a.state + currentBlkState := a.getOrAllocateBlockState(bID) + if currentBlkState.startFromPredIndex > -1 { + return + } + + s.currentBlockID = bID + a.updateLiveInVRState(currentBlkState) + + preds := blk.Preds() + var predState *blockState[I, B, F] + switch preds { + case 0: // This is the entry block. + case 1: + predID := f.Pred(blk, 0).ID() + predState = a.getOrAllocateBlockState(predID) + currentBlkState.startFromPredIndex = 0 + default: + // TODO: there should be some better heuristic to choose the predecessor. + for i := 0; i < preds; i++ { + predID := f.Pred(blk, i).ID() + if _predState := a.getOrAllocateBlockState(predID); _predState.visited { + predState = _predState + currentBlkState.startFromPredIndex = i + break + } + } + } + if predState == nil { + if !blk.Entry() { + panic(fmt.Sprintf("BUG: at lease one predecessor should be visited for blk%d", blk.ID())) + } + for _, u := range s.argRealRegs { + s.useRealReg(u.RealReg(), s.getVRegState(u.ID())) + } + currentBlkState.startFromPredIndex = 0 + } else { + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("allocating blk%d starting from blk%d (on index=%d) \n", + bID, f.Pred(blk, currentBlkState.startFromPredIndex).ID(), currentBlkState.startFromPredIndex) + } + s.resetAt(predState) + } + + s.regsInUse.range_(func(allocated RealReg, v *vrState[I, B, F]) { + currentBlkState.startRegs.add(allocated, v) + }) + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("finalized start reg for blk%d: %s\n", blk.ID(), currentBlkState.startRegs.format(a.regInfo)) + } +} + +func (a *Allocator[I, B, F]) allocBlock(f F, blk B) { + bID := blk.ID() + s := &a.state + currentBlkState := a.getOrAllocateBlockState(bID) + s.currentBlockID = bID + + if currentBlkState.startFromPredIndex < 0 { + panic("BUG: startFromPredIndex should be set in finalizeStartReg prior to allocBlock") + } + + // Clears the previous state. + s.regsInUse.range_(func(allocatedRealReg RealReg, vr *vrState[I, B, F]) { vr.r = RealRegInvalid }) + s.regsInUse.reset() + // Then set the start state. + currentBlkState.startRegs.range_(func(allocatedRealReg RealReg, vr *vrState[I, B, F]) { s.useRealReg(allocatedRealReg, vr) }) + + desiredUpdated := a.ss[:0] + + // Update the last use of each VReg. + a.copies = a.copies[:0] // Stores the copy instructions. + var pc programCounter + var nilInstr I + for instr := blk.InstrIteratorBegin(); instr != nilInstr; instr = blk.InstrIteratorNext() { + var useState *vrState[I, B, F] + for _, use := range instr.Uses(&a.vs) { + useState = s.getVRegState(use.ID()) + if !use.IsRealReg() { + useState.lastUse = pc + } + } + + if instr.IsCopy() { + def := instr.Defs(&a.vs)[0] + a.copies = append(a.copies, _copy[I, B, F]{src: useState, dstID: def.ID()}) + r := def.RealReg() + if r != RealRegInvalid { + if !useState.isPhi { // TODO: no idea why do we need this. + useState.desiredLoc = newDesiredLocReg(r) + desiredUpdated = append(desiredUpdated, useState) + } + } + } + pc++ + } + + // Mark all live-out values by checking live-in of the successors. + // While doing so, we also update the desired register values. + var succ B + var nilBlk B + for i, ns := 0, blk.Succs(); i < ns; i++ { + succ = f.Succ(blk, i) + if succ == nilBlk { + continue + } + + succID := succ.ID() + succState := a.getOrAllocateBlockState(succID) + for _, st := range succState.liveIns { + if st.phiBlk() != succ { + st.lastUse = programCounterLiveOut + } + } + + if succState.startFromPredIndex > -1 { + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("blk%d -> blk%d: start_regs: %s\n", bID, succID, succState.startRegs.format(a.regInfo)) + } + succState.startRegs.range_(func(allocatedRealReg RealReg, vs *vrState[I, B, F]) { + vs.desiredLoc = newDesiredLocReg(allocatedRealReg) + desiredUpdated = append(desiredUpdated, vs) + }) + for _, p := range f.BlockParams(succ, &a.vs) { + vs := s.getVRegState(p.ID()) + if vs.desiredLoc.realReg() == RealRegInvalid { + vs.desiredLoc = desiredLocStack + desiredUpdated = append(desiredUpdated, vs) + } + } + } + } + + // Propagate the desired register values from the end of the block to the beginning. + for _, instr := range a.copies { + defState := s.getVRegState(instr.dstID) + desired := defState.desiredLoc.realReg() + useState := instr.src + if useState.phiBlk() != succ && useState.desiredLoc == desiredLocUnspecified { + useState.desiredLoc = newDesiredLocReg(desired) + desiredUpdated = append(desiredUpdated, useState) + } + } + + pc = 0 + for instr := blk.InstrIteratorBegin(); instr != nilInstr; instr = blk.InstrIteratorNext() { + if wazevoapi.RegAllocLoggingEnabled { + fmt.Println(instr) + } + + var currentUsedSet RegSet + killSet := a.reals[:0] + + // Gather the set of registers that will be used in the current instruction. + uses := instr.Uses(&a.vs) + for _, use := range uses { + if use.IsRealReg() { + r := use.RealReg() + currentUsedSet = currentUsedSet.add(r) + if a.allocatableSet.has(r) { + killSet = append(killSet, r) + } + } else { + vs := s.getVRegState(use.ID()) + if r := vs.r; r != RealRegInvalid { + currentUsedSet = currentUsedSet.add(r) + } + } + } + + for i, use := range uses { + if !use.IsRealReg() { + vs := s.getVRegState(use.ID()) + killed := vs.lastUse == pc + r := vs.r + + if r == RealRegInvalid { + r = a.findOrSpillAllocatable(s, a.regInfo.AllocatableRegisters[use.RegType()], currentUsedSet, + // Prefer the desired register if it's available. + vs.desiredLoc.realReg()) + vs.recordReload(f, blk) + f.ReloadRegisterBefore(use.SetRealReg(r), instr) + s.useRealReg(r, vs) + } + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("\ttrying to use v%v on %s\n", use.ID(), a.regInfo.RealRegName(r)) + } + instr.AssignUse(i, use.SetRealReg(r)) + currentUsedSet = currentUsedSet.add(r) + if killed { + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("\tkill v%d with %s\n", use.ID(), a.regInfo.RealRegName(r)) + } + killSet = append(killSet, r) + } + } + } + + isIndirect := instr.IsIndirectCall() + if instr.IsCall() || isIndirect { + addr := RealRegInvalid + if isIndirect { + addr = a.vs[0].RealReg() + } + a.releaseCallerSavedRegs(addr) + } + + for _, r := range killSet { + s.releaseRealReg(r) + } + a.reals = killSet + + defs := instr.Defs(&a.vs) + switch len(defs) { + default: + // Some instructions define multiple values on real registers. + // E.g. call instructions (following calling convention) / div instruction on x64 that defines both rax and rdx. + // + // Note that currently I assume that such instructions define only the pre colored real registers, not the VRegs + // that require allocations. If we need to support such case, we need to add the logic to handle it here, + // though is there any such instruction? + for _, def := range defs { + if !def.IsRealReg() { + panic("BUG: multiple defs should be on real registers") + } + r := def.RealReg() + if s.regsInUse.has(r) { + s.releaseRealReg(r) + } + s.useRealReg(r, s.getVRegState(def.ID())) + } + case 0: + case 1: + def := defs[0] + vState := s.getVRegState(def.ID()) + if def.IsRealReg() { + r := def.RealReg() + if a.allocatableSet.has(r) { + if s.regsInUse.has(r) { + s.releaseRealReg(r) + } + s.useRealReg(r, vState) + } + } else { + r := vState.r + + if desired := vState.desiredLoc.realReg(); desired != RealRegInvalid { + if r != desired { + if (vState.isPhi && vState.defBlk == succ) || + // If this is not a phi and it's already assigned a real reg, + // this value has multiple definitions, hence we cannot assign the desired register. + (!s.regsInUse.has(desired) && r == RealRegInvalid) { + // If the phi value is passed via a real register, we force the value to be in the desired register. + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("\t\tv%d is phi and desiredReg=%s\n", def.ID(), a.regInfo.RealRegName(desired)) + } + if r != RealRegInvalid { + // If the value is already in a different real register, we release it to change the state. + // Otherwise, multiple registers might have the same values at the end, which results in + // messing up the merge state reconciliation. + s.releaseRealReg(r) + } + r = desired + s.releaseRealReg(r) + s.useRealReg(r, vState) + } + } + } + + // Allocate a new real register if `def` is not currently assigned one. + // It can happen when multiple instructions define the same VReg (e.g. const loads). + if r == RealRegInvalid { + if instr.IsCopy() { + copySrc := instr.Uses(&a.vs)[0].RealReg() + if a.allocatableSet.has(copySrc) && !s.regsInUse.has(copySrc) { + r = copySrc + } + } + if r == RealRegInvalid { + typ := def.RegType() + r = a.findOrSpillAllocatable(s, a.regInfo.AllocatableRegisters[typ], RegSet(0), RealRegInvalid) + } + s.useRealReg(r, vState) + } + dr := def.SetRealReg(r) + instr.AssignDef(dr) + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("\tdefining v%d with %s\n", def.ID(), a.regInfo.RealRegName(r)) + } + if vState.isPhi { + if vState.desiredLoc.stack() { // Stack based phi value. + f.StoreRegisterAfter(dr, instr) + // Release the real register as it's not used anymore. + s.releaseRealReg(r) + } else { + // Only the register based phis are necessary to track the defining instructions + // since the stack-based phis are already having stores inserted ^. + n := a.phiDefInstListPool.Allocate() + n.instr = instr + n.next = vState.phiDefInstList + n.v = dr + vState.phiDefInstList = n + } + } else { + vState.defInstr = instr + vState.defBlk = blk + } + } + } + if wazevoapi.RegAllocLoggingEnabled { + fmt.Println(instr) + } + pc++ + } + + s.regsInUse.range_(func(allocated RealReg, v *vrState[I, B, F]) { currentBlkState.endRegs.add(allocated, v) }) + + currentBlkState.visited = true + if wazevoapi.RegAllocLoggingEnabled { + currentBlkState.dump(a.regInfo) + } + + // Reset the desired end location. + for _, vs := range desiredUpdated { + vs.desiredLoc = desiredLocUnspecified + } + a.ss = desiredUpdated[:0] + + for i := 0; i < blk.Succs(); i++ { + succ := f.Succ(blk, i) + if succ == nilBlk { + continue + } + // If the successor is not visited yet, finalize the start state. + a.finalizeStartReg(f, succ) + } +} + +func (a *Allocator[I, B, F]) releaseCallerSavedRegs(addrReg RealReg) { + s := &a.state + + for allocated := RealReg(0); allocated < 64; allocated++ { + if allocated == addrReg { // If this is the call indirect, we should not touch the addr register. + continue + } + if vs := s.regsInUse.get(allocated); vs != nil { + if vs.v.IsRealReg() { + continue // This is the argument register as it's already used by VReg backed by the corresponding RealReg. + } + if !a.regInfo.CallerSavedRegisters.has(allocated) { + // If this is not a caller-saved register, it is safe to keep it across the call. + continue + } + s.releaseRealReg(allocated) + } + } +} + +func (a *Allocator[I, B, F]) fixMergeState(f F, blk B) { + preds := blk.Preds() + if preds <= 1 { + return + } + + s := &a.state + + // Restores the state at the beginning of the block. + bID := blk.ID() + blkSt := a.getOrAllocateBlockState(bID) + desiredOccupants := &blkSt.startRegs + var desiredOccupantsSet RegSet + for i, v := range desiredOccupants { + if v != nil { + desiredOccupantsSet = desiredOccupantsSet.add(RealReg(i)) + } + } + + if wazevoapi.RegAllocLoggingEnabled { + fmt.Println("fixMergeState", blk.ID(), ":", desiredOccupants.format(a.regInfo)) + } + + s.currentBlockID = bID + a.updateLiveInVRState(blkSt) + + for i := 0; i < preds; i++ { + if i == blkSt.startFromPredIndex { + continue + } + + pred := f.Pred(blk, i) + predSt := a.getOrAllocateBlockState(pred.ID()) + + s.resetAt(predSt) + + // Finds the free registers if any. + intTmp, floatTmp := VRegInvalid, VRegInvalid + if intFree := s.findAllocatable( + a.regInfo.AllocatableRegisters[RegTypeInt], desiredOccupantsSet, + ); intFree != RealRegInvalid { + intTmp = FromRealReg(intFree, RegTypeInt) + } + if floatFree := s.findAllocatable( + a.regInfo.AllocatableRegisters[RegTypeFloat], desiredOccupantsSet, + ); floatFree != RealRegInvalid { + floatTmp = FromRealReg(floatFree, RegTypeFloat) + } + + for r := RealReg(0); r < 64; r++ { + desiredVReg := desiredOccupants.get(r) + if desiredVReg == nil { + continue + } + + currentVReg := s.regsInUse.get(r) + if currentVReg != nil && desiredVReg.v.ID() == currentVReg.v.ID() { + continue + } + + typ := desiredVReg.v.RegType() + var tmpRealReg VReg + if typ == RegTypeInt { + tmpRealReg = intTmp + } else { + tmpRealReg = floatTmp + } + a.reconcileEdge(f, r, pred, currentVReg, desiredVReg, tmpRealReg, typ) + } + } +} + +// reconcileEdge reconciles the register state between the current block and the predecessor for the real register `r`. +// +// - currentVReg is the current VReg value that sits on the register `r`. This can be VRegInvalid if the register is not used at the end of the predecessor. +// - desiredVReg is the desired VReg value that should be on the register `r`. +// - freeReg is the temporary register that can be used to swap the values, which may or may not be used. +// - typ is the register type of the `r`. +func (a *Allocator[I, B, F]) reconcileEdge(f F, + r RealReg, + pred B, + currentState, desiredState *vrState[I, B, F], + freeReg VReg, + typ RegType, +) { + desiredVReg := desiredState.v + currentVReg := VRegInvalid + if currentState != nil { + currentVReg = currentState.v + } + // There are four cases to consider: + // 1. currentVReg is valid, but desiredVReg is on the stack. + // 2. Both currentVReg and desiredVReg are valid. + // 3. Desired is on a different register than `r` and currentReg is not valid. + // 4. Desired is on the stack and currentReg is not valid. + + s := &a.state + if currentVReg.Valid() { + er := desiredState.r + if er == RealRegInvalid { + // Case 1: currentVReg is valid, but desiredVReg is on the stack. + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("\t\tv%d is desired to be on %s, but currently on the stack\n", + desiredVReg.ID(), a.regInfo.RealRegName(r), + ) + } + // We need to move the current value to the stack, and reload the desired value into the register. + // TODO: we can do better here. + f.StoreRegisterBefore(currentVReg.SetRealReg(r), pred.LastInstrForInsertion()) + s.releaseRealReg(r) + + desiredState.recordReload(f, pred) + f.ReloadRegisterBefore(desiredVReg.SetRealReg(r), pred.LastInstrForInsertion()) + s.useRealReg(r, desiredState) + return + } else { + // Case 2: Both currentVReg and desiredVReg are valid. + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("\t\tv%d is desired to be on %s, but currently on %s\n", + desiredVReg.ID(), a.regInfo.RealRegName(r), a.regInfo.RealRegName(er), + ) + } + // This case, we need to swap the values between the current and desired values. + f.SwapBefore( + currentVReg.SetRealReg(r), + desiredVReg.SetRealReg(er), + freeReg, + pred.LastInstrForInsertion(), + ) + s.allocatedRegSet = s.allocatedRegSet.add(freeReg.RealReg()) + s.releaseRealReg(r) + s.releaseRealReg(er) + s.useRealReg(r, desiredState) + s.useRealReg(er, currentState) + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("\t\tv%d previously on %s moved to %s\n", currentVReg.ID(), a.regInfo.RealRegName(r), a.regInfo.RealRegName(er)) + } + } + } else { + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("\t\tv%d is desired to be on %s, current not used\n", + desiredVReg.ID(), a.regInfo.RealRegName(r), + ) + } + if currentReg := desiredState.r; currentReg != RealRegInvalid { + // Case 3: Desired is on a different register than `r` and currentReg is not valid. + // We simply need to move the desired value to the register. + f.InsertMoveBefore( + FromRealReg(r, typ), + desiredVReg.SetRealReg(currentReg), + pred.LastInstrForInsertion(), + ) + s.releaseRealReg(currentReg) + } else { + // Case 4: Both currentVReg and desiredVReg are not valid. + // We simply need to reload the desired value into the register. + desiredState.recordReload(f, pred) + f.ReloadRegisterBefore(desiredVReg.SetRealReg(r), pred.LastInstrForInsertion()) + } + s.useRealReg(r, desiredState) + } +} + +func (a *Allocator[I, B, F]) scheduleSpills(f F) { + states := a.state.vrStates + for i := 0; i <= states.MaxIDEncountered(); i++ { + vs := states.Get(i) + if vs == nil { + continue + } + if vs.spilled { + a.scheduleSpill(f, vs) + } + } +} + +func (a *Allocator[I, B, F]) scheduleSpill(f F, vs *vrState[I, B, F]) { + v := vs.v + // If the value is the phi value, we need to insert a spill after each phi definition. + if vs.isPhi { + for defInstr := vs.phiDefInstList; defInstr != nil; defInstr = defInstr.next { + f.StoreRegisterAfter(defInstr.v, defInstr.instr) + } + return + } + + pos := vs.lca + definingBlk := vs.defBlk + r := RealRegInvalid + var nilBlk B + if definingBlk == nilBlk { + panic(fmt.Sprintf("BUG: definingBlk should not be nil for %s. This is likley a bug in backend lowering logic", vs.v.String())) + } + if pos == nilBlk { + panic(fmt.Sprintf("BUG: pos should not be nil for %s. This is likley a bug in backend lowering logic", vs.v.String())) + } + + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("v%d is spilled in blk%d, lca=blk%d\n", v.ID(), definingBlk.ID(), pos.ID()) + } + for pos != definingBlk { + st := a.getOrAllocateBlockState(pos.ID()) + for rr := RealReg(0); rr < 64; rr++ { + if vs := st.startRegs.get(rr); vs != nil && vs.v == v { + r = rr + // Already in the register, so we can place the spill at the beginning of the block. + break + } + } + + if r != RealRegInvalid { + break + } + + pos = f.Idom(pos) + } + + if pos == definingBlk { + defInstr := vs.defInstr + defInstr.Defs(&a.vs) + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("schedule spill v%d after %v\n", v.ID(), defInstr) + } + f.StoreRegisterAfter(a.vs[0], defInstr) + } else { + // Found an ancestor block that holds the value in the register at the beginning of the block. + // We need to insert a spill before the last use. + first := pos.FirstInstr() + if wazevoapi.RegAllocLoggingEnabled { + fmt.Printf("schedule spill v%d before %v\n", v.ID(), first) + } + f.StoreRegisterAfter(v.SetRealReg(r), first) + } +} + +// Reset resets the allocator's internal state so that it can be reused. +func (a *Allocator[I, B, F]) Reset() { + a.state.reset() + a.blockStates.Reset() + a.phiDefInstListPool.Reset() + a.vs = a.vs[:0] +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc/regset.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc/regset.go new file mode 100644 index 00000000000..ce84c9c0cdd --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc/regset.go @@ -0,0 +1,96 @@ +package regalloc + +import ( + "fmt" + "strings" +) + +// NewRegSet returns a new RegSet with the given registers. +func NewRegSet(regs ...RealReg) RegSet { + var ret RegSet + for _, r := range regs { + ret = ret.add(r) + } + return ret +} + +// RegSet represents a set of registers. +type RegSet uint64 + +func (rs RegSet) format(info *RegisterInfo) string { //nolint:unused + var ret []string + for i := 0; i < 64; i++ { + if rs&(1<= 64 { + return rs + } + return rs | 1<v%d)", info.RealRegName(RealReg(i)), vr.v.ID())) + } + } + return strings.Join(ret, ", ") +} + +func (rs *regInUseSet[I, B, F]) has(r RealReg) bool { + return r < 64 && rs[r] != nil +} + +func (rs *regInUseSet[I, B, F]) get(r RealReg) *vrState[I, B, F] { + return rs[r] +} + +func (rs *regInUseSet[I, B, F]) remove(r RealReg) { + rs[r] = nil +} + +func (rs *regInUseSet[I, B, F]) add(r RealReg, vr *vrState[I, B, F]) { + if r >= 64 { + return + } + rs[r] = vr +} + +func (rs *regInUseSet[I, B, F]) range_(f func(allocatedRealReg RealReg, vr *vrState[I, B, F])) { + for i, vr := range rs { + if vr != nil { + f(RealReg(i), vr) + } + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/vdef.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/vdef.go new file mode 100644 index 00000000000..47a275a3ab5 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/vdef.go @@ -0,0 +1,19 @@ +package backend + +import ( + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" +) + +// SSAValueDefinition represents a definition of an SSA value. +type SSAValueDefinition struct { + V ssa.Value + // Instr is not nil if this is a definition from an instruction. + Instr *ssa.Instruction + // RefCount is the number of references to the result. + RefCount uint32 +} + +// IsFromInstr returns true if this definition is from an instruction. +func (d *SSAValueDefinition) IsFromInstr() bool { + return d.Instr != nil +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/call_engine.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/call_engine.go new file mode 100644 index 00000000000..639429a63f4 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/call_engine.go @@ -0,0 +1,708 @@ +package wazevo + +import ( + "context" + "fmt" + "reflect" + "runtime" + "sync/atomic" + "unsafe" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" + "github.com/tetratelabs/wazero/internal/expctxkeys" + "github.com/tetratelabs/wazero/internal/internalapi" + "github.com/tetratelabs/wazero/internal/wasm" + "github.com/tetratelabs/wazero/internal/wasmdebug" + "github.com/tetratelabs/wazero/internal/wasmruntime" +) + +type ( + // callEngine implements api.Function. + callEngine struct { + internalapi.WazeroOnly + stack []byte + // stackTop is the pointer to the *aligned* top of the stack. This must be updated + // whenever the stack is changed. This is passed to the assembly function + // at the very beginning of api.Function Call/CallWithStack. + stackTop uintptr + // executable is the pointer to the executable code for this function. + executable *byte + preambleExecutable *byte + // parent is the *moduleEngine from which this callEngine is created. + parent *moduleEngine + // indexInModule is the index of the function in the module. + indexInModule wasm.Index + // sizeOfParamResultSlice is the size of the parameter/result slice. + sizeOfParamResultSlice int + requiredParams int + // execCtx holds various information to be read/written by assembly functions. + execCtx executionContext + // execCtxPtr holds the pointer to the executionContext which doesn't change after callEngine is created. + execCtxPtr uintptr + numberOfResults int + stackIteratorImpl stackIterator + } + + // executionContext is the struct to be read/written by assembly functions. + executionContext struct { + // exitCode holds the wazevoapi.ExitCode describing the state of the function execution. + exitCode wazevoapi.ExitCode + // callerModuleContextPtr holds the moduleContextOpaque for Go function calls. + callerModuleContextPtr *byte + // originalFramePointer holds the original frame pointer of the caller of the assembly function. + originalFramePointer uintptr + // originalStackPointer holds the original stack pointer of the caller of the assembly function. + originalStackPointer uintptr + // goReturnAddress holds the return address to go back to the caller of the assembly function. + goReturnAddress uintptr + // stackBottomPtr holds the pointer to the bottom of the stack. + stackBottomPtr *byte + // goCallReturnAddress holds the return address to go back to the caller of the Go function. + goCallReturnAddress *byte + // stackPointerBeforeGoCall holds the stack pointer before calling a Go function. + stackPointerBeforeGoCall *uint64 + // stackGrowRequiredSize holds the required size of stack grow. + stackGrowRequiredSize uintptr + // memoryGrowTrampolineAddress holds the address of memory grow trampoline function. + memoryGrowTrampolineAddress *byte + // stackGrowCallTrampolineAddress holds the address of stack grow trampoline function. + stackGrowCallTrampolineAddress *byte + // checkModuleExitCodeTrampolineAddress holds the address of check-module-exit-code function. + checkModuleExitCodeTrampolineAddress *byte + // savedRegisters is the opaque spaces for save/restore registers. + // We want to align 16 bytes for each register, so we use [64][2]uint64. + savedRegisters [64][2]uint64 + // goFunctionCallCalleeModuleContextOpaque is the pointer to the target Go function's moduleContextOpaque. + goFunctionCallCalleeModuleContextOpaque uintptr + // tableGrowTrampolineAddress holds the address of table grow trampoline function. + tableGrowTrampolineAddress *byte + // refFuncTrampolineAddress holds the address of ref-func trampoline function. + refFuncTrampolineAddress *byte + // memmoveAddress holds the address of memmove function implemented by Go runtime. See memmove.go. + memmoveAddress uintptr + // framePointerBeforeGoCall holds the frame pointer before calling a Go function. Note: only used in amd64. + framePointerBeforeGoCall uintptr + // memoryWait32TrampolineAddress holds the address of memory_wait32 trampoline function. + memoryWait32TrampolineAddress *byte + // memoryWait32TrampolineAddress holds the address of memory_wait64 trampoline function. + memoryWait64TrampolineAddress *byte + // memoryNotifyTrampolineAddress holds the address of the memory_notify trampoline function. + memoryNotifyTrampolineAddress *byte + } +) + +func (c *callEngine) requiredInitialStackSize() int { + const initialStackSizeDefault = 10240 + stackSize := initialStackSizeDefault + paramResultInBytes := c.sizeOfParamResultSlice * 8 * 2 // * 8 because uint64 is 8 bytes, and *2 because we need both separated param/result slots. + required := paramResultInBytes + 32 + 16 // 32 is enough to accommodate the call frame info, and 16 exists just in case when []byte is not aligned to 16 bytes. + if required > stackSize { + stackSize = required + } + return stackSize +} + +func (c *callEngine) init() { + stackSize := c.requiredInitialStackSize() + if wazevoapi.StackGuardCheckEnabled { + stackSize += wazevoapi.StackGuardCheckGuardPageSize + } + c.stack = make([]byte, stackSize) + c.stackTop = alignedStackTop(c.stack) + if wazevoapi.StackGuardCheckEnabled { + c.execCtx.stackBottomPtr = &c.stack[wazevoapi.StackGuardCheckGuardPageSize] + } else { + c.execCtx.stackBottomPtr = &c.stack[0] + } + c.execCtxPtr = uintptr(unsafe.Pointer(&c.execCtx)) +} + +// alignedStackTop returns 16-bytes aligned stack top of given stack. +// 16 bytes should be good for all platform (arm64/amd64). +func alignedStackTop(s []byte) uintptr { + stackAddr := uintptr(unsafe.Pointer(&s[len(s)-1])) + return stackAddr - (stackAddr & (16 - 1)) +} + +// Definition implements api.Function. +func (c *callEngine) Definition() api.FunctionDefinition { + return c.parent.module.Source.FunctionDefinition(c.indexInModule) +} + +// Call implements api.Function. +func (c *callEngine) Call(ctx context.Context, params ...uint64) ([]uint64, error) { + if c.requiredParams != len(params) { + return nil, fmt.Errorf("expected %d params, but passed %d", c.requiredParams, len(params)) + } + paramResultSlice := make([]uint64, c.sizeOfParamResultSlice) + copy(paramResultSlice, params) + if err := c.callWithStack(ctx, paramResultSlice); err != nil { + return nil, err + } + return paramResultSlice[:c.numberOfResults], nil +} + +func (c *callEngine) addFrame(builder wasmdebug.ErrorBuilder, addr uintptr) (def api.FunctionDefinition, listener experimental.FunctionListener) { + eng := c.parent.parent.parent + cm := eng.compiledModuleOfAddr(addr) + if cm == nil { + // This case, the module might have been closed and deleted from the engine. + // We fall back to searching the imported modules that can be referenced from this callEngine. + + // First, we check itself. + if checkAddrInBytes(addr, c.parent.parent.executable) { + cm = c.parent.parent + } else { + // Otherwise, search all imported modules. TODO: maybe recursive, but not sure it's useful in practice. + p := c.parent + for i := range p.importedFunctions { + candidate := p.importedFunctions[i].me.parent + if checkAddrInBytes(addr, candidate.executable) { + cm = candidate + break + } + } + } + } + + if cm != nil { + index := cm.functionIndexOf(addr) + def = cm.module.FunctionDefinition(cm.module.ImportFunctionCount + index) + var sources []string + if dw := cm.module.DWARFLines; dw != nil { + sourceOffset := cm.getSourceOffset(addr) + sources = dw.Line(sourceOffset) + } + builder.AddFrame(def.DebugName(), def.ParamTypes(), def.ResultTypes(), sources) + if len(cm.listeners) > 0 { + listener = cm.listeners[index] + } + } + return +} + +// CallWithStack implements api.Function. +func (c *callEngine) CallWithStack(ctx context.Context, paramResultStack []uint64) (err error) { + if c.sizeOfParamResultSlice > len(paramResultStack) { + return fmt.Errorf("need %d params, but stack size is %d", c.sizeOfParamResultSlice, len(paramResultStack)) + } + return c.callWithStack(ctx, paramResultStack) +} + +// CallWithStack implements api.Function. +func (c *callEngine) callWithStack(ctx context.Context, paramResultStack []uint64) (err error) { + snapshotEnabled := ctx.Value(expctxkeys.EnableSnapshotterKey{}) != nil + if snapshotEnabled { + ctx = context.WithValue(ctx, expctxkeys.SnapshotterKey{}, c) + } + + if wazevoapi.StackGuardCheckEnabled { + defer func() { + wazevoapi.CheckStackGuardPage(c.stack) + }() + } + + p := c.parent + ensureTermination := p.parent.ensureTermination + m := p.module + if ensureTermination { + select { + case <-ctx.Done(): + // If the provided context is already done, close the module and return the error. + m.CloseWithCtxErr(ctx) + return m.FailIfClosed() + default: + } + } + + var paramResultPtr *uint64 + if len(paramResultStack) > 0 { + paramResultPtr = ¶mResultStack[0] + } + defer func() { + r := recover() + if s, ok := r.(*snapshot); ok { + // A snapshot that wasn't handled was created by a different call engine possibly from a nested wasm invocation, + // let it propagate up to be handled by the caller. + panic(s) + } + if r != nil { + type listenerForAbort struct { + def api.FunctionDefinition + lsn experimental.FunctionListener + } + + var listeners []listenerForAbort + builder := wasmdebug.NewErrorBuilder() + def, lsn := c.addFrame(builder, uintptr(unsafe.Pointer(c.execCtx.goCallReturnAddress))) + if lsn != nil { + listeners = append(listeners, listenerForAbort{def, lsn}) + } + returnAddrs := unwindStack( + uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), + c.execCtx.framePointerBeforeGoCall, + c.stackTop, + nil, + ) + for _, retAddr := range returnAddrs[:len(returnAddrs)-1] { // the last return addr is the trampoline, so we skip it. + def, lsn = c.addFrame(builder, retAddr) + if lsn != nil { + listeners = append(listeners, listenerForAbort{def, lsn}) + } + } + err = builder.FromRecovered(r) + + for _, lsn := range listeners { + lsn.lsn.Abort(ctx, m, lsn.def, err) + } + } else { + if err != wasmruntime.ErrRuntimeStackOverflow { // Stackoverflow case shouldn't be panic (to avoid extreme stack unwinding). + err = c.parent.module.FailIfClosed() + } + } + + if err != nil { + // Ensures that we can reuse this callEngine even after an error. + c.execCtx.exitCode = wazevoapi.ExitCodeOK + } + }() + + if ensureTermination { + done := m.CloseModuleOnCanceledOrTimeout(ctx) + defer done() + } + + if c.stackTop&(16-1) != 0 { + panic("BUG: stack must be aligned to 16 bytes") + } + entrypoint(c.preambleExecutable, c.executable, c.execCtxPtr, c.parent.opaquePtr, paramResultPtr, c.stackTop) + for { + switch ec := c.execCtx.exitCode; ec & wazevoapi.ExitCodeMask { + case wazevoapi.ExitCodeOK: + return nil + case wazevoapi.ExitCodeGrowStack: + oldsp := uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)) + oldTop := c.stackTop + oldStack := c.stack + var newsp, newfp uintptr + if wazevoapi.StackGuardCheckEnabled { + newsp, newfp, err = c.growStackWithGuarded() + } else { + newsp, newfp, err = c.growStack() + } + if err != nil { + return err + } + adjustClonedStack(oldsp, oldTop, newsp, newfp, c.stackTop) + // Old stack must be alive until the new stack is adjusted. + runtime.KeepAlive(oldStack) + c.execCtx.exitCode = wazevoapi.ExitCodeOK + afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, newsp, newfp) + case wazevoapi.ExitCodeGrowMemory: + mod := c.callerModuleInstance() + mem := mod.MemoryInstance + s := goCallStackView(c.execCtx.stackPointerBeforeGoCall) + argRes := &s[0] + if res, ok := mem.Grow(uint32(*argRes)); !ok { + *argRes = uint64(0xffffffff) // = -1 in signed 32-bit integer. + } else { + *argRes = uint64(res) + } + c.execCtx.exitCode = wazevoapi.ExitCodeOK + afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) + case wazevoapi.ExitCodeTableGrow: + mod := c.callerModuleInstance() + s := goCallStackView(c.execCtx.stackPointerBeforeGoCall) + tableIndex, num, ref := uint32(s[0]), uint32(s[1]), uintptr(s[2]) + table := mod.Tables[tableIndex] + s[0] = uint64(uint32(int32(table.Grow(num, ref)))) + c.execCtx.exitCode = wazevoapi.ExitCodeOK + afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, + uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) + case wazevoapi.ExitCodeCallGoFunction: + index := wazevoapi.GoFunctionIndexFromExitCode(ec) + f := hostModuleGoFuncFromOpaque[api.GoFunction](index, c.execCtx.goFunctionCallCalleeModuleContextOpaque) + func() { + if snapshotEnabled { + defer snapshotRecoverFn(c) + } + f.Call(ctx, goCallStackView(c.execCtx.stackPointerBeforeGoCall)) + }() + // Back to the native code. + c.execCtx.exitCode = wazevoapi.ExitCodeOK + afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, + uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) + case wazevoapi.ExitCodeCallGoFunctionWithListener: + index := wazevoapi.GoFunctionIndexFromExitCode(ec) + f := hostModuleGoFuncFromOpaque[api.GoFunction](index, c.execCtx.goFunctionCallCalleeModuleContextOpaque) + listeners := hostModuleListenersSliceFromOpaque(c.execCtx.goFunctionCallCalleeModuleContextOpaque) + s := goCallStackView(c.execCtx.stackPointerBeforeGoCall) + // Call Listener.Before. + callerModule := c.callerModuleInstance() + listener := listeners[index] + hostModule := hostModuleFromOpaque(c.execCtx.goFunctionCallCalleeModuleContextOpaque) + def := hostModule.FunctionDefinition(wasm.Index(index)) + listener.Before(ctx, callerModule, def, s, c.stackIterator(true)) + // Call into the Go function. + func() { + if snapshotEnabled { + defer snapshotRecoverFn(c) + } + f.Call(ctx, s) + }() + // Call Listener.After. + listener.After(ctx, callerModule, def, s) + // Back to the native code. + c.execCtx.exitCode = wazevoapi.ExitCodeOK + afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, + uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) + case wazevoapi.ExitCodeCallGoModuleFunction: + index := wazevoapi.GoFunctionIndexFromExitCode(ec) + f := hostModuleGoFuncFromOpaque[api.GoModuleFunction](index, c.execCtx.goFunctionCallCalleeModuleContextOpaque) + mod := c.callerModuleInstance() + func() { + if snapshotEnabled { + defer snapshotRecoverFn(c) + } + f.Call(ctx, mod, goCallStackView(c.execCtx.stackPointerBeforeGoCall)) + }() + // Back to the native code. + c.execCtx.exitCode = wazevoapi.ExitCodeOK + afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, + uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) + case wazevoapi.ExitCodeCallGoModuleFunctionWithListener: + index := wazevoapi.GoFunctionIndexFromExitCode(ec) + f := hostModuleGoFuncFromOpaque[api.GoModuleFunction](index, c.execCtx.goFunctionCallCalleeModuleContextOpaque) + listeners := hostModuleListenersSliceFromOpaque(c.execCtx.goFunctionCallCalleeModuleContextOpaque) + s := goCallStackView(c.execCtx.stackPointerBeforeGoCall) + // Call Listener.Before. + callerModule := c.callerModuleInstance() + listener := listeners[index] + hostModule := hostModuleFromOpaque(c.execCtx.goFunctionCallCalleeModuleContextOpaque) + def := hostModule.FunctionDefinition(wasm.Index(index)) + listener.Before(ctx, callerModule, def, s, c.stackIterator(true)) + // Call into the Go function. + func() { + if snapshotEnabled { + defer snapshotRecoverFn(c) + } + f.Call(ctx, callerModule, s) + }() + // Call Listener.After. + listener.After(ctx, callerModule, def, s) + // Back to the native code. + c.execCtx.exitCode = wazevoapi.ExitCodeOK + afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, + uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) + case wazevoapi.ExitCodeCallListenerBefore: + stack := goCallStackView(c.execCtx.stackPointerBeforeGoCall) + index := wasm.Index(stack[0]) + mod := c.callerModuleInstance() + listener := mod.Engine.(*moduleEngine).listeners[index] + def := mod.Source.FunctionDefinition(index + mod.Source.ImportFunctionCount) + listener.Before(ctx, mod, def, stack[1:], c.stackIterator(false)) + c.execCtx.exitCode = wazevoapi.ExitCodeOK + afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, + uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) + case wazevoapi.ExitCodeCallListenerAfter: + stack := goCallStackView(c.execCtx.stackPointerBeforeGoCall) + index := wasm.Index(stack[0]) + mod := c.callerModuleInstance() + listener := mod.Engine.(*moduleEngine).listeners[index] + def := mod.Source.FunctionDefinition(index + mod.Source.ImportFunctionCount) + listener.After(ctx, mod, def, stack[1:]) + c.execCtx.exitCode = wazevoapi.ExitCodeOK + afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, + uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) + case wazevoapi.ExitCodeCheckModuleExitCode: + // Note: this operation must be done in Go, not native code. The reason is that + // native code cannot be preempted and that means it can block forever if there are not + // enough OS threads (which we don't have control over). + if err := m.FailIfClosed(); err != nil { + panic(err) + } + c.execCtx.exitCode = wazevoapi.ExitCodeOK + afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, + uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) + case wazevoapi.ExitCodeRefFunc: + mod := c.callerModuleInstance() + s := goCallStackView(c.execCtx.stackPointerBeforeGoCall) + funcIndex := wasm.Index(s[0]) + ref := mod.Engine.FunctionInstanceReference(funcIndex) + s[0] = uint64(ref) + c.execCtx.exitCode = wazevoapi.ExitCodeOK + afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, + uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) + case wazevoapi.ExitCodeMemoryWait32: + mod := c.callerModuleInstance() + mem := mod.MemoryInstance + if !mem.Shared { + panic(wasmruntime.ErrRuntimeExpectedSharedMemory) + } + + s := goCallStackView(c.execCtx.stackPointerBeforeGoCall) + timeout, exp, addr := int64(s[0]), uint32(s[1]), uintptr(s[2]) + base := uintptr(unsafe.Pointer(&mem.Buffer[0])) + + offset := uint32(addr - base) + res := mem.Wait32(offset, exp, timeout, func(mem *wasm.MemoryInstance, offset uint32) uint32 { + addr := unsafe.Add(unsafe.Pointer(&mem.Buffer[0]), offset) + return atomic.LoadUint32((*uint32)(addr)) + }) + s[0] = res + c.execCtx.exitCode = wazevoapi.ExitCodeOK + afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, + uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) + case wazevoapi.ExitCodeMemoryWait64: + mod := c.callerModuleInstance() + mem := mod.MemoryInstance + if !mem.Shared { + panic(wasmruntime.ErrRuntimeExpectedSharedMemory) + } + + s := goCallStackView(c.execCtx.stackPointerBeforeGoCall) + timeout, exp, addr := int64(s[0]), uint64(s[1]), uintptr(s[2]) + base := uintptr(unsafe.Pointer(&mem.Buffer[0])) + + offset := uint32(addr - base) + res := mem.Wait64(offset, exp, timeout, func(mem *wasm.MemoryInstance, offset uint32) uint64 { + addr := unsafe.Add(unsafe.Pointer(&mem.Buffer[0]), offset) + return atomic.LoadUint64((*uint64)(addr)) + }) + s[0] = uint64(res) + c.execCtx.exitCode = wazevoapi.ExitCodeOK + afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, + uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) + case wazevoapi.ExitCodeMemoryNotify: + mod := c.callerModuleInstance() + mem := mod.MemoryInstance + + s := goCallStackView(c.execCtx.stackPointerBeforeGoCall) + count, addr := uint32(s[0]), s[1] + offset := uint32(uintptr(addr) - uintptr(unsafe.Pointer(&mem.Buffer[0]))) + res := mem.Notify(offset, count) + s[0] = uint64(res) + c.execCtx.exitCode = wazevoapi.ExitCodeOK + afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, + uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall) + case wazevoapi.ExitCodeUnreachable: + panic(wasmruntime.ErrRuntimeUnreachable) + case wazevoapi.ExitCodeMemoryOutOfBounds: + panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess) + case wazevoapi.ExitCodeTableOutOfBounds: + panic(wasmruntime.ErrRuntimeInvalidTableAccess) + case wazevoapi.ExitCodeIndirectCallNullPointer: + panic(wasmruntime.ErrRuntimeInvalidTableAccess) + case wazevoapi.ExitCodeIndirectCallTypeMismatch: + panic(wasmruntime.ErrRuntimeIndirectCallTypeMismatch) + case wazevoapi.ExitCodeIntegerOverflow: + panic(wasmruntime.ErrRuntimeIntegerOverflow) + case wazevoapi.ExitCodeIntegerDivisionByZero: + panic(wasmruntime.ErrRuntimeIntegerDivideByZero) + case wazevoapi.ExitCodeInvalidConversionToInteger: + panic(wasmruntime.ErrRuntimeInvalidConversionToInteger) + case wazevoapi.ExitCodeUnalignedAtomic: + panic(wasmruntime.ErrRuntimeUnalignedAtomic) + default: + panic("BUG") + } + } +} + +func (c *callEngine) callerModuleInstance() *wasm.ModuleInstance { + return moduleInstanceFromOpaquePtr(c.execCtx.callerModuleContextPtr) +} + +const callStackCeiling = uintptr(50000000) // in uint64 (8 bytes) == 400000000 bytes in total == 400mb. + +func (c *callEngine) growStackWithGuarded() (newSP uintptr, newFP uintptr, err error) { + if wazevoapi.StackGuardCheckEnabled { + wazevoapi.CheckStackGuardPage(c.stack) + } + newSP, newFP, err = c.growStack() + if err != nil { + return + } + if wazevoapi.StackGuardCheckEnabled { + c.execCtx.stackBottomPtr = &c.stack[wazevoapi.StackGuardCheckGuardPageSize] + } + return +} + +// growStack grows the stack, and returns the new stack pointer. +func (c *callEngine) growStack() (newSP, newFP uintptr, err error) { + currentLen := uintptr(len(c.stack)) + if callStackCeiling < currentLen { + err = wasmruntime.ErrRuntimeStackOverflow + return + } + + newLen := 2*currentLen + c.execCtx.stackGrowRequiredSize + 16 // Stack might be aligned to 16 bytes, so add 16 bytes just in case. + newSP, newFP, c.stackTop, c.stack = c.cloneStack(newLen) + c.execCtx.stackBottomPtr = &c.stack[0] + return +} + +func (c *callEngine) cloneStack(l uintptr) (newSP, newFP, newTop uintptr, newStack []byte) { + newStack = make([]byte, l) + + relSp := c.stackTop - uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)) + relFp := c.stackTop - c.execCtx.framePointerBeforeGoCall + + // Copy the existing contents in the previous Go-allocated stack into the new one. + var prevStackAligned, newStackAligned []byte + { + //nolint:staticcheck + sh := (*reflect.SliceHeader)(unsafe.Pointer(&prevStackAligned)) + sh.Data = c.stackTop - relSp + sh.Len = int(relSp) + sh.Cap = int(relSp) + } + newTop = alignedStackTop(newStack) + { + newSP = newTop - relSp + newFP = newTop - relFp + //nolint:staticcheck + sh := (*reflect.SliceHeader)(unsafe.Pointer(&newStackAligned)) + sh.Data = newSP + sh.Len = int(relSp) + sh.Cap = int(relSp) + } + copy(newStackAligned, prevStackAligned) + return +} + +func (c *callEngine) stackIterator(onHostCall bool) experimental.StackIterator { + c.stackIteratorImpl.reset(c, onHostCall) + return &c.stackIteratorImpl +} + +// stackIterator implements experimental.StackIterator. +type stackIterator struct { + retAddrs []uintptr + retAddrCursor int + eng *engine + pc uint64 + + currentDef *wasm.FunctionDefinition +} + +func (si *stackIterator) reset(c *callEngine, onHostCall bool) { + if onHostCall { + si.retAddrs = append(si.retAddrs[:0], uintptr(unsafe.Pointer(c.execCtx.goCallReturnAddress))) + } else { + si.retAddrs = si.retAddrs[:0] + } + si.retAddrs = unwindStack(uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall, c.stackTop, si.retAddrs) + si.retAddrs = si.retAddrs[:len(si.retAddrs)-1] // the last return addr is the trampoline, so we skip it. + si.retAddrCursor = 0 + si.eng = c.parent.parent.parent +} + +// Next implements the same method as documented on experimental.StackIterator. +func (si *stackIterator) Next() bool { + if si.retAddrCursor >= len(si.retAddrs) { + return false + } + + addr := si.retAddrs[si.retAddrCursor] + cm := si.eng.compiledModuleOfAddr(addr) + if cm != nil { + index := cm.functionIndexOf(addr) + def := cm.module.FunctionDefinition(cm.module.ImportFunctionCount + index) + si.currentDef = def + si.retAddrCursor++ + si.pc = uint64(addr) + return true + } + return false +} + +// ProgramCounter implements the same method as documented on experimental.StackIterator. +func (si *stackIterator) ProgramCounter() experimental.ProgramCounter { + return experimental.ProgramCounter(si.pc) +} + +// Function implements the same method as documented on experimental.StackIterator. +func (si *stackIterator) Function() experimental.InternalFunction { + return si +} + +// Definition implements the same method as documented on experimental.InternalFunction. +func (si *stackIterator) Definition() api.FunctionDefinition { + return si.currentDef +} + +// SourceOffsetForPC implements the same method as documented on experimental.InternalFunction. +func (si *stackIterator) SourceOffsetForPC(pc experimental.ProgramCounter) uint64 { + upc := uintptr(pc) + cm := si.eng.compiledModuleOfAddr(upc) + return cm.getSourceOffset(upc) +} + +// snapshot implements experimental.Snapshot +type snapshot struct { + sp, fp, top uintptr + returnAddress *byte + stack []byte + savedRegisters [64][2]uint64 + ret []uint64 + c *callEngine +} + +// Snapshot implements the same method as documented on experimental.Snapshotter. +func (c *callEngine) Snapshot() experimental.Snapshot { + returnAddress := c.execCtx.goCallReturnAddress + oldTop, oldSp := c.stackTop, uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)) + newSP, newFP, newTop, newStack := c.cloneStack(uintptr(len(c.stack)) + 16) + adjustClonedStack(oldSp, oldTop, newSP, newFP, newTop) + return &snapshot{ + sp: newSP, + fp: newFP, + top: newTop, + savedRegisters: c.execCtx.savedRegisters, + returnAddress: returnAddress, + stack: newStack, + c: c, + } +} + +// Restore implements the same method as documented on experimental.Snapshot. +func (s *snapshot) Restore(ret []uint64) { + s.ret = ret + panic(s) +} + +func (s *snapshot) doRestore() { + spp := *(**uint64)(unsafe.Pointer(&s.sp)) + view := goCallStackView(spp) + copy(view, s.ret) + + c := s.c + c.stack = s.stack + c.stackTop = s.top + ec := &c.execCtx + ec.stackBottomPtr = &c.stack[0] + ec.stackPointerBeforeGoCall = spp + ec.framePointerBeforeGoCall = s.fp + ec.goCallReturnAddress = s.returnAddress + ec.savedRegisters = s.savedRegisters +} + +// Error implements the same method on error. +func (s *snapshot) Error() string { + return "unhandled snapshot restore, this generally indicates restore was called from a different " + + "exported function invocation than snapshot" +} + +func snapshotRecoverFn(c *callEngine) { + if r := recover(); r != nil { + if s, ok := r.(*snapshot); ok && s.c == c { + s.doRestore() + } else { + panic(r) + } + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/engine.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/engine.go new file mode 100644 index 00000000000..f09dd1791b8 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/engine.go @@ -0,0 +1,944 @@ +package wazevo + +import ( + "context" + "encoding/hex" + "errors" + "fmt" + "runtime" + "slices" + "sort" + "sync" + "sync/atomic" + "unsafe" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/frontend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" + "github.com/tetratelabs/wazero/internal/filecache" + "github.com/tetratelabs/wazero/internal/platform" + "github.com/tetratelabs/wazero/internal/version" + "github.com/tetratelabs/wazero/internal/wasm" +) + +type ( + compiledModuleWithCount struct { + *compiledModule + refCount int + } + // engine implements wasm.Engine. + engine struct { + wazeroVersion string + fileCache filecache.Cache + compiledModules map[wasm.ModuleID]*compiledModuleWithCount + // sortedCompiledModules is a list of compiled modules sorted by the initial address of the executable. + sortedCompiledModules []*compiledModule + mux sync.RWMutex + // sharedFunctions is compiled functions shared by all modules. + sharedFunctions *sharedFunctions + // setFinalizer defaults to runtime.SetFinalizer, but overridable for tests. + setFinalizer func(obj interface{}, finalizer interface{}) + + // The followings are reused for compiling shared functions. + machine backend.Machine + be backend.Compiler + } + + sharedFunctions struct { + // The compiled trampolines executable. + executable []byte + // memoryGrowAddress is the address of memory.grow builtin function. + memoryGrowAddress *byte + // checkModuleExitCodeAddress is the address of checking module instance exit code. + // This is used when ensureTermination is true. + checkModuleExitCodeAddress *byte + // stackGrowAddress is the address of growing stack builtin function. + stackGrowAddress *byte + // tableGrowAddress is the address of table.grow builtin function. + tableGrowAddress *byte + // refFuncAddress is the address of ref.func builtin function. + refFuncAddress *byte + // memoryWait32Address is the address of memory.wait32 builtin function + memoryWait32Address *byte + // memoryWait64Address is the address of memory.wait64 builtin function + memoryWait64Address *byte + // memoryNotifyAddress is the address of memory.notify builtin function + memoryNotifyAddress *byte + listenerTrampolines listenerTrampolines + } + + listenerTrampolines = map[*wasm.FunctionType]struct { + executable []byte + before *byte + after *byte + } + + // compiledModule is a compiled variant of a wasm.Module and ready to be used for instantiation. + compiledModule struct { + *executables + // functionOffsets maps a local function index to the offset in the executable. + functionOffsets []int + parent *engine + module *wasm.Module + ensureTermination bool + listeners []experimental.FunctionListener + listenerBeforeTrampolines []*byte + listenerAfterTrampolines []*byte + + // The followings are only available for non host modules. + + offsets wazevoapi.ModuleContextOffsetData + sharedFunctions *sharedFunctions + sourceMap sourceMap + } + + executables struct { + executable []byte + entryPreambles []byte + entryPreamblesPtrs []*byte + } +) + +// sourceMap is a mapping from the offset of the executable to the offset of the original wasm binary. +type sourceMap struct { + // executableOffsets is a sorted list of offsets of the executable. This is index-correlated with wasmBinaryOffsets, + // in other words executableOffsets[i] is the offset of the executable which corresponds to the offset of a Wasm + // binary pointed by wasmBinaryOffsets[i]. + executableOffsets []uintptr + // wasmBinaryOffsets is the counterpart of executableOffsets. + wasmBinaryOffsets []uint64 +} + +var _ wasm.Engine = (*engine)(nil) + +// NewEngine returns the implementation of wasm.Engine. +func NewEngine(ctx context.Context, _ api.CoreFeatures, fc filecache.Cache) wasm.Engine { + machine := newMachine() + be := backend.NewCompiler(ctx, machine, ssa.NewBuilder()) + e := &engine{ + compiledModules: make(map[wasm.ModuleID]*compiledModuleWithCount), + setFinalizer: runtime.SetFinalizer, + machine: machine, + be: be, + fileCache: fc, + wazeroVersion: version.GetWazeroVersion(), + } + e.compileSharedFunctions() + return e +} + +// CompileModule implements wasm.Engine. +func (e *engine) CompileModule(ctx context.Context, module *wasm.Module, listeners []experimental.FunctionListener, ensureTermination bool) (err error) { + if wazevoapi.PerfMapEnabled { + wazevoapi.PerfMap.Lock() + defer wazevoapi.PerfMap.Unlock() + } + + if _, ok, err := e.getCompiledModule(module, listeners, ensureTermination); ok { // cache hit! + return nil + } else if err != nil { + return err + } + + if wazevoapi.DeterministicCompilationVerifierEnabled { + ctx = wazevoapi.NewDeterministicCompilationVerifierContext(ctx, len(module.CodeSection)) + } + cm, err := e.compileModule(ctx, module, listeners, ensureTermination) + if err != nil { + return err + } + if cm, err = e.addCompiledModule(module, cm); err != nil { + return err + } + + if wazevoapi.DeterministicCompilationVerifierEnabled { + for i := 0; i < wazevoapi.DeterministicCompilationVerifyingIter; i++ { + _, err := e.compileModule(ctx, module, listeners, ensureTermination) + if err != nil { + return err + } + } + } + + if len(listeners) > 0 { + cm.listeners = listeners + cm.listenerBeforeTrampolines = make([]*byte, len(module.TypeSection)) + cm.listenerAfterTrampolines = make([]*byte, len(module.TypeSection)) + for i := range module.TypeSection { + typ := &module.TypeSection[i] + before, after := e.getListenerTrampolineForType(typ) + cm.listenerBeforeTrampolines[i] = before + cm.listenerAfterTrampolines[i] = after + } + } + return nil +} + +func (exec *executables) compileEntryPreambles(m *wasm.Module, machine backend.Machine, be backend.Compiler) { + if len(m.TypeSection) == 0 { + return + } + + var preambles []byte + sizes := make([]int, len(m.TypeSection)) + + for i := range sizes { + typ := &m.TypeSection[i] + sig := frontend.SignatureForWasmFunctionType(typ) + be.Init() + buf := machine.CompileEntryPreamble(&sig) + preambles = append(preambles, buf...) + align := 15 & -len(preambles) // Align 16-bytes boundary. + preambles = append(preambles, make([]byte, align)...) + sizes[i] = len(buf) + align + } + + exec.entryPreambles = mmapExecutable(preambles) + exec.entryPreamblesPtrs = make([]*byte, len(sizes)) + + offset := 0 + for i, size := range sizes { + ptr := &exec.entryPreambles[offset] + exec.entryPreamblesPtrs[i] = ptr + offset += size + + if wazevoapi.PerfMapEnabled { + typ := &m.TypeSection[i] + wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(ptr)), + uint64(size), fmt.Sprintf("entry_preamble::type=%s", typ.String())) + } + } +} + +func (e *engine) compileModule(ctx context.Context, module *wasm.Module, listeners []experimental.FunctionListener, ensureTermination bool) (*compiledModule, error) { + if module.IsHostModule { + return e.compileHostModule(ctx, module, listeners) + } + + withListener := len(listeners) > 0 + cm := &compiledModule{ + offsets: wazevoapi.NewModuleContextOffsetData(module, withListener), parent: e, module: module, + ensureTermination: ensureTermination, + executables: &executables{}, + } + + importedFns, localFns := int(module.ImportFunctionCount), len(module.FunctionSection) + if localFns == 0 { + return cm, nil + } + + machine := newMachine() + relocator, err := newEngineRelocator(machine, importedFns, localFns) + if err != nil { + return nil, err + } + + needSourceInfo := module.DWARFLines != nil + + ssaBuilder := ssa.NewBuilder() + be := backend.NewCompiler(ctx, machine, ssaBuilder) + cm.executables.compileEntryPreambles(module, machine, be) + cm.functionOffsets = make([]int, localFns) + + var indexes []int + if wazevoapi.DeterministicCompilationVerifierEnabled { + // The compilation must be deterministic regardless of the order of functions being compiled. + indexes = wazevoapi.DeterministicCompilationVerifierRandomizeIndexes(ctx) + } + + if workers := experimental.GetCompilationWorkers(ctx); workers <= 1 { + // Compile with a single goroutine. + fe := frontend.NewFrontendCompiler(module, ssaBuilder, &cm.offsets, ensureTermination, withListener, needSourceInfo) + + for i := range module.CodeSection { + if wazevoapi.DeterministicCompilationVerifierEnabled { + i = indexes[i] + } + + fidx := wasm.Index(i + importedFns) + fctx := functionContext(ctx, module, i, fidx) + + needListener := len(listeners) > i && listeners[i] != nil + body, relsPerFunc, err := e.compileLocalWasmFunction(fctx, module, wasm.Index(i), fe, ssaBuilder, be, needListener) + if err != nil { + return nil, fmt.Errorf("compile function %d/%d: %v", i, len(module.CodeSection)-1, err) + } + + relocator.appendFunction(fctx, module, cm, i, fidx, body, relsPerFunc, be.SourceOffsetInfo()) + } + } else { + // Compile with N worker goroutines. + // Collect compiled functions across workers in a slice, + // to be added to the relocator in-order and resolved serially at the end. + // This uses more memory and CPU (across cores), but can be significantly faster. + type compiledFunc struct { + fctx context.Context + fnum int + fidx wasm.Index + body []byte + relsPerFunc []backend.RelocationInfo + offsPerFunc []backend.SourceOffsetInfo + } + + compiledFuncs := make([]compiledFunc, len(module.CodeSection)) + ctx, cancel := context.WithCancelCause(ctx) + defer cancel(nil) + + var count atomic.Uint32 + var wg sync.WaitGroup + wg.Add(workers) + + for range workers { + go func() { + defer wg.Done() + + // Creates new compiler instances which are reused for each function. + machine := newMachine() + ssaBuilder := ssa.NewBuilder() + be := backend.NewCompiler(ctx, machine, ssaBuilder) + fe := frontend.NewFrontendCompiler(module, ssaBuilder, &cm.offsets, ensureTermination, withListener, needSourceInfo) + + for { + if err := ctx.Err(); err != nil { + // Compilation canceled! + return + } + + i := int(count.Add(1)) - 1 + if i >= len(module.CodeSection) { + return + } + + if wazevoapi.DeterministicCompilationVerifierEnabled { + i = indexes[i] + } + + fidx := wasm.Index(i + importedFns) + fctx := functionContext(ctx, module, i, fidx) + + needListener := len(listeners) > i && listeners[i] != nil + body, relsPerFunc, err := e.compileLocalWasmFunction(fctx, module, wasm.Index(i), fe, ssaBuilder, be, needListener) + if err != nil { + cancel(fmt.Errorf("compile function %d/%d: %v", i, len(module.CodeSection)-1, err)) + return + } + + compiledFuncs[i] = compiledFunc{ + fctx, i, fidx, body, + // These slices are internal to the backend compiler and since we are going to buffer them instead + // of process them immediately we need to copy the memory. + slices.Clone(relsPerFunc), + slices.Clone(be.SourceOffsetInfo()), + } + } + }() + } + + wg.Wait() + if err := context.Cause(ctx); err != nil { + return nil, err + } + + for i := range compiledFuncs { + fn := &compiledFuncs[i] + relocator.appendFunction(fn.fctx, module, cm, fn.fnum, fn.fidx, fn.body, fn.relsPerFunc, fn.offsPerFunc) + } + } + + // Allocate executable memory and then copy the generated machine code. + executable, err := platform.MmapCodeSegment(relocator.totalSize) + if err != nil { + panic(err) + } + cm.executable = executable + + for i, b := range relocator.bodies { + offset := cm.functionOffsets[i] + copy(executable[offset:], b) + } + + if wazevoapi.PerfMapEnabled { + wazevoapi.PerfMap.Flush(uintptr(unsafe.Pointer(&executable[0])), cm.functionOffsets) + } + + if needSourceInfo { + for i := range cm.sourceMap.executableOffsets { + cm.sourceMap.executableOffsets[i] += uintptr(unsafe.Pointer(&cm.executable[0])) + } + } + + relocator.resolveRelocations(machine, executable, importedFns) + + if err = platform.MprotectCodeSegment(executable); err != nil { + return nil, err + } + cm.sharedFunctions = e.sharedFunctions + e.setFinalizer(cm.executables, executablesFinalizer) + return cm, nil +} + +func functionContext(ctx context.Context, module *wasm.Module, fnum int, fidx wasm.Index) context.Context { + if wazevoapi.NeedFunctionNameInContext { + def := module.FunctionDefinition(fidx) + name := def.DebugName() + if len(def.ExportNames()) > 0 { + name = def.ExportNames()[0] + } + ctx = wazevoapi.SetCurrentFunctionName(ctx, fnum, fmt.Sprintf("[%d/%d]%s", fnum, len(module.CodeSection)-1, name)) + } + return ctx +} + +type engineRelocator struct { + bodies [][]byte + refToBinaryOffset []int + rels []backend.RelocationInfo + totalSize int // Total binary size of the executable. + trampolineInterval int + callTrampolineIslandSize int + callTrampolineIslandOffsets []int // Holds the offsets of trampoline islands. +} + +func newEngineRelocator( + machine backend.Machine, + importedFns, localFns int, +) (r engineRelocator, err error) { + // Trampoline relocation related variables. + r.trampolineInterval, r.callTrampolineIslandSize, err = machine.CallTrampolineIslandInfo(localFns) + r.refToBinaryOffset = make([]int, importedFns+localFns) + r.bodies = make([][]byte, 0, localFns) + return +} + +func (r *engineRelocator) resolveRelocations(machine backend.Machine, executable []byte, importedFns int) { + // Resolve relocations for local function calls. + if len(r.rels) > 0 { + machine.ResolveRelocations(r.refToBinaryOffset, importedFns, executable, r.rels, r.callTrampolineIslandOffsets) + } +} + +func (r *engineRelocator) appendFunction( + ctx context.Context, + module *wasm.Module, + cm *compiledModule, + fnum int, fidx wasm.Index, + body []byte, + relsPerFunc []backend.RelocationInfo, + offsPerFunc []backend.SourceOffsetInfo, +) { + // Align 16-bytes boundary. + r.totalSize = (r.totalSize + 15) &^ 15 + cm.functionOffsets[fnum] = r.totalSize + + needSourceInfo := module.DWARFLines != nil + if needSourceInfo { + // At the beginning of the function, we add the offset of the function body so that + // we can resolve the source location of the call site of before listener call. + cm.sourceMap.executableOffsets = append(cm.sourceMap.executableOffsets, uintptr(r.totalSize)) + cm.sourceMap.wasmBinaryOffsets = append(cm.sourceMap.wasmBinaryOffsets, module.CodeSection[fnum].BodyOffsetInCodeSection) + + for _, info := range offsPerFunc { + cm.sourceMap.executableOffsets = append(cm.sourceMap.executableOffsets, uintptr(r.totalSize)+uintptr(info.ExecutableOffset)) + cm.sourceMap.wasmBinaryOffsets = append(cm.sourceMap.wasmBinaryOffsets, uint64(info.SourceOffset)) + } + } + + fref := frontend.FunctionIndexToFuncRef(fidx) + r.refToBinaryOffset[fref] = r.totalSize + + // At this point, relocation offsets are relative to the start of the function body, + // so we adjust it to the start of the executable. + r.rels = slices.Grow(r.rels, len(relsPerFunc)) + for _, rel := range relsPerFunc { + rel.Offset += int64(r.totalSize) + r.rels = append(r.rels, rel) + } + + r.totalSize += len(body) + r.bodies = append(r.bodies, body) + if wazevoapi.PrintMachineCodeHexPerFunction { + fmt.Printf("[[[machine code for %s]]]\n%s\n\n", wazevoapi.GetCurrentFunctionName(ctx), hex.EncodeToString(body)) + } + + if r.callTrampolineIslandSize > 0 { + // If the total size exceeds the trampoline interval, we need to add a trampoline island. + if r.totalSize/r.trampolineInterval > len(r.callTrampolineIslandOffsets) { + r.callTrampolineIslandOffsets = append(r.callTrampolineIslandOffsets, r.totalSize) + r.totalSize += r.callTrampolineIslandSize + } + } +} + +func (e *engine) compileLocalWasmFunction( + ctx context.Context, + module *wasm.Module, + localFunctionIndex wasm.Index, + fe *frontend.Compiler, + ssaBuilder ssa.Builder, + be backend.Compiler, + needListener bool, +) (body []byte, rels []backend.RelocationInfo, err error) { + typIndex := module.FunctionSection[localFunctionIndex] + typ := &module.TypeSection[typIndex] + codeSeg := &module.CodeSection[localFunctionIndex] + + // Initializes both frontend and backend compilers. + fe.Init(localFunctionIndex, typIndex, typ, codeSeg.LocalTypes, codeSeg.Body, needListener, codeSeg.BodyOffsetInCodeSection) + be.Init() + + // Lower Wasm to SSA. + fe.LowerToSSA() + if wazevoapi.PrintSSA && wazevoapi.PrintEnabledIndex(ctx) { + fmt.Printf("[[[SSA for %s]]]%s\n", wazevoapi.GetCurrentFunctionName(ctx), ssaBuilder.Format()) + } + + if wazevoapi.DeterministicCompilationVerifierEnabled { + wazevoapi.VerifyOrSetDeterministicCompilationContextValue(ctx, "SSA", ssaBuilder.Format()) + } + + // Run SSA-level optimization passes. + ssaBuilder.RunPasses() + + if wazevoapi.PrintOptimizedSSA && wazevoapi.PrintEnabledIndex(ctx) { + fmt.Printf("[[[Optimized SSA for %s]]]%s\n", wazevoapi.GetCurrentFunctionName(ctx), ssaBuilder.Format()) + } + + if wazevoapi.DeterministicCompilationVerifierEnabled { + wazevoapi.VerifyOrSetDeterministicCompilationContextValue(ctx, "Optimized SSA", ssaBuilder.Format()) + } + + // Now our ssaBuilder contains the necessary information to further lower them to + // machine code. + original, rels, err := be.Compile(ctx) + if err != nil { + return nil, nil, fmt.Errorf("ssa->machine code: %v", err) + } + + // TODO: optimize as zero copy. + return slices.Clone(original), rels, nil +} + +func (e *engine) compileHostModule(ctx context.Context, module *wasm.Module, listeners []experimental.FunctionListener) (*compiledModule, error) { + machine := newMachine() + be := backend.NewCompiler(ctx, machine, ssa.NewBuilder()) + + num := len(module.CodeSection) + cm := &compiledModule{module: module, listeners: listeners, executables: &executables{}} + cm.functionOffsets = make([]int, num) + totalSize := 0 // Total binary size of the executable. + bodies := make([][]byte, num) + var sig ssa.Signature + for i := range module.CodeSection { + totalSize = (totalSize + 15) &^ 15 + cm.functionOffsets[i] = totalSize + + typIndex := module.FunctionSection[i] + typ := &module.TypeSection[typIndex] + + // We can relax until the index fits together in ExitCode as we do in wazevoapi.ExitCodeCallGoModuleFunctionWithIndex. + // However, 1 << 16 should be large enough for a real use case. + const hostFunctionNumMaximum = 1 << 16 + if i >= hostFunctionNumMaximum { + return nil, fmt.Errorf("too many host functions (maximum %d)", hostFunctionNumMaximum) + } + + sig.ID = ssa.SignatureID(typIndex) // This is important since we reuse the `machine` which caches the ABI based on the SignatureID. + sig.Params = append(sig.Params[:0], + ssa.TypeI64, // First argument must be exec context. + ssa.TypeI64, // The second argument is the moduleContextOpaque of this host module. + ) + for _, t := range typ.Params { + sig.Params = append(sig.Params, frontend.WasmTypeToSSAType(t)) + } + + sig.Results = sig.Results[:0] + for _, t := range typ.Results { + sig.Results = append(sig.Results, frontend.WasmTypeToSSAType(t)) + } + + c := &module.CodeSection[i] + if c.GoFunc == nil { + panic("BUG: GoFunc must be set for host module") + } + + withListener := len(listeners) > 0 && listeners[i] != nil + var exitCode wazevoapi.ExitCode + fn := c.GoFunc + switch fn.(type) { + case api.GoModuleFunction: + exitCode = wazevoapi.ExitCodeCallGoModuleFunctionWithIndex(i, withListener) + case api.GoFunction: + exitCode = wazevoapi.ExitCodeCallGoFunctionWithIndex(i, withListener) + } + + be.Init() + machine.CompileGoFunctionTrampoline(exitCode, &sig, true) + if err := be.Finalize(ctx); err != nil { + return nil, err + } + body := be.Buf() + + if wazevoapi.PerfMapEnabled { + name := module.FunctionDefinition(wasm.Index(i)).DebugName() + wazevoapi.PerfMap.AddModuleEntry(i, + int64(totalSize), + uint64(len(body)), + fmt.Sprintf("trampoline:%s", name)) + } + + // TODO: optimize as zero copy. + bodies[i] = slices.Clone(body) + totalSize += len(body) + } + + if totalSize == 0 { + // Empty module. + return cm, nil + } + + // Allocate executable memory and then copy the generated machine code. + executable, err := platform.MmapCodeSegment(totalSize) + if err != nil { + panic(err) + } + cm.executable = executable + + for i, b := range bodies { + offset := cm.functionOffsets[i] + copy(executable[offset:], b) + } + + if wazevoapi.PerfMapEnabled { + wazevoapi.PerfMap.Flush(uintptr(unsafe.Pointer(&executable[0])), cm.functionOffsets) + } + + if err = platform.MprotectCodeSegment(executable); err != nil { + return nil, err + } + e.setFinalizer(cm.executables, executablesFinalizer) + return cm, nil +} + +// Close implements wasm.Engine. +func (e *engine) Close() (err error) { + e.mux.Lock() + defer e.mux.Unlock() + e.sortedCompiledModules = nil + e.compiledModules = nil + e.sharedFunctions = nil + return nil +} + +// CompiledModuleCount implements wasm.Engine. +func (e *engine) CompiledModuleCount() uint32 { + e.mux.RLock() + defer e.mux.RUnlock() + return uint32(len(e.compiledModules)) +} + +// DeleteCompiledModule implements wasm.Engine. +func (e *engine) DeleteCompiledModule(m *wasm.Module) { + e.mux.Lock() + defer e.mux.Unlock() + cm, ok := e.compiledModules[m.ID] + if !ok { + return + } + cm.refCount-- + if cm.refCount > 0 { + return + } + if len(cm.executable) > 0 { + e.deleteCompiledModuleFromSortedList(cm.compiledModule) + } + delete(e.compiledModules, m.ID) +} + +func (e *engine) addCompiledModuleToSortedList(cm *compiledModule) { + ptr := uintptr(unsafe.Pointer(&cm.executable[0])) + + index := sort.Search(len(e.sortedCompiledModules), func(i int) bool { + return uintptr(unsafe.Pointer(&e.sortedCompiledModules[i].executable[0])) >= ptr + }) + e.sortedCompiledModules = append(e.sortedCompiledModules, nil) + copy(e.sortedCompiledModules[index+1:], e.sortedCompiledModules[index:]) + e.sortedCompiledModules[index] = cm +} + +func (e *engine) deleteCompiledModuleFromSortedList(cm *compiledModule) { + ptr := uintptr(unsafe.Pointer(&cm.executable[0])) + + index := sort.Search(len(e.sortedCompiledModules), func(i int) bool { + return uintptr(unsafe.Pointer(&e.sortedCompiledModules[i].executable[0])) >= ptr + }) + if index >= len(e.sortedCompiledModules) { + return + } + copy(e.sortedCompiledModules[index:], e.sortedCompiledModules[index+1:]) + e.sortedCompiledModules = e.sortedCompiledModules[:len(e.sortedCompiledModules)-1] +} + +func (e *engine) compiledModuleOfAddr(addr uintptr) *compiledModule { + e.mux.RLock() + defer e.mux.RUnlock() + + index := sort.Search(len(e.sortedCompiledModules), func(i int) bool { + return uintptr(unsafe.Pointer(&e.sortedCompiledModules[i].executable[0])) > addr + }) + index -= 1 + if index < 0 { + return nil + } + candidate := e.sortedCompiledModules[index] + if checkAddrInBytes(addr, candidate.executable) { + // If a module is already deleted, the found module may have been wrong. + return candidate + } + return nil +} + +func checkAddrInBytes(addr uintptr, b []byte) bool { + return uintptr(unsafe.Pointer(&b[0])) <= addr && addr <= uintptr(unsafe.Pointer(&b[len(b)-1])) +} + +// NewModuleEngine implements wasm.Engine. +func (e *engine) NewModuleEngine(m *wasm.Module, mi *wasm.ModuleInstance) (wasm.ModuleEngine, error) { + me := &moduleEngine{} + + // Note: imported functions are resolved in moduleEngine.ResolveImportedFunction. + me.importedFunctions = make([]importedFunction, m.ImportFunctionCount) + + compiled, ok := e.getCompiledModuleFromMemory(m, false) + if !ok { + return nil, errors.New("source module must be compiled before instantiation") + } + me.parent = compiled + me.module = mi + me.listeners = compiled.listeners + + if m.IsHostModule { + me.opaque = buildHostModuleOpaque(m, compiled.listeners) + me.opaquePtr = &me.opaque[0] + } else { + if size := compiled.offsets.TotalSize; size != 0 { + opaque := newAlignedOpaque(size) + me.opaque = opaque + me.opaquePtr = &opaque[0] + } + } + return me, nil +} + +func (e *engine) compileSharedFunctions() { + var sizes [8]int + var trampolines []byte + + addTrampoline := func(i int, buf []byte) { + trampolines = append(trampolines, buf...) + align := 15 & -len(trampolines) // Align 16-bytes boundary. + trampolines = append(trampolines, make([]byte, align)...) + sizes[i] = len(buf) + align + } + + e.be.Init() + addTrampoline(0, + e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeGrowMemory, &ssa.Signature{ + Params: []ssa.Type{ssa.TypeI64 /* exec context */, ssa.TypeI32}, + Results: []ssa.Type{ssa.TypeI32}, + }, false)) + + e.be.Init() + addTrampoline(1, + e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeTableGrow, &ssa.Signature{ + Params: []ssa.Type{ssa.TypeI64 /* exec context */, ssa.TypeI32 /* table index */, ssa.TypeI32 /* num */, ssa.TypeI64 /* ref */}, + Results: []ssa.Type{ssa.TypeI32}, + }, false)) + + e.be.Init() + addTrampoline(2, + e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeCheckModuleExitCode, &ssa.Signature{ + Params: []ssa.Type{ssa.TypeI32 /* exec context */}, + Results: []ssa.Type{ssa.TypeI32}, + }, false)) + + e.be.Init() + addTrampoline(3, + e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeRefFunc, &ssa.Signature{ + Params: []ssa.Type{ssa.TypeI64 /* exec context */, ssa.TypeI32 /* function index */}, + Results: []ssa.Type{ssa.TypeI64}, // returns the function reference. + }, false)) + + e.be.Init() + addTrampoline(4, e.machine.CompileStackGrowCallSequence()) + + e.be.Init() + addTrampoline(5, + e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeMemoryWait32, &ssa.Signature{ + // exec context, timeout, expected, addr + Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeI32, ssa.TypeI64}, + // Returns the status. + Results: []ssa.Type{ssa.TypeI32}, + }, false)) + + e.be.Init() + addTrampoline(6, + e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeMemoryWait64, &ssa.Signature{ + // exec context, timeout, expected, addr + Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeI64, ssa.TypeI64}, + // Returns the status. + Results: []ssa.Type{ssa.TypeI32}, + }, false)) + + e.be.Init() + addTrampoline(7, + e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeMemoryNotify, &ssa.Signature{ + // exec context, count, addr + Params: []ssa.Type{ssa.TypeI64, ssa.TypeI32, ssa.TypeI64}, + // Returns the number notified. + Results: []ssa.Type{ssa.TypeI32}, + }, false)) + + fns := &sharedFunctions{ + executable: mmapExecutable(trampolines), + listenerTrampolines: make(listenerTrampolines), + } + e.setFinalizer(fns, sharedFunctionsFinalizer) + + offset := 0 + fns.memoryGrowAddress = &fns.executable[offset] + offset += sizes[0] + fns.tableGrowAddress = &fns.executable[offset] + offset += sizes[1] + fns.checkModuleExitCodeAddress = &fns.executable[offset] + offset += sizes[2] + fns.refFuncAddress = &fns.executable[offset] + offset += sizes[3] + fns.stackGrowAddress = &fns.executable[offset] + offset += sizes[4] + fns.memoryWait32Address = &fns.executable[offset] + offset += sizes[5] + fns.memoryWait64Address = &fns.executable[offset] + offset += sizes[6] + fns.memoryNotifyAddress = &fns.executable[offset] + + if wazevoapi.PerfMapEnabled { + wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(fns.memoryGrowAddress)), uint64(sizes[0]), "memory_grow_trampoline") + wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(fns.tableGrowAddress)), uint64(sizes[1]), "table_grow_trampoline") + wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(fns.checkModuleExitCodeAddress)), uint64(sizes[2]), "check_module_exit_code_trampoline") + wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(fns.refFuncAddress)), uint64(sizes[3]), "ref_func_trampoline") + wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(fns.stackGrowAddress)), uint64(sizes[4]), "stack_grow_trampoline") + wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(fns.memoryWait32Address)), uint64(sizes[5]), "memory_wait32_trampoline") + wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(fns.memoryWait64Address)), uint64(sizes[6]), "memory_wait64_trampoline") + wazevoapi.PerfMap.AddEntry(uintptr(unsafe.Pointer(fns.memoryNotifyAddress)), uint64(sizes[7]), "memory_notify_trampoline") + } + + e.sharedFunctions = fns +} + +func sharedFunctionsFinalizer(sf *sharedFunctions) { + if err := platform.MunmapCodeSegment(sf.executable); err != nil { + panic(err) + } + for _, f := range sf.listenerTrampolines { + if err := platform.MunmapCodeSegment(f.executable); err != nil { + panic(err) + } + } + + sf.executable = nil + sf.listenerTrampolines = nil +} + +func executablesFinalizer(exec *executables) { + if len(exec.executable) > 0 { + if err := platform.MunmapCodeSegment(exec.executable); err != nil { + panic(err) + } + } + exec.executable = nil + + if len(exec.entryPreambles) > 0 { + if err := platform.MunmapCodeSegment(exec.entryPreambles); err != nil { + panic(err) + } + } + exec.entryPreambles = nil + exec.entryPreamblesPtrs = nil +} + +func mmapExecutable(src []byte) []byte { + executable, err := platform.MmapCodeSegment(len(src)) + if err != nil { + panic(err) + } + + copy(executable, src) + + if err = platform.MprotectCodeSegment(executable); err != nil { + panic(err) + } + return executable +} + +func (cm *compiledModule) functionIndexOf(addr uintptr) wasm.Index { + addr -= uintptr(unsafe.Pointer(&cm.executable[0])) + offset := cm.functionOffsets + index := sort.Search(len(offset), func(i int) bool { + return offset[i] > int(addr) + }) + index-- + if index < 0 { + panic("BUG") + } + return wasm.Index(index) +} + +func (e *engine) getListenerTrampolineForType(functionType *wasm.FunctionType) (before, after *byte) { + e.mux.Lock() + defer e.mux.Unlock() + + trampoline, ok := e.sharedFunctions.listenerTrampolines[functionType] + if !ok { + var executable []byte + beforeSig, afterSig := frontend.SignatureForListener(functionType) + + e.be.Init() + buf := e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeCallListenerBefore, beforeSig, false) + executable = append(executable, buf...) + + align := 15 & -len(executable) // Align 16-bytes boundary. + executable = append(executable, make([]byte, align)...) + offset := len(executable) + + e.be.Init() + buf = e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeCallListenerAfter, afterSig, false) + executable = append(executable, buf...) + + trampoline.executable = mmapExecutable(executable) + trampoline.before = &trampoline.executable[0] + trampoline.after = &trampoline.executable[offset] + + e.sharedFunctions.listenerTrampolines[functionType] = trampoline + } + return trampoline.before, trampoline.after +} + +func (cm *compiledModule) getSourceOffset(pc uintptr) uint64 { + offsets := cm.sourceMap.executableOffsets + if len(offsets) == 0 { + return 0 + } + + index := sort.Search(len(offsets), func(i int) bool { + return offsets[i] >= pc + }) + + index-- + if index < 0 { + return 0 + } + return cm.sourceMap.wasmBinaryOffsets[index] +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/engine_cache.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/engine_cache.go new file mode 100644 index 00000000000..eba1ac9633b --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/engine_cache.go @@ -0,0 +1,311 @@ +package wazevo + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/binary" + "fmt" + "hash/crc32" + "io" + "unsafe" + + "github.com/tetratelabs/wazero/experimental" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" + "github.com/tetratelabs/wazero/internal/filecache" + "github.com/tetratelabs/wazero/internal/platform" + "github.com/tetratelabs/wazero/internal/u32" + "github.com/tetratelabs/wazero/internal/u64" + "github.com/tetratelabs/wazero/internal/wasm" +) + +var crc = crc32.MakeTable(crc32.Castagnoli) + +// fileCacheKey returns a key for the file cache. +// In order to avoid collisions with the existing compiler, we do not use m.ID directly, +// but instead we rehash it with magic. +func fileCacheKey(m *wasm.Module) (ret filecache.Key) { + s := sha256.New() + s.Write(m.ID[:]) + s.Write(magic) + // Write the CPU features so that we can cache the compiled module for the same CPU. + // This prevents the incompatible CPU features from being used. + cpu := platform.CpuFeatures.Raw() + // Reuse the `ret` buffer to write the first 8 bytes of the CPU features so that we can avoid the allocation. + binary.LittleEndian.PutUint64(ret[:8], cpu) + s.Write(ret[:8]) + // Finally, write the hash to the ret buffer. + s.Sum(ret[:0]) + return +} + +func (e *engine) addCompiledModule(module *wasm.Module, cm *compiledModule) (c *compiledModule, err error) { + c = e.addCompiledModuleToMemory(module, cm) + if !module.IsHostModule && e.fileCache != nil { + err = e.addCompiledModuleToCache(module, c) + } + return +} + +func (e *engine) getCompiledModule(module *wasm.Module, listeners []experimental.FunctionListener, ensureTermination bool) (cm *compiledModule, ok bool, err error) { + cm, ok = e.getCompiledModuleFromMemory(module, true) + if ok { + return + } + cm, ok, err = e.getCompiledModuleFromCache(module) + if ok { + cm.parent = e + cm.module = module + cm.sharedFunctions = e.sharedFunctions + cm.ensureTermination = ensureTermination + cm.offsets = wazevoapi.NewModuleContextOffsetData(module, len(listeners) > 0) + if len(listeners) > 0 { + cm.listeners = listeners + cm.listenerBeforeTrampolines = make([]*byte, len(module.TypeSection)) + cm.listenerAfterTrampolines = make([]*byte, len(module.TypeSection)) + for i := range module.TypeSection { + typ := &module.TypeSection[i] + before, after := e.getListenerTrampolineForType(typ) + cm.listenerBeforeTrampolines[i] = before + cm.listenerAfterTrampolines[i] = after + } + } + e.addCompiledModuleToMemory(module, cm) + ssaBuilder := ssa.NewBuilder() + machine := newMachine() + be := backend.NewCompiler(context.Background(), machine, ssaBuilder) + cm.executables.compileEntryPreambles(module, machine, be) + + // Set the finalizer. + e.setFinalizer(cm.executables, executablesFinalizer) + } + return +} + +func (e *engine) addCompiledModuleToMemory(m *wasm.Module, cm *compiledModule) *compiledModule { + e.mux.Lock() + defer e.mux.Unlock() + if c, ok := e.compiledModules[m.ID]; ok { + c.refCount++ + return c.compiledModule + } + e.compiledModules[m.ID] = &compiledModuleWithCount{compiledModule: cm, refCount: 1} + if len(cm.executable) > 0 { + e.addCompiledModuleToSortedList(cm) + } + return cm +} + +func (e *engine) getCompiledModuleFromMemory(module *wasm.Module, increaseRefCount bool) (cm *compiledModule, ok bool) { + e.mux.Lock() + defer e.mux.Unlock() + + cmWithCount, ok := e.compiledModules[module.ID] + if ok { + cm = cmWithCount.compiledModule + if increaseRefCount { + cmWithCount.refCount++ + } + } + return +} + +func (e *engine) addCompiledModuleToCache(module *wasm.Module, cm *compiledModule) (err error) { + if e.fileCache == nil || module.IsHostModule { + return + } + err = e.fileCache.Add(fileCacheKey(module), serializeCompiledModule(e.wazeroVersion, cm)) + return +} + +func (e *engine) getCompiledModuleFromCache(module *wasm.Module) (cm *compiledModule, hit bool, err error) { + if e.fileCache == nil || module.IsHostModule { + return + } + + // Check if the entries exist in the external cache. + var cached io.ReadCloser + cached, hit, err = e.fileCache.Get(fileCacheKey(module)) + if !hit || err != nil { + return + } + + // Otherwise, we hit the cache on external cache. + // We retrieve *code structures from `cached`. + var staleCache bool + // Note: cached.Close is ensured to be called in deserializeCodes. + cm, staleCache, err = deserializeCompiledModule(e.wazeroVersion, cached) + if err != nil { + hit = false + return + } else if staleCache { + return nil, false, e.fileCache.Delete(fileCacheKey(module)) + } + return +} + +var magic = []byte{'W', 'A', 'Z', 'E', 'V', 'O'} + +func serializeCompiledModule(wazeroVersion string, cm *compiledModule) io.Reader { + buf := bytes.NewBuffer(nil) + // First 6 byte: WAZEVO header. + buf.Write(magic) + // Next 1 byte: length of version: + buf.WriteByte(byte(len(wazeroVersion))) + // Version of wazero. + buf.WriteString(wazeroVersion) + // Number of *code (== locally defined functions in the module): 4 bytes. + buf.Write(u32.LeBytes(uint32(len(cm.functionOffsets)))) + for _, offset := range cm.functionOffsets { + // The offset of this function in the executable (8 bytes). + buf.Write(u64.LeBytes(uint64(offset))) + } + // The length of code segment (8 bytes). + buf.Write(u64.LeBytes(uint64(len(cm.executable)))) + // Append the native code. + buf.Write(cm.executable) + // Append checksum. + checksum := crc32.Checksum(cm.executable, crc) + buf.Write(u32.LeBytes(checksum)) + if sm := cm.sourceMap; len(sm.executableOffsets) > 0 { + buf.WriteByte(1) // indicates that source map is present. + l := len(sm.wasmBinaryOffsets) + buf.Write(u64.LeBytes(uint64(l))) + executableAddr := uintptr(unsafe.Pointer(&cm.executable[0])) + for i := 0; i < l; i++ { + buf.Write(u64.LeBytes(sm.wasmBinaryOffsets[i])) + // executableOffsets is absolute address, so we need to subtract executableAddr. + buf.Write(u64.LeBytes(uint64(sm.executableOffsets[i] - executableAddr))) + } + } else { + buf.WriteByte(0) // indicates that source map is not present. + } + return bytes.NewReader(buf.Bytes()) +} + +func deserializeCompiledModule(wazeroVersion string, reader io.ReadCloser) (cm *compiledModule, staleCache bool, err error) { + defer reader.Close() + cacheHeaderSize := len(magic) + 1 /* version size */ + len(wazeroVersion) + 4 /* number of functions */ + + // Read the header before the native code. + header := make([]byte, cacheHeaderSize) + n, err := reader.Read(header) + if err != nil { + return nil, false, fmt.Errorf("compilationcache: error reading header: %v", err) + } + + if n != cacheHeaderSize { + return nil, false, fmt.Errorf("compilationcache: invalid header length: %d", n) + } + + if !bytes.Equal(header[:len(magic)], magic) { + return nil, false, fmt.Errorf( + "compilationcache: invalid magic number: got %s but want %s", magic, header[:len(magic)]) + } + + // Check the version compatibility. + versionSize := int(header[len(magic)]) + + cachedVersionBegin, cachedVersionEnd := len(magic)+1, len(magic)+1+versionSize + if cachedVersionEnd >= len(header) { + staleCache = true + return + } else if cachedVersion := string(header[cachedVersionBegin:cachedVersionEnd]); cachedVersion != wazeroVersion { + staleCache = true + return + } + + functionsNum := binary.LittleEndian.Uint32(header[len(header)-4:]) + cm = &compiledModule{functionOffsets: make([]int, functionsNum), executables: &executables{}} + + var eightBytes [8]byte + for i := uint32(0); i < functionsNum; i++ { + // Read the offset of each function in the executable. + var offset uint64 + if offset, err = readUint64(reader, &eightBytes); err != nil { + err = fmt.Errorf("compilationcache: error reading func[%d] executable offset: %v", i, err) + return + } + cm.functionOffsets[i] = int(offset) + } + + executableLen, err := readUint64(reader, &eightBytes) + if err != nil { + err = fmt.Errorf("compilationcache: error reading executable size: %v", err) + return + } + + if executableLen > 0 { + executable, err := platform.MmapCodeSegment(int(executableLen)) + if err != nil { + err = fmt.Errorf("compilationcache: error mmapping executable (len=%d): %v", executableLen, err) + return nil, false, err + } + + _, err = io.ReadFull(reader, executable) + if err != nil { + err = fmt.Errorf("compilationcache: error reading executable (len=%d): %v", executableLen, err) + return nil, false, err + } + + expected := crc32.Checksum(executable, crc) + if _, err = io.ReadFull(reader, eightBytes[:4]); err != nil { + return nil, false, fmt.Errorf("compilationcache: could not read checksum: %v", err) + } else if checksum := binary.LittleEndian.Uint32(eightBytes[:4]); expected != checksum { + return nil, false, fmt.Errorf("compilationcache: checksum mismatch (expected %d, got %d)", expected, checksum) + } + + if err = platform.MprotectCodeSegment(executable); err != nil { + return nil, false, err + } + cm.executable = executable + } + + if _, err := io.ReadFull(reader, eightBytes[:1]); err != nil { + return nil, false, fmt.Errorf("compilationcache: error reading source map presence: %v", err) + } + + if eightBytes[0] == 1 { + sm := &cm.sourceMap + sourceMapLen, err := readUint64(reader, &eightBytes) + if err != nil { + err = fmt.Errorf("compilationcache: error reading source map length: %v", err) + return nil, false, err + } + executableOffset := uintptr(unsafe.Pointer(&cm.executable[0])) + for i := uint64(0); i < sourceMapLen; i++ { + wasmBinaryOffset, err := readUint64(reader, &eightBytes) + if err != nil { + err = fmt.Errorf("compilationcache: error reading source map[%d] wasm binary offset: %v", i, err) + return nil, false, err + } + executableRelativeOffset, err := readUint64(reader, &eightBytes) + if err != nil { + err = fmt.Errorf("compilationcache: error reading source map[%d] executable offset: %v", i, err) + return nil, false, err + } + sm.wasmBinaryOffsets = append(sm.wasmBinaryOffsets, wasmBinaryOffset) + // executableOffsets is absolute address, so we need to add executableOffset. + sm.executableOffsets = append(sm.executableOffsets, uintptr(executableRelativeOffset)+executableOffset) + } + } + return +} + +// readUint64 strictly reads an uint64 in little-endian byte order, using the +// given array as a buffer. This returns io.EOF if less than 8 bytes were read. +func readUint64(reader io.Reader, b *[8]byte) (uint64, error) { + s := b[0:8] + n, err := reader.Read(s) + if err != nil { + return 0, err + } else if n < 8 { // more strict than reader.Read + return 0, io.EOF + } + + // Read the u64 from the underlying buffer. + ret := binary.LittleEndian.Uint64(s) + return ret, nil +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/entrypoint_amd64.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/entrypoint_amd64.go new file mode 100644 index 00000000000..6cb0c457e26 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/entrypoint_amd64.go @@ -0,0 +1,13 @@ +package wazevo + +import _ "unsafe" + +// entrypoint is implemented by the backend. +// +//go:linkname entrypoint github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64.entrypoint +func entrypoint(preambleExecutable, functionExecutable *byte, executionContextPtr uintptr, moduleContextPtr *byte, paramResultStackPtr *uint64, goAllocatedStackSlicePtr uintptr) + +// entrypoint is implemented by the backend. +// +//go:linkname afterGoFunctionCallEntrypoint github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64.afterGoFunctionCallEntrypoint +func afterGoFunctionCallEntrypoint(executable *byte, executionContextPtr uintptr, stackPointer, framePointer uintptr) diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/entrypoint_arm64.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/entrypoint_arm64.go new file mode 100644 index 00000000000..0b8b55335cb --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/entrypoint_arm64.go @@ -0,0 +1,13 @@ +package wazevo + +import _ "unsafe" + +// entrypoint is implemented by the backend. +// +//go:linkname entrypoint github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64.entrypoint +func entrypoint(preambleExecutable, functionExecutable *byte, executionContextPtr uintptr, moduleContextPtr *byte, paramResultStackPtr *uint64, goAllocatedStackSlicePtr uintptr) + +// entrypoint is implemented by the backend. +// +//go:linkname afterGoFunctionCallEntrypoint github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64.afterGoFunctionCallEntrypoint +func afterGoFunctionCallEntrypoint(executable *byte, executionContextPtr uintptr, stackPointer, framePointer uintptr) diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/entrypoint_other.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/entrypoint_other.go new file mode 100644 index 00000000000..10cf5d4e139 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/entrypoint_other.go @@ -0,0 +1,15 @@ +//go:build !(arm64 || amd64) + +package wazevo + +import ( + "runtime" +) + +func entrypoint(preambleExecutable, functionExecutable *byte, executionContextPtr uintptr, moduleContextPtr *byte, paramResultStackPtr *uint64, goAllocatedStackSlicePtr uintptr) { + panic(runtime.GOARCH) +} + +func afterGoFunctionCallEntrypoint(executable *byte, executionContextPtr uintptr, stackPointer, framePointer uintptr) { + panic(runtime.GOARCH) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/frontend.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/frontend.go new file mode 100644 index 00000000000..5749e03c70c --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/frontend.go @@ -0,0 +1,567 @@ +// Package frontend implements the translation of WebAssembly to SSA IR using the ssa package. +package frontend + +import ( + "bytes" + "math" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" + "github.com/tetratelabs/wazero/internal/wasm" +) + +// Compiler is in charge of lowering Wasm to SSA IR, and does the optimization +// on top of it in architecture-independent way. +type Compiler struct { + // Per-module data that is used across all functions. + + m *wasm.Module + offset *wazevoapi.ModuleContextOffsetData + // ssaBuilder is a ssa.Builder used by this frontend. + ssaBuilder ssa.Builder + signatures map[*wasm.FunctionType]*ssa.Signature + listenerSignatures map[*wasm.FunctionType][2]*ssa.Signature + memoryGrowSig ssa.Signature + memoryWait32Sig ssa.Signature + memoryWait64Sig ssa.Signature + memoryNotifySig ssa.Signature + checkModuleExitCodeSig ssa.Signature + tableGrowSig ssa.Signature + refFuncSig ssa.Signature + memmoveSig ssa.Signature + ensureTermination bool + + // Followings are reset by per function. + + // wasmLocalToVariable maps the index (considered as wasm.Index of locals) + // to the corresponding ssa.Variable. + wasmLocalToVariable [] /* local index to */ ssa.Variable + wasmLocalFunctionIndex wasm.Index + wasmFunctionTypeIndex wasm.Index + wasmFunctionTyp *wasm.FunctionType + wasmFunctionLocalTypes []wasm.ValueType + wasmFunctionBody []byte + wasmFunctionBodyOffsetInCodeSection uint64 + memoryBaseVariable, memoryLenVariable ssa.Variable + needMemory bool + memoryShared bool + globalVariables []ssa.Variable + globalVariablesTypes []ssa.Type + mutableGlobalVariablesIndexes []wasm.Index // index to ^. + needListener bool + needSourceOffsetInfo bool + // br is reused during lowering. + br *bytes.Reader + loweringState loweringState + + knownSafeBounds [] /* ssa.ValueID to */ knownSafeBound + knownSafeBoundsSet []ssa.ValueID + + knownSafeBoundsAtTheEndOfBlocks [] /* ssa.BlockID to */ knownSafeBoundsAtTheEndOfBlock + varLengthKnownSafeBoundWithIDPool wazevoapi.VarLengthPool[knownSafeBoundWithID] + + execCtxPtrValue, moduleCtxPtrValue ssa.Value + + // Following are reused for the known safe bounds analysis. + + pointers []int + bounds [][]knownSafeBoundWithID +} + +type ( + // knownSafeBound represents a known safe bound for a value. + knownSafeBound struct { + // bound is a constant upper bound for the value. + bound uint64 + // absoluteAddr is the absolute address of the value. + absoluteAddr ssa.Value + } + // knownSafeBoundWithID is a knownSafeBound with the ID of the value. + knownSafeBoundWithID struct { + knownSafeBound + id ssa.ValueID + } + knownSafeBoundsAtTheEndOfBlock = wazevoapi.VarLength[knownSafeBoundWithID] +) + +var knownSafeBoundsAtTheEndOfBlockNil = wazevoapi.NewNilVarLength[knownSafeBoundWithID]() + +// NewFrontendCompiler returns a frontend Compiler. +func NewFrontendCompiler(m *wasm.Module, ssaBuilder ssa.Builder, offset *wazevoapi.ModuleContextOffsetData, ensureTermination bool, listenerOn bool, sourceInfo bool) *Compiler { + c := &Compiler{ + m: m, + ssaBuilder: ssaBuilder, + br: bytes.NewReader(nil), + offset: offset, + ensureTermination: ensureTermination, + needSourceOffsetInfo: sourceInfo, + varLengthKnownSafeBoundWithIDPool: wazevoapi.NewVarLengthPool[knownSafeBoundWithID](), + } + c.declareSignatures(listenerOn) + return c +} + +func (c *Compiler) declareSignatures(listenerOn bool) { + m := c.m + c.signatures = make(map[*wasm.FunctionType]*ssa.Signature, len(m.TypeSection)+2) + if listenerOn { + c.listenerSignatures = make(map[*wasm.FunctionType][2]*ssa.Signature, len(m.TypeSection)) + } + for i := range m.TypeSection { + wasmSig := &m.TypeSection[i] + sig := SignatureForWasmFunctionType(wasmSig) + sig.ID = ssa.SignatureID(i) + c.signatures[wasmSig] = &sig + c.ssaBuilder.DeclareSignature(&sig) + + if listenerOn { + beforeSig, afterSig := SignatureForListener(wasmSig) + beforeSig.ID = ssa.SignatureID(i) + ssa.SignatureID(len(m.TypeSection)) + afterSig.ID = ssa.SignatureID(i) + ssa.SignatureID(len(m.TypeSection))*2 + c.listenerSignatures[wasmSig] = [2]*ssa.Signature{beforeSig, afterSig} + c.ssaBuilder.DeclareSignature(beforeSig) + c.ssaBuilder.DeclareSignature(afterSig) + } + } + + begin := ssa.SignatureID(len(m.TypeSection)) + if listenerOn { + begin *= 3 + } + c.memoryGrowSig = ssa.Signature{ + ID: begin, + // Takes execution context and the page size to grow. + Params: []ssa.Type{ssa.TypeI64, ssa.TypeI32}, + // Returns the previous page size. + Results: []ssa.Type{ssa.TypeI32}, + } + c.ssaBuilder.DeclareSignature(&c.memoryGrowSig) + + c.checkModuleExitCodeSig = ssa.Signature{ + ID: c.memoryGrowSig.ID + 1, + // Only takes execution context. + Params: []ssa.Type{ssa.TypeI64}, + } + c.ssaBuilder.DeclareSignature(&c.checkModuleExitCodeSig) + + c.tableGrowSig = ssa.Signature{ + ID: c.checkModuleExitCodeSig.ID + 1, + Params: []ssa.Type{ssa.TypeI64 /* exec context */, ssa.TypeI32 /* table index */, ssa.TypeI32 /* num */, ssa.TypeI64 /* ref */}, + // Returns the previous size. + Results: []ssa.Type{ssa.TypeI32}, + } + c.ssaBuilder.DeclareSignature(&c.tableGrowSig) + + c.refFuncSig = ssa.Signature{ + ID: c.tableGrowSig.ID + 1, + Params: []ssa.Type{ssa.TypeI64 /* exec context */, ssa.TypeI32 /* func index */}, + // Returns the function reference. + Results: []ssa.Type{ssa.TypeI64}, + } + c.ssaBuilder.DeclareSignature(&c.refFuncSig) + + c.memmoveSig = ssa.Signature{ + ID: c.refFuncSig.ID + 1, + // dst, src, and the byte count. + Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeI64}, + } + + c.ssaBuilder.DeclareSignature(&c.memmoveSig) + + c.memoryWait32Sig = ssa.Signature{ + ID: c.memmoveSig.ID + 1, + // exec context, timeout, expected, addr + Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeI32, ssa.TypeI64}, + // Returns the status. + Results: []ssa.Type{ssa.TypeI32}, + } + c.ssaBuilder.DeclareSignature(&c.memoryWait32Sig) + + c.memoryWait64Sig = ssa.Signature{ + ID: c.memoryWait32Sig.ID + 1, + // exec context, timeout, expected, addr + Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeI64, ssa.TypeI64}, + // Returns the status. + Results: []ssa.Type{ssa.TypeI32}, + } + c.ssaBuilder.DeclareSignature(&c.memoryWait64Sig) + + c.memoryNotifySig = ssa.Signature{ + ID: c.memoryWait64Sig.ID + 1, + // exec context, count, addr + Params: []ssa.Type{ssa.TypeI64, ssa.TypeI32, ssa.TypeI64}, + // Returns the number notified. + Results: []ssa.Type{ssa.TypeI32}, + } + c.ssaBuilder.DeclareSignature(&c.memoryNotifySig) +} + +// SignatureForWasmFunctionType returns the ssa.Signature for the given wasm.FunctionType. +func SignatureForWasmFunctionType(typ *wasm.FunctionType) ssa.Signature { + sig := ssa.Signature{ + // +2 to pass moduleContextPtr and executionContextPtr. See the inline comment LowerToSSA. + Params: make([]ssa.Type, len(typ.Params)+2), + Results: make([]ssa.Type, len(typ.Results)), + } + sig.Params[0] = executionContextPtrTyp + sig.Params[1] = moduleContextPtrTyp + for j, typ := range typ.Params { + sig.Params[j+2] = WasmTypeToSSAType(typ) + } + for j, typ := range typ.Results { + sig.Results[j] = WasmTypeToSSAType(typ) + } + return sig +} + +// Init initializes the state of frontendCompiler and make it ready for a next function. +func (c *Compiler) Init(idx, typIndex wasm.Index, typ *wasm.FunctionType, localTypes []wasm.ValueType, body []byte, needListener bool, bodyOffsetInCodeSection uint64) { + c.ssaBuilder.Init(c.signatures[typ]) + c.loweringState.reset() + + c.wasmFunctionTypeIndex = typIndex + c.wasmLocalFunctionIndex = idx + c.wasmFunctionTyp = typ + c.wasmFunctionLocalTypes = localTypes + c.wasmFunctionBody = body + c.wasmFunctionBodyOffsetInCodeSection = bodyOffsetInCodeSection + c.needListener = needListener + c.clearSafeBounds() + c.varLengthKnownSafeBoundWithIDPool.Reset() + c.knownSafeBoundsAtTheEndOfBlocks = c.knownSafeBoundsAtTheEndOfBlocks[:0] +} + +// Note: this assumes 64-bit platform (I believe we won't have 32-bit backend ;)). +const executionContextPtrTyp, moduleContextPtrTyp = ssa.TypeI64, ssa.TypeI64 + +// LowerToSSA lowers the current function to SSA function which will be held by ssaBuilder. +// After calling this, the caller will be able to access the SSA info in *Compiler.ssaBuilder. +// +// Note that this only does the naive lowering, and do not do any optimization, instead the caller is expected to do so. +func (c *Compiler) LowerToSSA() { + builder := c.ssaBuilder + + // Set up the entry block. + entryBlock := builder.AllocateBasicBlock() + builder.SetCurrentBlock(entryBlock) + + // Functions always take two parameters in addition to Wasm-level parameters: + // + // 1. executionContextPtr: pointer to the *executionContext in wazevo package. + // This will be used to exit the execution in the face of trap, plus used for host function calls. + // + // 2. moduleContextPtr: pointer to the *moduleContextOpaque in wazevo package. + // This will be used to access memory, etc. Also, this will be used during host function calls. + // + // Note: it's clear that sometimes a function won't need them. For example, + // if the function doesn't trap and doesn't make function call, then + // we might be able to eliminate the parameter. However, if that function + // can be called via call_indirect, then we cannot eliminate because the + // signature won't match with the expected one. + // TODO: maybe there's some way to do this optimization without glitches, but so far I have no clue about the feasibility. + // + // Note: In Wasmtime or many other runtimes, moduleContextPtr is called "vmContext". Also note that `moduleContextPtr` + // is wazero-specific since other runtimes can naturally use the OS-level signal to do this job thanks to the fact that + // they can use native stack vs wazero cannot use Go-routine stack and have to use Go-runtime allocated []byte as a stack. + c.execCtxPtrValue = entryBlock.AddParam(builder, executionContextPtrTyp) + c.moduleCtxPtrValue = entryBlock.AddParam(builder, moduleContextPtrTyp) + builder.AnnotateValue(c.execCtxPtrValue, "exec_ctx") + builder.AnnotateValue(c.moduleCtxPtrValue, "module_ctx") + + for i, typ := range c.wasmFunctionTyp.Params { + st := WasmTypeToSSAType(typ) + variable := builder.DeclareVariable(st) + value := entryBlock.AddParam(builder, st) + builder.DefineVariable(variable, value, entryBlock) + c.setWasmLocalVariable(wasm.Index(i), variable) + } + c.declareWasmLocals() + c.declareNecessaryVariables() + + c.lowerBody(entryBlock) +} + +// localVariable returns the SSA variable for the given Wasm local index. +func (c *Compiler) localVariable(index wasm.Index) ssa.Variable { + return c.wasmLocalToVariable[index] +} + +func (c *Compiler) setWasmLocalVariable(index wasm.Index, variable ssa.Variable) { + idx := int(index) + if idx >= len(c.wasmLocalToVariable) { + c.wasmLocalToVariable = append(c.wasmLocalToVariable, make([]ssa.Variable, idx+1-len(c.wasmLocalToVariable))...) + } + c.wasmLocalToVariable[idx] = variable +} + +// declareWasmLocals declares the SSA variables for the Wasm locals. +func (c *Compiler) declareWasmLocals() { + localCount := wasm.Index(len(c.wasmFunctionTyp.Params)) + for i, typ := range c.wasmFunctionLocalTypes { + st := WasmTypeToSSAType(typ) + variable := c.ssaBuilder.DeclareVariable(st) + c.setWasmLocalVariable(wasm.Index(i)+localCount, variable) + c.ssaBuilder.InsertZeroValue(st) + } +} + +func (c *Compiler) declareNecessaryVariables() { + if c.needMemory = c.m.MemorySection != nil; c.needMemory { + c.memoryShared = c.m.MemorySection.IsShared + } else if c.needMemory = c.m.ImportMemoryCount > 0; c.needMemory { + for _, imp := range c.m.ImportSection { + if imp.Type == wasm.ExternTypeMemory { + c.memoryShared = imp.DescMem.IsShared + break + } + } + } + + if c.needMemory { + c.memoryBaseVariable = c.ssaBuilder.DeclareVariable(ssa.TypeI64) + c.memoryLenVariable = c.ssaBuilder.DeclareVariable(ssa.TypeI64) + } + + c.globalVariables = c.globalVariables[:0] + c.mutableGlobalVariablesIndexes = c.mutableGlobalVariablesIndexes[:0] + c.globalVariablesTypes = c.globalVariablesTypes[:0] + for _, imp := range c.m.ImportSection { + if imp.Type == wasm.ExternTypeGlobal { + desc := imp.DescGlobal + c.declareWasmGlobal(desc.ValType, desc.Mutable) + } + } + for _, g := range c.m.GlobalSection { + desc := g.Type + c.declareWasmGlobal(desc.ValType, desc.Mutable) + } + + // TODO: add tables. +} + +func (c *Compiler) declareWasmGlobal(typ wasm.ValueType, mutable bool) { + var st ssa.Type + switch typ { + case wasm.ValueTypeI32: + st = ssa.TypeI32 + case wasm.ValueTypeI64, + // Both externref and funcref are represented as I64 since we only support 64-bit platforms. + wasm.ValueTypeExternref, wasm.ValueTypeFuncref: + st = ssa.TypeI64 + case wasm.ValueTypeF32: + st = ssa.TypeF32 + case wasm.ValueTypeF64: + st = ssa.TypeF64 + case wasm.ValueTypeV128: + st = ssa.TypeV128 + default: + panic("TODO: " + wasm.ValueTypeName(typ)) + } + v := c.ssaBuilder.DeclareVariable(st) + index := wasm.Index(len(c.globalVariables)) + c.globalVariables = append(c.globalVariables, v) + c.globalVariablesTypes = append(c.globalVariablesTypes, st) + if mutable { + c.mutableGlobalVariablesIndexes = append(c.mutableGlobalVariablesIndexes, index) + } +} + +// WasmTypeToSSAType converts wasm.ValueType to ssa.Type. +func WasmTypeToSSAType(vt wasm.ValueType) ssa.Type { + switch vt { + case wasm.ValueTypeI32: + return ssa.TypeI32 + case wasm.ValueTypeI64, + // Both externref and funcref are represented as I64 since we only support 64-bit platforms. + wasm.ValueTypeExternref, wasm.ValueTypeFuncref: + return ssa.TypeI64 + case wasm.ValueTypeF32: + return ssa.TypeF32 + case wasm.ValueTypeF64: + return ssa.TypeF64 + case wasm.ValueTypeV128: + return ssa.TypeV128 + default: + panic("TODO: " + wasm.ValueTypeName(vt)) + } +} + +// addBlockParamsFromWasmTypes adds the block parameters to the given block. +func (c *Compiler) addBlockParamsFromWasmTypes(tps []wasm.ValueType, blk ssa.BasicBlock) { + for _, typ := range tps { + st := WasmTypeToSSAType(typ) + blk.AddParam(c.ssaBuilder, st) + } +} + +// formatBuilder outputs the constructed SSA function as a string with a source information. +func (c *Compiler) formatBuilder() string { + return c.ssaBuilder.Format() +} + +// SignatureForListener returns the signatures for the listener functions. +func SignatureForListener(wasmSig *wasm.FunctionType) (*ssa.Signature, *ssa.Signature) { + beforeSig := &ssa.Signature{} + beforeSig.Params = make([]ssa.Type, len(wasmSig.Params)+2) + beforeSig.Params[0] = ssa.TypeI64 // Execution context. + beforeSig.Params[1] = ssa.TypeI32 // Function index. + for i, p := range wasmSig.Params { + beforeSig.Params[i+2] = WasmTypeToSSAType(p) + } + afterSig := &ssa.Signature{} + afterSig.Params = make([]ssa.Type, len(wasmSig.Results)+2) + afterSig.Params[0] = ssa.TypeI64 // Execution context. + afterSig.Params[1] = ssa.TypeI32 // Function index. + for i, p := range wasmSig.Results { + afterSig.Params[i+2] = WasmTypeToSSAType(p) + } + return beforeSig, afterSig +} + +// isBoundSafe returns true if the given value is known to be safe to access up to the given bound. +func (c *Compiler) getKnownSafeBound(v ssa.ValueID) *knownSafeBound { + if int(v) >= len(c.knownSafeBounds) { + return nil + } + return &c.knownSafeBounds[v] +} + +// recordKnownSafeBound records the given safe bound for the given value. +func (c *Compiler) recordKnownSafeBound(v ssa.ValueID, safeBound uint64, absoluteAddr ssa.Value) { + if int(v) >= len(c.knownSafeBounds) { + c.knownSafeBounds = append(c.knownSafeBounds, make([]knownSafeBound, v+1)...) + } + + if exiting := c.knownSafeBounds[v]; exiting.bound == 0 { + c.knownSafeBounds[v] = knownSafeBound{ + bound: safeBound, + absoluteAddr: absoluteAddr, + } + c.knownSafeBoundsSet = append(c.knownSafeBoundsSet, v) + } else if safeBound > exiting.bound { + c.knownSafeBounds[v].bound = safeBound + } +} + +// clearSafeBounds clears the known safe bounds. +func (c *Compiler) clearSafeBounds() { + for _, v := range c.knownSafeBoundsSet { + ptr := &c.knownSafeBounds[v] + ptr.bound = 0 + ptr.absoluteAddr = ssa.ValueInvalid + } + c.knownSafeBoundsSet = c.knownSafeBoundsSet[:0] +} + +// resetAbsoluteAddressInSafeBounds resets the absolute addresses recorded in the known safe bounds. +func (c *Compiler) resetAbsoluteAddressInSafeBounds() { + for _, v := range c.knownSafeBoundsSet { + ptr := &c.knownSafeBounds[v] + ptr.absoluteAddr = ssa.ValueInvalid + } +} + +func (k *knownSafeBound) valid() bool { + return k != nil && k.bound > 0 +} + +func (c *Compiler) allocateVarLengthValues(_cap int, vs ...ssa.Value) ssa.Values { + builder := c.ssaBuilder + pool := builder.VarLengthPool() + args := pool.Allocate(_cap) + args = args.Append(pool, vs...) + return args +} + +func (c *Compiler) finalizeKnownSafeBoundsAtTheEndOfBlock(bID ssa.BasicBlockID) { + _bID := int(bID) + if l := len(c.knownSafeBoundsAtTheEndOfBlocks); _bID >= l { + c.knownSafeBoundsAtTheEndOfBlocks = append(c.knownSafeBoundsAtTheEndOfBlocks, + make([]knownSafeBoundsAtTheEndOfBlock, _bID+1-len(c.knownSafeBoundsAtTheEndOfBlocks))...) + for i := l; i < len(c.knownSafeBoundsAtTheEndOfBlocks); i++ { + c.knownSafeBoundsAtTheEndOfBlocks[i] = knownSafeBoundsAtTheEndOfBlockNil + } + } + p := &c.varLengthKnownSafeBoundWithIDPool + size := len(c.knownSafeBoundsSet) + allocated := c.varLengthKnownSafeBoundWithIDPool.Allocate(size) + // Sort the known safe bounds by the value ID so that we can use the intersection algorithm in initializeCurrentBlockKnownBounds. + sortSSAValueIDs(c.knownSafeBoundsSet) + for _, vID := range c.knownSafeBoundsSet { + kb := c.knownSafeBounds[vID] + allocated = allocated.Append(p, knownSafeBoundWithID{ + knownSafeBound: kb, + id: vID, + }) + } + c.knownSafeBoundsAtTheEndOfBlocks[bID] = allocated + c.clearSafeBounds() +} + +func (c *Compiler) initializeCurrentBlockKnownBounds() { + currentBlk := c.ssaBuilder.CurrentBlock() + switch preds := currentBlk.Preds(); preds { + case 0: + case 1: + pred := currentBlk.Pred(0).ID() + for _, kb := range c.getKnownSafeBoundsAtTheEndOfBlocks(pred).View() { + // Unless the block is sealed, we cannot assume the absolute address is valid: + // later we might add another predecessor that has no visibility of that value. + addr := ssa.ValueInvalid + if currentBlk.Sealed() { + addr = kb.absoluteAddr + } + c.recordKnownSafeBound(kb.id, kb.bound, addr) + } + default: + c.pointers = c.pointers[:0] + c.bounds = c.bounds[:0] + for i := 0; i < preds; i++ { + c.bounds = append(c.bounds, c.getKnownSafeBoundsAtTheEndOfBlocks(currentBlk.Pred(i).ID()).View()) + c.pointers = append(c.pointers, 0) + } + + // If there are multiple predecessors, we need to find the intersection of the known safe bounds. + + outer: + for { + smallestID := ssa.ValueID(math.MaxUint32) + for i, ptr := range c.pointers { + if ptr >= len(c.bounds[i]) { + break outer + } + cb := &c.bounds[i][ptr] + if id := cb.id; id < smallestID { + smallestID = cb.id + } + } + + // Check if current elements are the same across all lists. + same := true + minBound := uint64(math.MaxUint64) + for i := 0; i < preds; i++ { + cb := &c.bounds[i][c.pointers[i]] + if cb.id != smallestID { + same = false + } else { + if cb.bound < minBound { + minBound = cb.bound + } + c.pointers[i]++ + } + } + + if same { // All elements are the same. + // Absolute address cannot be used in the intersection since the value might be only defined in one of the predecessors. + c.recordKnownSafeBound(smallestID, minBound, ssa.ValueInvalid) + } + } + } +} + +func (c *Compiler) getKnownSafeBoundsAtTheEndOfBlocks(id ssa.BasicBlockID) knownSafeBoundsAtTheEndOfBlock { + if int(id) >= len(c.knownSafeBoundsAtTheEndOfBlocks) { + return knownSafeBoundsAtTheEndOfBlockNil + } + return c.knownSafeBoundsAtTheEndOfBlocks[id] +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/lower.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/lower.go new file mode 100644 index 00000000000..1277db0bfea --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/lower.go @@ -0,0 +1,4360 @@ +package frontend + +import ( + "encoding/binary" + "fmt" + "math" + "runtime" + "strings" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" + "github.com/tetratelabs/wazero/internal/leb128" + "github.com/tetratelabs/wazero/internal/wasm" +) + +type ( + // loweringState is used to keep the state of lowering. + loweringState struct { + // values holds the values on the Wasm stack. + values []ssa.Value + controlFrames []controlFrame + unreachable bool + unreachableDepth int + tmpForBrTable []uint32 + pc int + } + controlFrame struct { + kind controlFrameKind + // originalStackLen holds the number of values on the Wasm stack + // when start executing this control frame minus params for the block. + originalStackLenWithoutParam int + // blk is the loop header if this is loop, and is the else-block if this is an if frame. + blk, + // followingBlock is the basic block we enter if we reach "end" of block. + followingBlock ssa.BasicBlock + blockType *wasm.FunctionType + // clonedArgs hold the arguments to Else block. + clonedArgs ssa.Values + } + + controlFrameKind byte +) + +// String implements fmt.Stringer for debugging. +func (l *loweringState) String() string { + var str []string + for _, v := range l.values { + str = append(str, fmt.Sprintf("v%v", v.ID())) + } + var frames []string + for i := range l.controlFrames { + frames = append(frames, l.controlFrames[i].kind.String()) + } + return fmt.Sprintf("\n\tunreachable=%v(depth=%d)\n\tstack: %s\n\tcontrol frames: %s", + l.unreachable, l.unreachableDepth, + strings.Join(str, ", "), + strings.Join(frames, ", "), + ) +} + +const ( + controlFrameKindFunction = iota + 1 + controlFrameKindLoop + controlFrameKindIfWithElse + controlFrameKindIfWithoutElse + controlFrameKindBlock +) + +// String implements fmt.Stringer for debugging. +func (k controlFrameKind) String() string { + switch k { + case controlFrameKindFunction: + return "function" + case controlFrameKindLoop: + return "loop" + case controlFrameKindIfWithElse: + return "if_with_else" + case controlFrameKindIfWithoutElse: + return "if_without_else" + case controlFrameKindBlock: + return "block" + default: + panic(k) + } +} + +// isLoop returns true if this is a loop frame. +func (ctrl *controlFrame) isLoop() bool { + return ctrl.kind == controlFrameKindLoop +} + +// reset resets the state of loweringState for reuse. +func (l *loweringState) reset() { + l.values = l.values[:0] + l.controlFrames = l.controlFrames[:0] + l.pc = 0 + l.unreachable = false + l.unreachableDepth = 0 +} + +func (l *loweringState) peek() (ret ssa.Value) { + tail := len(l.values) - 1 + return l.values[tail] +} + +func (l *loweringState) pop() (ret ssa.Value) { + tail := len(l.values) - 1 + ret = l.values[tail] + l.values = l.values[:tail] + return +} + +func (l *loweringState) push(ret ssa.Value) { + l.values = append(l.values, ret) +} + +func (c *Compiler) nPeekDup(n int) ssa.Values { + if n == 0 { + return ssa.ValuesNil + } + + l := c.state() + tail := len(l.values) + + args := c.allocateVarLengthValues(n, l.values[tail-n:tail]...) + return args +} + +func (l *loweringState) ctrlPop() (ret controlFrame) { + tail := len(l.controlFrames) - 1 + ret = l.controlFrames[tail] + l.controlFrames = l.controlFrames[:tail] + return +} + +func (l *loweringState) ctrlPush(ret controlFrame) { + l.controlFrames = append(l.controlFrames, ret) +} + +func (l *loweringState) ctrlPeekAt(n int) (ret *controlFrame) { + tail := len(l.controlFrames) - 1 + return &l.controlFrames[tail-n] +} + +// lowerBody lowers the body of the Wasm function to the SSA form. +func (c *Compiler) lowerBody(entryBlk ssa.BasicBlock) { + c.ssaBuilder.Seal(entryBlk) + + if c.needListener { + c.callListenerBefore() + } + + // Pushes the empty control frame which corresponds to the function return. + c.loweringState.ctrlPush(controlFrame{ + kind: controlFrameKindFunction, + blockType: c.wasmFunctionTyp, + followingBlock: c.ssaBuilder.ReturnBlock(), + }) + + for c.loweringState.pc < len(c.wasmFunctionBody) { + blkBeforeLowering := c.ssaBuilder.CurrentBlock() + c.lowerCurrentOpcode() + blkAfterLowering := c.ssaBuilder.CurrentBlock() + if blkBeforeLowering != blkAfterLowering { + // In Wasm, once a block exits, that means we've done compiling the block. + // Therefore, we finalize the known bounds at the end of the block for the exiting block. + c.finalizeKnownSafeBoundsAtTheEndOfBlock(blkBeforeLowering.ID()) + // After that, we initialize the known bounds for the new compilation target block. + c.initializeCurrentBlockKnownBounds() + } + } +} + +func (c *Compiler) state() *loweringState { + return &c.loweringState +} + +func (c *Compiler) lowerCurrentOpcode() { + op := c.wasmFunctionBody[c.loweringState.pc] + + if c.needSourceOffsetInfo { + c.ssaBuilder.SetCurrentSourceOffset( + ssa.SourceOffset(c.loweringState.pc) + ssa.SourceOffset(c.wasmFunctionBodyOffsetInCodeSection), + ) + } + + builder := c.ssaBuilder + state := c.state() + switch op { + case wasm.OpcodeI32Const: + c := c.readI32s() + if state.unreachable { + break + } + + iconst := builder.AllocateInstruction().AsIconst32(uint32(c)).Insert(builder) + value := iconst.Return() + state.push(value) + case wasm.OpcodeI64Const: + c := c.readI64s() + if state.unreachable { + break + } + iconst := builder.AllocateInstruction().AsIconst64(uint64(c)).Insert(builder) + value := iconst.Return() + state.push(value) + case wasm.OpcodeF32Const: + f32 := c.readF32() + if state.unreachable { + break + } + f32const := builder.AllocateInstruction(). + AsF32const(f32). + Insert(builder). + Return() + state.push(f32const) + case wasm.OpcodeF64Const: + f64 := c.readF64() + if state.unreachable { + break + } + f64const := builder.AllocateInstruction(). + AsF64const(f64). + Insert(builder). + Return() + state.push(f64const) + case wasm.OpcodeI32Add, wasm.OpcodeI64Add: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + iadd := builder.AllocateInstruction() + iadd.AsIadd(x, y) + builder.InsertInstruction(iadd) + value := iadd.Return() + state.push(value) + case wasm.OpcodeI32Sub, wasm.OpcodeI64Sub: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + isub := builder.AllocateInstruction() + isub.AsIsub(x, y) + builder.InsertInstruction(isub) + value := isub.Return() + state.push(value) + case wasm.OpcodeF32Add, wasm.OpcodeF64Add: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + iadd := builder.AllocateInstruction() + iadd.AsFadd(x, y) + builder.InsertInstruction(iadd) + value := iadd.Return() + state.push(value) + case wasm.OpcodeI32Mul, wasm.OpcodeI64Mul: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + imul := builder.AllocateInstruction() + imul.AsImul(x, y) + builder.InsertInstruction(imul) + value := imul.Return() + state.push(value) + case wasm.OpcodeF32Sub, wasm.OpcodeF64Sub: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + isub := builder.AllocateInstruction() + isub.AsFsub(x, y) + builder.InsertInstruction(isub) + value := isub.Return() + state.push(value) + case wasm.OpcodeF32Mul, wasm.OpcodeF64Mul: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + isub := builder.AllocateInstruction() + isub.AsFmul(x, y) + builder.InsertInstruction(isub) + value := isub.Return() + state.push(value) + case wasm.OpcodeF32Div, wasm.OpcodeF64Div: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + isub := builder.AllocateInstruction() + isub.AsFdiv(x, y) + builder.InsertInstruction(isub) + value := isub.Return() + state.push(value) + case wasm.OpcodeF32Max, wasm.OpcodeF64Max: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + isub := builder.AllocateInstruction() + isub.AsFmax(x, y) + builder.InsertInstruction(isub) + value := isub.Return() + state.push(value) + case wasm.OpcodeF32Min, wasm.OpcodeF64Min: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + isub := builder.AllocateInstruction() + isub.AsFmin(x, y) + builder.InsertInstruction(isub) + value := isub.Return() + state.push(value) + case wasm.OpcodeI64Extend8S: + if state.unreachable { + break + } + c.insertIntegerExtend(true, 8, 64) + case wasm.OpcodeI64Extend16S: + if state.unreachable { + break + } + c.insertIntegerExtend(true, 16, 64) + case wasm.OpcodeI64Extend32S, wasm.OpcodeI64ExtendI32S: + if state.unreachable { + break + } + c.insertIntegerExtend(true, 32, 64) + case wasm.OpcodeI64ExtendI32U: + if state.unreachable { + break + } + c.insertIntegerExtend(false, 32, 64) + case wasm.OpcodeI32Extend8S: + if state.unreachable { + break + } + c.insertIntegerExtend(true, 8, 32) + case wasm.OpcodeI32Extend16S: + if state.unreachable { + break + } + c.insertIntegerExtend(true, 16, 32) + case wasm.OpcodeI32Eqz, wasm.OpcodeI64Eqz: + if state.unreachable { + break + } + x := state.pop() + zero := builder.AllocateInstruction() + if op == wasm.OpcodeI32Eqz { + zero.AsIconst32(0) + } else { + zero.AsIconst64(0) + } + builder.InsertInstruction(zero) + icmp := builder.AllocateInstruction(). + AsIcmp(x, zero.Return(), ssa.IntegerCmpCondEqual). + Insert(builder). + Return() + state.push(icmp) + case wasm.OpcodeI32Eq, wasm.OpcodeI64Eq: + if state.unreachable { + break + } + c.insertIcmp(ssa.IntegerCmpCondEqual) + case wasm.OpcodeI32Ne, wasm.OpcodeI64Ne: + if state.unreachable { + break + } + c.insertIcmp(ssa.IntegerCmpCondNotEqual) + case wasm.OpcodeI32LtS, wasm.OpcodeI64LtS: + if state.unreachable { + break + } + c.insertIcmp(ssa.IntegerCmpCondSignedLessThan) + case wasm.OpcodeI32LtU, wasm.OpcodeI64LtU: + if state.unreachable { + break + } + c.insertIcmp(ssa.IntegerCmpCondUnsignedLessThan) + case wasm.OpcodeI32GtS, wasm.OpcodeI64GtS: + if state.unreachable { + break + } + c.insertIcmp(ssa.IntegerCmpCondSignedGreaterThan) + case wasm.OpcodeI32GtU, wasm.OpcodeI64GtU: + if state.unreachable { + break + } + c.insertIcmp(ssa.IntegerCmpCondUnsignedGreaterThan) + case wasm.OpcodeI32LeS, wasm.OpcodeI64LeS: + if state.unreachable { + break + } + c.insertIcmp(ssa.IntegerCmpCondSignedLessThanOrEqual) + case wasm.OpcodeI32LeU, wasm.OpcodeI64LeU: + if state.unreachable { + break + } + c.insertIcmp(ssa.IntegerCmpCondUnsignedLessThanOrEqual) + case wasm.OpcodeI32GeS, wasm.OpcodeI64GeS: + if state.unreachable { + break + } + c.insertIcmp(ssa.IntegerCmpCondSignedGreaterThanOrEqual) + case wasm.OpcodeI32GeU, wasm.OpcodeI64GeU: + if state.unreachable { + break + } + c.insertIcmp(ssa.IntegerCmpCondUnsignedGreaterThanOrEqual) + + case wasm.OpcodeF32Eq, wasm.OpcodeF64Eq: + if state.unreachable { + break + } + c.insertFcmp(ssa.FloatCmpCondEqual) + case wasm.OpcodeF32Ne, wasm.OpcodeF64Ne: + if state.unreachable { + break + } + c.insertFcmp(ssa.FloatCmpCondNotEqual) + case wasm.OpcodeF32Lt, wasm.OpcodeF64Lt: + if state.unreachable { + break + } + c.insertFcmp(ssa.FloatCmpCondLessThan) + case wasm.OpcodeF32Gt, wasm.OpcodeF64Gt: + if state.unreachable { + break + } + c.insertFcmp(ssa.FloatCmpCondGreaterThan) + case wasm.OpcodeF32Le, wasm.OpcodeF64Le: + if state.unreachable { + break + } + c.insertFcmp(ssa.FloatCmpCondLessThanOrEqual) + case wasm.OpcodeF32Ge, wasm.OpcodeF64Ge: + if state.unreachable { + break + } + c.insertFcmp(ssa.FloatCmpCondGreaterThanOrEqual) + case wasm.OpcodeF32Neg, wasm.OpcodeF64Neg: + if state.unreachable { + break + } + x := state.pop() + v := builder.AllocateInstruction().AsFneg(x).Insert(builder).Return() + state.push(v) + case wasm.OpcodeF32Sqrt, wasm.OpcodeF64Sqrt: + if state.unreachable { + break + } + x := state.pop() + v := builder.AllocateInstruction().AsSqrt(x).Insert(builder).Return() + state.push(v) + case wasm.OpcodeF32Abs, wasm.OpcodeF64Abs: + if state.unreachable { + break + } + x := state.pop() + v := builder.AllocateInstruction().AsFabs(x).Insert(builder).Return() + state.push(v) + case wasm.OpcodeF32Copysign, wasm.OpcodeF64Copysign: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + v := builder.AllocateInstruction().AsFcopysign(x, y).Insert(builder).Return() + state.push(v) + + case wasm.OpcodeF32Ceil, wasm.OpcodeF64Ceil: + if state.unreachable { + break + } + x := state.pop() + v := builder.AllocateInstruction().AsCeil(x).Insert(builder).Return() + state.push(v) + case wasm.OpcodeF32Floor, wasm.OpcodeF64Floor: + if state.unreachable { + break + } + x := state.pop() + v := builder.AllocateInstruction().AsFloor(x).Insert(builder).Return() + state.push(v) + case wasm.OpcodeF32Trunc, wasm.OpcodeF64Trunc: + if state.unreachable { + break + } + x := state.pop() + v := builder.AllocateInstruction().AsTrunc(x).Insert(builder).Return() + state.push(v) + case wasm.OpcodeF32Nearest, wasm.OpcodeF64Nearest: + if state.unreachable { + break + } + x := state.pop() + v := builder.AllocateInstruction().AsNearest(x).Insert(builder).Return() + state.push(v) + case wasm.OpcodeI64TruncF64S, wasm.OpcodeI64TruncF32S, + wasm.OpcodeI32TruncF64S, wasm.OpcodeI32TruncF32S, + wasm.OpcodeI64TruncF64U, wasm.OpcodeI64TruncF32U, + wasm.OpcodeI32TruncF64U, wasm.OpcodeI32TruncF32U: + if state.unreachable { + break + } + ret := builder.AllocateInstruction().AsFcvtToInt( + state.pop(), + c.execCtxPtrValue, + op == wasm.OpcodeI64TruncF64S || op == wasm.OpcodeI64TruncF32S || op == wasm.OpcodeI32TruncF32S || op == wasm.OpcodeI32TruncF64S, + op == wasm.OpcodeI64TruncF64S || op == wasm.OpcodeI64TruncF32S || op == wasm.OpcodeI64TruncF64U || op == wasm.OpcodeI64TruncF32U, + false, + ).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeMiscPrefix: + state.pc++ + // A misc opcode is encoded as an unsigned variable 32-bit integer. + miscOpUint, num, err := leb128.LoadUint32(c.wasmFunctionBody[state.pc:]) + if err != nil { + // In normal conditions this should never happen because the function has passed validation. + panic(fmt.Sprintf("failed to read misc opcode: %v", err)) + } + state.pc += int(num - 1) + miscOp := wasm.OpcodeMisc(miscOpUint) + switch miscOp { + case wasm.OpcodeMiscI64TruncSatF64S, wasm.OpcodeMiscI64TruncSatF32S, + wasm.OpcodeMiscI32TruncSatF64S, wasm.OpcodeMiscI32TruncSatF32S, + wasm.OpcodeMiscI64TruncSatF64U, wasm.OpcodeMiscI64TruncSatF32U, + wasm.OpcodeMiscI32TruncSatF64U, wasm.OpcodeMiscI32TruncSatF32U: + if state.unreachable { + break + } + ret := builder.AllocateInstruction().AsFcvtToInt( + state.pop(), + c.execCtxPtrValue, + miscOp == wasm.OpcodeMiscI64TruncSatF64S || miscOp == wasm.OpcodeMiscI64TruncSatF32S || miscOp == wasm.OpcodeMiscI32TruncSatF32S || miscOp == wasm.OpcodeMiscI32TruncSatF64S, + miscOp == wasm.OpcodeMiscI64TruncSatF64S || miscOp == wasm.OpcodeMiscI64TruncSatF32S || miscOp == wasm.OpcodeMiscI64TruncSatF64U || miscOp == wasm.OpcodeMiscI64TruncSatF32U, + true, + ).Insert(builder).Return() + state.push(ret) + + case wasm.OpcodeMiscTableSize: + tableIndex := c.readI32u() + if state.unreachable { + break + } + + // Load the table. + loadTableInstancePtr := builder.AllocateInstruction() + loadTableInstancePtr.AsLoad(c.moduleCtxPtrValue, c.offset.TableOffset(int(tableIndex)).U32(), ssa.TypeI64) + builder.InsertInstruction(loadTableInstancePtr) + tableInstancePtr := loadTableInstancePtr.Return() + + // Load the table's length. + loadTableLen := builder.AllocateInstruction(). + AsLoad(tableInstancePtr, tableInstanceLenOffset, ssa.TypeI32). + Insert(builder) + state.push(loadTableLen.Return()) + + case wasm.OpcodeMiscTableGrow: + tableIndex := c.readI32u() + if state.unreachable { + break + } + + c.storeCallerModuleContext() + + tableIndexVal := builder.AllocateInstruction().AsIconst32(tableIndex).Insert(builder).Return() + + num := state.pop() + r := state.pop() + + tableGrowPtr := builder.AllocateInstruction(). + AsLoad(c.execCtxPtrValue, + wazevoapi.ExecutionContextOffsetTableGrowTrampolineAddress.U32(), + ssa.TypeI64, + ).Insert(builder).Return() + + args := c.allocateVarLengthValues(4, c.execCtxPtrValue, tableIndexVal, num, r) + callGrowRet := builder. + AllocateInstruction(). + AsCallIndirect(tableGrowPtr, &c.tableGrowSig, args). + Insert(builder).Return() + state.push(callGrowRet) + + case wasm.OpcodeMiscTableCopy: + dstTableIndex := c.readI32u() + srcTableIndex := c.readI32u() + if state.unreachable { + break + } + + copySize := builder. + AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return() + srcOffset := builder. + AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return() + dstOffset := builder. + AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return() + + // Out of bounds check. + dstTableInstancePtr := c.boundsCheckInTable(dstTableIndex, dstOffset, copySize) + srcTableInstancePtr := c.boundsCheckInTable(srcTableIndex, srcOffset, copySize) + + dstTableBaseAddr := c.loadTableBaseAddr(dstTableInstancePtr) + srcTableBaseAddr := c.loadTableBaseAddr(srcTableInstancePtr) + + three := builder.AllocateInstruction().AsIconst64(3).Insert(builder).Return() + + dstOffsetInBytes := builder.AllocateInstruction().AsIshl(dstOffset, three).Insert(builder).Return() + dstAddr := builder.AllocateInstruction().AsIadd(dstTableBaseAddr, dstOffsetInBytes).Insert(builder).Return() + srcOffsetInBytes := builder.AllocateInstruction().AsIshl(srcOffset, three).Insert(builder).Return() + srcAddr := builder.AllocateInstruction().AsIadd(srcTableBaseAddr, srcOffsetInBytes).Insert(builder).Return() + + copySizeInBytes := builder.AllocateInstruction().AsIshl(copySize, three).Insert(builder).Return() + c.callMemmove(dstAddr, srcAddr, copySizeInBytes) + + case wasm.OpcodeMiscMemoryCopy: + state.pc += 2 // +2 to skip two memory indexes which are fixed to zero. + if state.unreachable { + break + } + + copySize := builder. + AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return() + srcOffset := builder. + AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return() + dstOffset := builder. + AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return() + + // Out of bounds check. + memLen := c.getMemoryLenValue(false) + c.boundsCheckInMemory(memLen, dstOffset, copySize) + c.boundsCheckInMemory(memLen, srcOffset, copySize) + + memBase := c.getMemoryBaseValue(false) + dstAddr := builder.AllocateInstruction().AsIadd(memBase, dstOffset).Insert(builder).Return() + srcAddr := builder.AllocateInstruction().AsIadd(memBase, srcOffset).Insert(builder).Return() + + c.callMemmove(dstAddr, srcAddr, copySize) + + case wasm.OpcodeMiscTableFill: + tableIndex := c.readI32u() + if state.unreachable { + break + } + fillSize := state.pop() + value := state.pop() + offset := state.pop() + + fillSizeExt := builder. + AllocateInstruction().AsUExtend(fillSize, 32, 64).Insert(builder).Return() + offsetExt := builder. + AllocateInstruction().AsUExtend(offset, 32, 64).Insert(builder).Return() + tableInstancePtr := c.boundsCheckInTable(tableIndex, offsetExt, fillSizeExt) + + three := builder.AllocateInstruction().AsIconst64(3).Insert(builder).Return() + offsetInBytes := builder.AllocateInstruction().AsIshl(offsetExt, three).Insert(builder).Return() + fillSizeInBytes := builder.AllocateInstruction().AsIshl(fillSizeExt, three).Insert(builder).Return() + + // Calculate the base address of the table. + tableBaseAddr := c.loadTableBaseAddr(tableInstancePtr) + addr := builder.AllocateInstruction().AsIadd(tableBaseAddr, offsetInBytes).Insert(builder).Return() + + // Uses the copy trick for faster filling buffer like memory.fill, but in this case we copy 8 bytes at a time. + // Tables are rarely huge, so ignore the 8KB maximum. + // https://github.com/golang/go/blob/go1.24.0/src/slices/slices.go#L514-L517 + // + // buf := memoryInst.Buffer[offset : offset+fillSize] + // buf[0:8] = value + // for i := 8; i < fillSize; i *= 2 { Begin with 8 bytes. + // copy(buf[i:], buf[:i]) + // } + + // Prepare the loop and following block. + beforeLoop := builder.AllocateBasicBlock() + loopBlk := builder.AllocateBasicBlock() + loopVar := loopBlk.AddParam(builder, ssa.TypeI64) + followingBlk := builder.AllocateBasicBlock() + + // Insert the jump to the beforeLoop block; If the fillSize is zero, then jump to the following block to skip entire logics. + zero := builder.AllocateInstruction().AsIconst64(0).Insert(builder).Return() + ifFillSizeZero := builder.AllocateInstruction().AsIcmp(fillSizeExt, zero, ssa.IntegerCmpCondEqual). + Insert(builder).Return() + builder.AllocateInstruction().AsBrnz(ifFillSizeZero, ssa.ValuesNil, followingBlk).Insert(builder) + c.insertJumpToBlock(ssa.ValuesNil, beforeLoop) + + // buf[0:8] = value + builder.SetCurrentBlock(beforeLoop) + builder.AllocateInstruction().AsStore(ssa.OpcodeStore, value, addr, 0).Insert(builder) + eight := builder.AllocateInstruction().AsIconst64(8).Insert(builder).Return() + c.insertJumpToBlock(c.allocateVarLengthValues(1, eight), loopBlk) + + builder.SetCurrentBlock(loopBlk) + dstAddr := builder.AllocateInstruction().AsIadd(addr, loopVar).Insert(builder).Return() + + newLoopVar := builder.AllocateInstruction().AsIadd(loopVar, loopVar).Insert(builder).Return() + newLoopVarLessThanFillSize := builder.AllocateInstruction(). + AsIcmp(newLoopVar, fillSizeInBytes, ssa.IntegerCmpCondUnsignedLessThan).Insert(builder).Return() + + // On the last iteration, count must be fillSizeInBytes-loopVar. + diff := builder.AllocateInstruction().AsIsub(fillSizeInBytes, loopVar).Insert(builder).Return() + count := builder.AllocateInstruction().AsSelect(newLoopVarLessThanFillSize, loopVar, diff).Insert(builder).Return() + + c.callMemmove(dstAddr, addr, count) + + builder.AllocateInstruction(). + AsBrnz(newLoopVarLessThanFillSize, c.allocateVarLengthValues(1, newLoopVar), loopBlk). + Insert(builder) + + c.insertJumpToBlock(ssa.ValuesNil, followingBlk) + builder.SetCurrentBlock(followingBlk) + + builder.Seal(beforeLoop) + builder.Seal(loopBlk) + builder.Seal(followingBlk) + + case wasm.OpcodeMiscMemoryFill: + state.pc++ // Skip the memory index which is fixed to zero. + if state.unreachable { + break + } + + fillSize := builder. + AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return() + value := state.pop() + offset := builder. + AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return() + + // Out of bounds check. + c.boundsCheckInMemory(c.getMemoryLenValue(false), offset, fillSize) + + // Calculate the base address: + addr := builder.AllocateInstruction().AsIadd(c.getMemoryBaseValue(false), offset).Insert(builder).Return() + + // Uses the copy trick for faster filling buffer, with a maximum chunk size of 8KB. + // https://github.com/golang/go/blob/go1.24.0/src/bytes/bytes.go#L664-L673 + // + // buf := memoryInst.Buffer[offset : offset+fillSize] + // buf[0] = value + // for i := 1; i < fillSize; { + // chunk := ((i - 1) & 8191) + 1 + // copy(buf[i:], buf[:chunk]) + // i += chunk + // } + + // Prepare the loop and following block. + beforeLoop := builder.AllocateBasicBlock() + loopBlk := builder.AllocateBasicBlock() + loopVar := loopBlk.AddParam(builder, ssa.TypeI64) + followingBlk := builder.AllocateBasicBlock() + + // Insert the jump to the beforeLoop block; If the fillSize is zero, then jump to the following block to skip entire logics. + zero := builder.AllocateInstruction().AsIconst64(0).Insert(builder).Return() + ifFillSizeZero := builder.AllocateInstruction().AsIcmp(fillSize, zero, ssa.IntegerCmpCondEqual). + Insert(builder).Return() + builder.AllocateInstruction().AsBrnz(ifFillSizeZero, ssa.ValuesNil, followingBlk).Insert(builder) + c.insertJumpToBlock(ssa.ValuesNil, beforeLoop) + + // buf[0] = value + builder.SetCurrentBlock(beforeLoop) + builder.AllocateInstruction().AsStore(ssa.OpcodeIstore8, value, addr, 0).Insert(builder) + one := builder.AllocateInstruction().AsIconst64(1).Insert(builder).Return() + c.insertJumpToBlock(c.allocateVarLengthValues(1, one), loopBlk) + + builder.SetCurrentBlock(loopBlk) + dstAddr := builder.AllocateInstruction().AsIadd(addr, loopVar).Insert(builder).Return() + + // chunk := ((i - 1) & 8191) + 1 + mask := builder.AllocateInstruction().AsIconst64(8191).Insert(builder).Return() + tmp1 := builder.AllocateInstruction().AsIsub(loopVar, one).Insert(builder).Return() + tmp2 := builder.AllocateInstruction().AsBand(tmp1, mask).Insert(builder).Return() + chunk := builder.AllocateInstruction().AsIadd(tmp2, one).Insert(builder).Return() + + // i += chunk + newLoopVar := builder.AllocateInstruction().AsIadd(loopVar, chunk).Insert(builder).Return() + newLoopVarLessThanFillSize := builder.AllocateInstruction(). + AsIcmp(newLoopVar, fillSize, ssa.IntegerCmpCondUnsignedLessThan).Insert(builder).Return() + + // count = min(chunk, fillSize-loopVar) + diff := builder.AllocateInstruction().AsIsub(fillSize, loopVar).Insert(builder).Return() + count := builder.AllocateInstruction().AsSelect(newLoopVarLessThanFillSize, chunk, diff).Insert(builder).Return() + + c.callMemmove(dstAddr, addr, count) + + builder.AllocateInstruction(). + AsBrnz(newLoopVarLessThanFillSize, c.allocateVarLengthValues(1, newLoopVar), loopBlk). + Insert(builder) + + c.insertJumpToBlock(ssa.ValuesNil, followingBlk) + builder.SetCurrentBlock(followingBlk) + + builder.Seal(beforeLoop) + builder.Seal(loopBlk) + builder.Seal(followingBlk) + + case wasm.OpcodeMiscMemoryInit: + index := c.readI32u() + state.pc++ // Skip the memory index which is fixed to zero. + if state.unreachable { + break + } + + copySize := builder. + AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return() + offsetInDataInstance := builder. + AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return() + offsetInMemory := builder. + AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return() + + dataInstPtr := c.dataOrElementInstanceAddr(index, c.offset.DataInstances1stElement) + + // Bounds check. + c.boundsCheckInMemory(c.getMemoryLenValue(false), offsetInMemory, copySize) + c.boundsCheckInDataOrElementInstance(dataInstPtr, offsetInDataInstance, copySize, wazevoapi.ExitCodeMemoryOutOfBounds) + + dataInstBaseAddr := builder.AllocateInstruction().AsLoad(dataInstPtr, 0, ssa.TypeI64).Insert(builder).Return() + srcAddr := builder.AllocateInstruction().AsIadd(dataInstBaseAddr, offsetInDataInstance).Insert(builder).Return() + + memBase := c.getMemoryBaseValue(false) + dstAddr := builder.AllocateInstruction().AsIadd(memBase, offsetInMemory).Insert(builder).Return() + + c.callMemmove(dstAddr, srcAddr, copySize) + + case wasm.OpcodeMiscTableInit: + elemIndex := c.readI32u() + tableIndex := c.readI32u() + if state.unreachable { + break + } + + copySize := builder. + AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return() + offsetInElementInstance := builder. + AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return() + offsetInTable := builder. + AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return() + + elemInstPtr := c.dataOrElementInstanceAddr(elemIndex, c.offset.ElementInstances1stElement) + + // Bounds check. + tableInstancePtr := c.boundsCheckInTable(tableIndex, offsetInTable, copySize) + c.boundsCheckInDataOrElementInstance(elemInstPtr, offsetInElementInstance, copySize, wazevoapi.ExitCodeTableOutOfBounds) + + three := builder.AllocateInstruction().AsIconst64(3).Insert(builder).Return() + // Calculates the destination address in the table. + tableOffsetInBytes := builder.AllocateInstruction().AsIshl(offsetInTable, three).Insert(builder).Return() + tableBaseAddr := c.loadTableBaseAddr(tableInstancePtr) + dstAddr := builder.AllocateInstruction().AsIadd(tableBaseAddr, tableOffsetInBytes).Insert(builder).Return() + + // Calculates the source address in the element instance. + srcOffsetInBytes := builder.AllocateInstruction().AsIshl(offsetInElementInstance, three).Insert(builder).Return() + elemInstBaseAddr := builder.AllocateInstruction().AsLoad(elemInstPtr, 0, ssa.TypeI64).Insert(builder).Return() + srcAddr := builder.AllocateInstruction().AsIadd(elemInstBaseAddr, srcOffsetInBytes).Insert(builder).Return() + + copySizeInBytes := builder.AllocateInstruction().AsIshl(copySize, three).Insert(builder).Return() + c.callMemmove(dstAddr, srcAddr, copySizeInBytes) + + case wasm.OpcodeMiscElemDrop: + index := c.readI32u() + if state.unreachable { + break + } + + c.dropDataOrElementInstance(index, c.offset.ElementInstances1stElement) + + case wasm.OpcodeMiscDataDrop: + index := c.readI32u() + if state.unreachable { + break + } + c.dropDataOrElementInstance(index, c.offset.DataInstances1stElement) + + default: + panic("Unknown MiscOp " + wasm.MiscInstructionName(miscOp)) + } + + case wasm.OpcodeI32ReinterpretF32: + if state.unreachable { + break + } + reinterpret := builder.AllocateInstruction(). + AsBitcast(state.pop(), ssa.TypeI32). + Insert(builder).Return() + state.push(reinterpret) + + case wasm.OpcodeI64ReinterpretF64: + if state.unreachable { + break + } + reinterpret := builder.AllocateInstruction(). + AsBitcast(state.pop(), ssa.TypeI64). + Insert(builder).Return() + state.push(reinterpret) + + case wasm.OpcodeF32ReinterpretI32: + if state.unreachable { + break + } + reinterpret := builder.AllocateInstruction(). + AsBitcast(state.pop(), ssa.TypeF32). + Insert(builder).Return() + state.push(reinterpret) + + case wasm.OpcodeF64ReinterpretI64: + if state.unreachable { + break + } + reinterpret := builder.AllocateInstruction(). + AsBitcast(state.pop(), ssa.TypeF64). + Insert(builder).Return() + state.push(reinterpret) + + case wasm.OpcodeI32DivS, wasm.OpcodeI64DivS: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + result := builder.AllocateInstruction().AsSDiv(x, y, c.execCtxPtrValue).Insert(builder).Return() + state.push(result) + + case wasm.OpcodeI32DivU, wasm.OpcodeI64DivU: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + result := builder.AllocateInstruction().AsUDiv(x, y, c.execCtxPtrValue).Insert(builder).Return() + state.push(result) + + case wasm.OpcodeI32RemS, wasm.OpcodeI64RemS: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + result := builder.AllocateInstruction().AsSRem(x, y, c.execCtxPtrValue).Insert(builder).Return() + state.push(result) + + case wasm.OpcodeI32RemU, wasm.OpcodeI64RemU: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + result := builder.AllocateInstruction().AsURem(x, y, c.execCtxPtrValue).Insert(builder).Return() + state.push(result) + + case wasm.OpcodeI32And, wasm.OpcodeI64And: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + and := builder.AllocateInstruction() + and.AsBand(x, y) + builder.InsertInstruction(and) + value := and.Return() + state.push(value) + case wasm.OpcodeI32Or, wasm.OpcodeI64Or: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + or := builder.AllocateInstruction() + or.AsBor(x, y) + builder.InsertInstruction(or) + value := or.Return() + state.push(value) + case wasm.OpcodeI32Xor, wasm.OpcodeI64Xor: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + xor := builder.AllocateInstruction() + xor.AsBxor(x, y) + builder.InsertInstruction(xor) + value := xor.Return() + state.push(value) + case wasm.OpcodeI32Shl, wasm.OpcodeI64Shl: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + ishl := builder.AllocateInstruction() + ishl.AsIshl(x, y) + builder.InsertInstruction(ishl) + value := ishl.Return() + state.push(value) + case wasm.OpcodeI32ShrU, wasm.OpcodeI64ShrU: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + ishl := builder.AllocateInstruction() + ishl.AsUshr(x, y) + builder.InsertInstruction(ishl) + value := ishl.Return() + state.push(value) + case wasm.OpcodeI32ShrS, wasm.OpcodeI64ShrS: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + ishl := builder.AllocateInstruction() + ishl.AsSshr(x, y) + builder.InsertInstruction(ishl) + value := ishl.Return() + state.push(value) + case wasm.OpcodeI32Rotl, wasm.OpcodeI64Rotl: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + rotl := builder.AllocateInstruction() + rotl.AsRotl(x, y) + builder.InsertInstruction(rotl) + value := rotl.Return() + state.push(value) + case wasm.OpcodeI32Rotr, wasm.OpcodeI64Rotr: + if state.unreachable { + break + } + y, x := state.pop(), state.pop() + rotr := builder.AllocateInstruction() + rotr.AsRotr(x, y) + builder.InsertInstruction(rotr) + value := rotr.Return() + state.push(value) + case wasm.OpcodeI32Clz, wasm.OpcodeI64Clz: + if state.unreachable { + break + } + x := state.pop() + clz := builder.AllocateInstruction() + clz.AsClz(x) + builder.InsertInstruction(clz) + value := clz.Return() + state.push(value) + case wasm.OpcodeI32Ctz, wasm.OpcodeI64Ctz: + if state.unreachable { + break + } + x := state.pop() + ctz := builder.AllocateInstruction() + ctz.AsCtz(x) + builder.InsertInstruction(ctz) + value := ctz.Return() + state.push(value) + case wasm.OpcodeI32Popcnt, wasm.OpcodeI64Popcnt: + if state.unreachable { + break + } + x := state.pop() + popcnt := builder.AllocateInstruction() + popcnt.AsPopcnt(x) + builder.InsertInstruction(popcnt) + value := popcnt.Return() + state.push(value) + + case wasm.OpcodeI32WrapI64: + if state.unreachable { + break + } + x := state.pop() + wrap := builder.AllocateInstruction().AsIreduce(x, ssa.TypeI32).Insert(builder).Return() + state.push(wrap) + case wasm.OpcodeGlobalGet: + index := c.readI32u() + if state.unreachable { + break + } + v := c.getWasmGlobalValue(index, false) + state.push(v) + case wasm.OpcodeGlobalSet: + index := c.readI32u() + if state.unreachable { + break + } + v := state.pop() + c.setWasmGlobalValue(index, v) + case wasm.OpcodeLocalGet: + index := c.readI32u() + if state.unreachable { + break + } + variable := c.localVariable(index) + state.push(builder.MustFindValue(variable)) + + case wasm.OpcodeLocalSet: + index := c.readI32u() + if state.unreachable { + break + } + variable := c.localVariable(index) + newValue := state.pop() + builder.DefineVariableInCurrentBB(variable, newValue) + + case wasm.OpcodeLocalTee: + index := c.readI32u() + if state.unreachable { + break + } + variable := c.localVariable(index) + newValue := state.peek() + builder.DefineVariableInCurrentBB(variable, newValue) + + case wasm.OpcodeSelect, wasm.OpcodeTypedSelect: + if op == wasm.OpcodeTypedSelect { + state.pc += 2 // ignores the type which is only needed during validation. + } + + if state.unreachable { + break + } + + cond := state.pop() + v2 := state.pop() + v1 := state.pop() + + sl := builder.AllocateInstruction(). + AsSelect(cond, v1, v2). + Insert(builder). + Return() + state.push(sl) + + case wasm.OpcodeMemorySize: + state.pc++ // skips the memory index. + if state.unreachable { + break + } + + var memSizeInBytes ssa.Value + if c.offset.LocalMemoryBegin < 0 { + memInstPtr := builder.AllocateInstruction(). + AsLoad(c.moduleCtxPtrValue, c.offset.ImportedMemoryBegin.U32(), ssa.TypeI64). + Insert(builder). + Return() + + memSizeInBytes = builder.AllocateInstruction(). + AsLoad(memInstPtr, memoryInstanceBufSizeOffset, ssa.TypeI32). + Insert(builder). + Return() + } else { + memSizeInBytes = builder.AllocateInstruction(). + AsLoad(c.moduleCtxPtrValue, c.offset.LocalMemoryLen().U32(), ssa.TypeI32). + Insert(builder). + Return() + } + + amount := builder.AllocateInstruction() + amount.AsIconst32(uint32(wasm.MemoryPageSizeInBits)) + builder.InsertInstruction(amount) + memSize := builder.AllocateInstruction(). + AsUshr(memSizeInBytes, amount.Return()). + Insert(builder). + Return() + state.push(memSize) + + case wasm.OpcodeMemoryGrow: + state.pc++ // skips the memory index. + if state.unreachable { + break + } + + c.storeCallerModuleContext() + + pages := state.pop() + memoryGrowPtr := builder.AllocateInstruction(). + AsLoad(c.execCtxPtrValue, + wazevoapi.ExecutionContextOffsetMemoryGrowTrampolineAddress.U32(), + ssa.TypeI64, + ).Insert(builder).Return() + + args := c.allocateVarLengthValues(2, c.execCtxPtrValue, pages) + callGrowRet := builder. + AllocateInstruction(). + AsCallIndirect(memoryGrowPtr, &c.memoryGrowSig, args). + Insert(builder).Return() + state.push(callGrowRet) + + // After the memory grow, reload the cached memory base and len. + c.reloadMemoryBaseLen() + + case wasm.OpcodeI32Store, + wasm.OpcodeI64Store, + wasm.OpcodeF32Store, + wasm.OpcodeF64Store, + wasm.OpcodeI32Store8, + wasm.OpcodeI32Store16, + wasm.OpcodeI64Store8, + wasm.OpcodeI64Store16, + wasm.OpcodeI64Store32: + + _, offset := c.readMemArg() + if state.unreachable { + break + } + var opSize uint64 + var opcode ssa.Opcode + switch op { + case wasm.OpcodeI32Store, wasm.OpcodeF32Store: + opcode = ssa.OpcodeStore + opSize = 4 + case wasm.OpcodeI64Store, wasm.OpcodeF64Store: + opcode = ssa.OpcodeStore + opSize = 8 + case wasm.OpcodeI32Store8, wasm.OpcodeI64Store8: + opcode = ssa.OpcodeIstore8 + opSize = 1 + case wasm.OpcodeI32Store16, wasm.OpcodeI64Store16: + opcode = ssa.OpcodeIstore16 + opSize = 2 + case wasm.OpcodeI64Store32: + opcode = ssa.OpcodeIstore32 + opSize = 4 + default: + panic("BUG") + } + + value := state.pop() + baseAddr := state.pop() + addr := c.memOpSetup(baseAddr, uint64(offset), opSize) + builder.AllocateInstruction(). + AsStore(opcode, value, addr, offset). + Insert(builder) + + case wasm.OpcodeI32Load, + wasm.OpcodeI64Load, + wasm.OpcodeF32Load, + wasm.OpcodeF64Load, + wasm.OpcodeI32Load8S, + wasm.OpcodeI32Load8U, + wasm.OpcodeI32Load16S, + wasm.OpcodeI32Load16U, + wasm.OpcodeI64Load8S, + wasm.OpcodeI64Load8U, + wasm.OpcodeI64Load16S, + wasm.OpcodeI64Load16U, + wasm.OpcodeI64Load32S, + wasm.OpcodeI64Load32U: + _, offset := c.readMemArg() + if state.unreachable { + break + } + + var opSize uint64 + switch op { + case wasm.OpcodeI32Load, wasm.OpcodeF32Load: + opSize = 4 + case wasm.OpcodeI64Load, wasm.OpcodeF64Load: + opSize = 8 + case wasm.OpcodeI32Load8S, wasm.OpcodeI32Load8U: + opSize = 1 + case wasm.OpcodeI32Load16S, wasm.OpcodeI32Load16U: + opSize = 2 + case wasm.OpcodeI64Load8S, wasm.OpcodeI64Load8U: + opSize = 1 + case wasm.OpcodeI64Load16S, wasm.OpcodeI64Load16U: + opSize = 2 + case wasm.OpcodeI64Load32S, wasm.OpcodeI64Load32U: + opSize = 4 + default: + panic("BUG") + } + + baseAddr := state.pop() + addr := c.memOpSetup(baseAddr, uint64(offset), opSize) + load := builder.AllocateInstruction() + switch op { + case wasm.OpcodeI32Load: + load.AsLoad(addr, offset, ssa.TypeI32) + case wasm.OpcodeI64Load: + load.AsLoad(addr, offset, ssa.TypeI64) + case wasm.OpcodeF32Load: + load.AsLoad(addr, offset, ssa.TypeF32) + case wasm.OpcodeF64Load: + load.AsLoad(addr, offset, ssa.TypeF64) + case wasm.OpcodeI32Load8S: + load.AsExtLoad(ssa.OpcodeSload8, addr, offset, false) + case wasm.OpcodeI32Load8U: + load.AsExtLoad(ssa.OpcodeUload8, addr, offset, false) + case wasm.OpcodeI32Load16S: + load.AsExtLoad(ssa.OpcodeSload16, addr, offset, false) + case wasm.OpcodeI32Load16U: + load.AsExtLoad(ssa.OpcodeUload16, addr, offset, false) + case wasm.OpcodeI64Load8S: + load.AsExtLoad(ssa.OpcodeSload8, addr, offset, true) + case wasm.OpcodeI64Load8U: + load.AsExtLoad(ssa.OpcodeUload8, addr, offset, true) + case wasm.OpcodeI64Load16S: + load.AsExtLoad(ssa.OpcodeSload16, addr, offset, true) + case wasm.OpcodeI64Load16U: + load.AsExtLoad(ssa.OpcodeUload16, addr, offset, true) + case wasm.OpcodeI64Load32S: + load.AsExtLoad(ssa.OpcodeSload32, addr, offset, true) + case wasm.OpcodeI64Load32U: + load.AsExtLoad(ssa.OpcodeUload32, addr, offset, true) + default: + panic("BUG") + } + builder.InsertInstruction(load) + state.push(load.Return()) + case wasm.OpcodeBlock: + // Note: we do not need to create a BB for this as that would always have only one predecessor + // which is the current BB, and therefore it's always ok to merge them in any way. + + bt := c.readBlockType() + + if state.unreachable { + state.unreachableDepth++ + break + } + + followingBlk := builder.AllocateBasicBlock() + c.addBlockParamsFromWasmTypes(bt.Results, followingBlk) + + state.ctrlPush(controlFrame{ + kind: controlFrameKindBlock, + originalStackLenWithoutParam: len(state.values) - len(bt.Params), + followingBlock: followingBlk, + blockType: bt, + }) + case wasm.OpcodeLoop: + bt := c.readBlockType() + + if state.unreachable { + state.unreachableDepth++ + break + } + + loopHeader, afterLoopBlock := builder.AllocateBasicBlock(), builder.AllocateBasicBlock() + c.addBlockParamsFromWasmTypes(bt.Params, loopHeader) + c.addBlockParamsFromWasmTypes(bt.Results, afterLoopBlock) + + originalLen := len(state.values) - len(bt.Params) + state.ctrlPush(controlFrame{ + originalStackLenWithoutParam: originalLen, + kind: controlFrameKindLoop, + blk: loopHeader, + followingBlock: afterLoopBlock, + blockType: bt, + }) + + args := c.allocateVarLengthValues(len(bt.Params), state.values[originalLen:]...) + + // Insert the jump to the header of loop. + br := builder.AllocateInstruction() + br.AsJump(args, loopHeader) + builder.InsertInstruction(br) + + c.switchTo(originalLen, loopHeader) + + if c.ensureTermination { + checkModuleExitCodePtr := builder.AllocateInstruction(). + AsLoad(c.execCtxPtrValue, + wazevoapi.ExecutionContextOffsetCheckModuleExitCodeTrampolineAddress.U32(), + ssa.TypeI64, + ).Insert(builder).Return() + + args := c.allocateVarLengthValues(1, c.execCtxPtrValue) + builder.AllocateInstruction(). + AsCallIndirect(checkModuleExitCodePtr, &c.checkModuleExitCodeSig, args). + Insert(builder) + } + case wasm.OpcodeIf: + bt := c.readBlockType() + + if state.unreachable { + state.unreachableDepth++ + break + } + + v := state.pop() + thenBlk, elseBlk, followingBlk := builder.AllocateBasicBlock(), builder.AllocateBasicBlock(), builder.AllocateBasicBlock() + + // We do not make the Wasm-level block parameters as SSA-level block params for if-else blocks + // since they won't be PHI and the definition is unique. + + // On the other hand, the following block after if-else-end will likely have + // multiple definitions (one in Then and another in Else blocks). + c.addBlockParamsFromWasmTypes(bt.Results, followingBlk) + + args := c.allocateVarLengthValues(len(bt.Params), state.values[len(state.values)-len(bt.Params):]...) + + // Insert the conditional jump to the Else block. + brz := builder.AllocateInstruction() + brz.AsBrz(v, ssa.ValuesNil, elseBlk) + builder.InsertInstruction(brz) + + // Then, insert the jump to the Then block. + br := builder.AllocateInstruction() + br.AsJump(ssa.ValuesNil, thenBlk) + builder.InsertInstruction(br) + + state.ctrlPush(controlFrame{ + kind: controlFrameKindIfWithoutElse, + originalStackLenWithoutParam: len(state.values) - len(bt.Params), + blk: elseBlk, + followingBlock: followingBlk, + blockType: bt, + clonedArgs: args, + }) + + builder.SetCurrentBlock(thenBlk) + + // Then and Else (if exists) have only one predecessor. + builder.Seal(thenBlk) + builder.Seal(elseBlk) + case wasm.OpcodeElse: + ifctrl := state.ctrlPeekAt(0) + if unreachable := state.unreachable; unreachable && state.unreachableDepth > 0 { + // If it is currently in unreachable and is a nested if, + // we just remove the entire else block. + break + } + + ifctrl.kind = controlFrameKindIfWithElse + if !state.unreachable { + // If this Then block is currently reachable, we have to insert the branching to the following BB. + followingBlk := ifctrl.followingBlock // == the BB after if-then-else. + args := c.nPeekDup(len(ifctrl.blockType.Results)) + c.insertJumpToBlock(args, followingBlk) + } else { + state.unreachable = false + } + + // Reset the stack so that we can correctly handle the else block. + state.values = state.values[:ifctrl.originalStackLenWithoutParam] + elseBlk := ifctrl.blk + for _, arg := range ifctrl.clonedArgs.View() { + state.push(arg) + } + + builder.SetCurrentBlock(elseBlk) + + case wasm.OpcodeEnd: + if state.unreachableDepth > 0 { + state.unreachableDepth-- + break + } + + ctrl := state.ctrlPop() + followingBlk := ctrl.followingBlock + + unreachable := state.unreachable + if !unreachable { + // Top n-th args will be used as a result of the current control frame. + args := c.nPeekDup(len(ctrl.blockType.Results)) + + // Insert the unconditional branch to the target. + c.insertJumpToBlock(args, followingBlk) + } else { // recover from the unreachable state. + state.unreachable = false + } + + switch ctrl.kind { + case controlFrameKindFunction: + break // This is the very end of function. + case controlFrameKindLoop: + // Loop header block can be reached from any br/br_table contained in the loop, + // so now that we've reached End of it, we can seal it. + builder.Seal(ctrl.blk) + case controlFrameKindIfWithoutElse: + // If this is the end of Then block, we have to emit the empty Else block. + elseBlk := ctrl.blk + builder.SetCurrentBlock(elseBlk) + c.insertJumpToBlock(ctrl.clonedArgs, followingBlk) + } + + builder.Seal(followingBlk) + + // Ready to start translating the following block. + c.switchTo(ctrl.originalStackLenWithoutParam, followingBlk) + + case wasm.OpcodeBr: + labelIndex := c.readI32u() + if state.unreachable { + break + } + + targetBlk, argNum := state.brTargetArgNumFor(labelIndex) + args := c.nPeekDup(argNum) + c.insertJumpToBlock(args, targetBlk) + + state.unreachable = true + + case wasm.OpcodeBrIf: + labelIndex := c.readI32u() + if state.unreachable { + break + } + + v := state.pop() + + targetBlk, argNum := state.brTargetArgNumFor(labelIndex) + args := c.nPeekDup(argNum) + var sealTargetBlk bool + if c.needListener && targetBlk.ReturnBlock() { // In this case, we have to call the listener before returning. + // Save the currently active block. + current := builder.CurrentBlock() + + // Allocate the trampoline block to the return where we call the listener. + targetBlk = builder.AllocateBasicBlock() + builder.SetCurrentBlock(targetBlk) + sealTargetBlk = true + + c.callListenerAfter() + + instr := builder.AllocateInstruction() + instr.AsReturn(args) + builder.InsertInstruction(instr) + + args = ssa.ValuesNil + + // Revert the current block. + builder.SetCurrentBlock(current) + } + + // Insert the conditional jump to the target block. + brnz := builder.AllocateInstruction() + brnz.AsBrnz(v, args, targetBlk) + builder.InsertInstruction(brnz) + + if sealTargetBlk { + builder.Seal(targetBlk) + } + + // Insert the unconditional jump to the Else block which corresponds to after br_if. + elseBlk := builder.AllocateBasicBlock() + c.insertJumpToBlock(ssa.ValuesNil, elseBlk) + + // Now start translating the instructions after br_if. + builder.Seal(elseBlk) // Else of br_if has the current block as the only one successor. + builder.SetCurrentBlock(elseBlk) + + case wasm.OpcodeBrTable: + labels := state.tmpForBrTable[:0] + labelCount := c.readI32u() + for i := 0; i < int(labelCount); i++ { + labels = append(labels, c.readI32u()) + } + labels = append(labels, c.readI32u()) // default label. + if state.unreachable { + break + } + + index := state.pop() + if labelCount == 0 { // If this br_table is empty, we can just emit the unconditional jump. + targetBlk, argNum := state.brTargetArgNumFor(labels[0]) + args := c.nPeekDup(argNum) + c.insertJumpToBlock(args, targetBlk) + } else { + c.lowerBrTable(labels, index) + } + state.tmpForBrTable = labels // reuse the temporary slice for next use. + state.unreachable = true + + case wasm.OpcodeNop: + case wasm.OpcodeReturn: + if state.unreachable { + break + } + if c.needListener { + c.callListenerAfter() + } + + c.lowerReturn(builder) + state.unreachable = true + + case wasm.OpcodeUnreachable: + if state.unreachable { + break + } + exit := builder.AllocateInstruction() + exit.AsExitWithCode(c.execCtxPtrValue, wazevoapi.ExitCodeUnreachable) + builder.InsertInstruction(exit) + state.unreachable = true + + case wasm.OpcodeCallIndirect: + typeIndex := c.readI32u() + tableIndex := c.readI32u() + if state.unreachable { + break + } + c.lowerCallIndirect(typeIndex, tableIndex) + + case wasm.OpcodeCall: + fnIndex := c.readI32u() + if state.unreachable { + break + } + c.lowerCall(fnIndex) + + case wasm.OpcodeDrop: + if state.unreachable { + break + } + _ = state.pop() + case wasm.OpcodeF64ConvertI32S, wasm.OpcodeF64ConvertI64S, wasm.OpcodeF64ConvertI32U, wasm.OpcodeF64ConvertI64U: + if state.unreachable { + break + } + result := builder.AllocateInstruction().AsFcvtFromInt( + state.pop(), + op == wasm.OpcodeF64ConvertI32S || op == wasm.OpcodeF64ConvertI64S, + true, + ).Insert(builder).Return() + state.push(result) + case wasm.OpcodeF32ConvertI32S, wasm.OpcodeF32ConvertI64S, wasm.OpcodeF32ConvertI32U, wasm.OpcodeF32ConvertI64U: + if state.unreachable { + break + } + result := builder.AllocateInstruction().AsFcvtFromInt( + state.pop(), + op == wasm.OpcodeF32ConvertI32S || op == wasm.OpcodeF32ConvertI64S, + false, + ).Insert(builder).Return() + state.push(result) + case wasm.OpcodeF32DemoteF64: + if state.unreachable { + break + } + cvt := builder.AllocateInstruction() + cvt.AsFdemote(state.pop()) + builder.InsertInstruction(cvt) + state.push(cvt.Return()) + case wasm.OpcodeF64PromoteF32: + if state.unreachable { + break + } + cvt := builder.AllocateInstruction() + cvt.AsFpromote(state.pop()) + builder.InsertInstruction(cvt) + state.push(cvt.Return()) + + case wasm.OpcodeVecPrefix: + state.pc++ + vecOp := c.wasmFunctionBody[state.pc] + switch vecOp { + case wasm.OpcodeVecV128Const: + state.pc++ + lo := binary.LittleEndian.Uint64(c.wasmFunctionBody[state.pc:]) + state.pc += 8 + hi := binary.LittleEndian.Uint64(c.wasmFunctionBody[state.pc:]) + state.pc += 7 + if state.unreachable { + break + } + ret := builder.AllocateInstruction().AsVconst(lo, hi).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecV128Load: + _, offset := c.readMemArg() + if state.unreachable { + break + } + baseAddr := state.pop() + addr := c.memOpSetup(baseAddr, uint64(offset), 16) + load := builder.AllocateInstruction() + load.AsLoad(addr, offset, ssa.TypeV128) + builder.InsertInstruction(load) + state.push(load.Return()) + case wasm.OpcodeVecV128Load8Lane, wasm.OpcodeVecV128Load16Lane, wasm.OpcodeVecV128Load32Lane: + _, offset := c.readMemArg() + state.pc++ + if state.unreachable { + break + } + var lane ssa.VecLane + var loadOp ssa.Opcode + var opSize uint64 + switch vecOp { + case wasm.OpcodeVecV128Load8Lane: + loadOp, lane, opSize = ssa.OpcodeUload8, ssa.VecLaneI8x16, 1 + case wasm.OpcodeVecV128Load16Lane: + loadOp, lane, opSize = ssa.OpcodeUload16, ssa.VecLaneI16x8, 2 + case wasm.OpcodeVecV128Load32Lane: + loadOp, lane, opSize = ssa.OpcodeUload32, ssa.VecLaneI32x4, 4 + } + laneIndex := c.wasmFunctionBody[state.pc] + vector := state.pop() + baseAddr := state.pop() + addr := c.memOpSetup(baseAddr, uint64(offset), opSize) + load := builder.AllocateInstruction(). + AsExtLoad(loadOp, addr, offset, false). + Insert(builder).Return() + ret := builder.AllocateInstruction(). + AsInsertlane(vector, load, laneIndex, lane). + Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecV128Load64Lane: + _, offset := c.readMemArg() + state.pc++ + if state.unreachable { + break + } + laneIndex := c.wasmFunctionBody[state.pc] + vector := state.pop() + baseAddr := state.pop() + addr := c.memOpSetup(baseAddr, uint64(offset), 8) + load := builder.AllocateInstruction(). + AsLoad(addr, offset, ssa.TypeI64). + Insert(builder).Return() + ret := builder.AllocateInstruction(). + AsInsertlane(vector, load, laneIndex, ssa.VecLaneI64x2). + Insert(builder).Return() + state.push(ret) + + case wasm.OpcodeVecV128Load32zero, wasm.OpcodeVecV128Load64zero: + _, offset := c.readMemArg() + if state.unreachable { + break + } + + var scalarType ssa.Type + switch vecOp { + case wasm.OpcodeVecV128Load32zero: + scalarType = ssa.TypeF32 + case wasm.OpcodeVecV128Load64zero: + scalarType = ssa.TypeF64 + } + + baseAddr := state.pop() + addr := c.memOpSetup(baseAddr, uint64(offset), uint64(scalarType.Size())) + + ret := builder.AllocateInstruction(). + AsVZeroExtLoad(addr, offset, scalarType). + Insert(builder).Return() + state.push(ret) + + case wasm.OpcodeVecV128Load8x8u, wasm.OpcodeVecV128Load8x8s, + wasm.OpcodeVecV128Load16x4u, wasm.OpcodeVecV128Load16x4s, + wasm.OpcodeVecV128Load32x2u, wasm.OpcodeVecV128Load32x2s: + _, offset := c.readMemArg() + if state.unreachable { + break + } + var lane ssa.VecLane + var signed bool + switch vecOp { + case wasm.OpcodeVecV128Load8x8s: + signed = true + fallthrough + case wasm.OpcodeVecV128Load8x8u: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecV128Load16x4s: + signed = true + fallthrough + case wasm.OpcodeVecV128Load16x4u: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecV128Load32x2s: + signed = true + fallthrough + case wasm.OpcodeVecV128Load32x2u: + lane = ssa.VecLaneI32x4 + } + baseAddr := state.pop() + addr := c.memOpSetup(baseAddr, uint64(offset), 8) + load := builder.AllocateInstruction(). + AsLoad(addr, offset, ssa.TypeF64). + Insert(builder).Return() + ret := builder.AllocateInstruction(). + AsWiden(load, lane, signed, true). + Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecV128Load8Splat, wasm.OpcodeVecV128Load16Splat, + wasm.OpcodeVecV128Load32Splat, wasm.OpcodeVecV128Load64Splat: + _, offset := c.readMemArg() + if state.unreachable { + break + } + var lane ssa.VecLane + var opSize uint64 + switch vecOp { + case wasm.OpcodeVecV128Load8Splat: + lane, opSize = ssa.VecLaneI8x16, 1 + case wasm.OpcodeVecV128Load16Splat: + lane, opSize = ssa.VecLaneI16x8, 2 + case wasm.OpcodeVecV128Load32Splat: + lane, opSize = ssa.VecLaneI32x4, 4 + case wasm.OpcodeVecV128Load64Splat: + lane, opSize = ssa.VecLaneI64x2, 8 + } + baseAddr := state.pop() + addr := c.memOpSetup(baseAddr, uint64(offset), opSize) + ret := builder.AllocateInstruction(). + AsLoadSplat(addr, offset, lane). + Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecV128Store: + _, offset := c.readMemArg() + if state.unreachable { + break + } + value := state.pop() + baseAddr := state.pop() + addr := c.memOpSetup(baseAddr, uint64(offset), 16) + builder.AllocateInstruction(). + AsStore(ssa.OpcodeStore, value, addr, offset). + Insert(builder) + case wasm.OpcodeVecV128Store8Lane, wasm.OpcodeVecV128Store16Lane, + wasm.OpcodeVecV128Store32Lane, wasm.OpcodeVecV128Store64Lane: + _, offset := c.readMemArg() + state.pc++ + if state.unreachable { + break + } + laneIndex := c.wasmFunctionBody[state.pc] + var storeOp ssa.Opcode + var lane ssa.VecLane + var opSize uint64 + switch vecOp { + case wasm.OpcodeVecV128Store8Lane: + storeOp, lane, opSize = ssa.OpcodeIstore8, ssa.VecLaneI8x16, 1 + case wasm.OpcodeVecV128Store16Lane: + storeOp, lane, opSize = ssa.OpcodeIstore16, ssa.VecLaneI16x8, 2 + case wasm.OpcodeVecV128Store32Lane: + storeOp, lane, opSize = ssa.OpcodeIstore32, ssa.VecLaneI32x4, 4 + case wasm.OpcodeVecV128Store64Lane: + storeOp, lane, opSize = ssa.OpcodeStore, ssa.VecLaneI64x2, 8 + } + vector := state.pop() + baseAddr := state.pop() + addr := c.memOpSetup(baseAddr, uint64(offset), opSize) + value := builder.AllocateInstruction(). + AsExtractlane(vector, laneIndex, lane, false). + Insert(builder).Return() + builder.AllocateInstruction(). + AsStore(storeOp, value, addr, offset). + Insert(builder) + case wasm.OpcodeVecV128Not: + if state.unreachable { + break + } + v1 := state.pop() + ret := builder.AllocateInstruction().AsVbnot(v1).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecV128And: + if state.unreachable { + break + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVband(v1, v2).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecV128AndNot: + if state.unreachable { + break + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVbandnot(v1, v2).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecV128Or: + if state.unreachable { + break + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVbor(v1, v2).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecV128Xor: + if state.unreachable { + break + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVbxor(v1, v2).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecV128Bitselect: + if state.unreachable { + break + } + c := state.pop() + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVbitselect(c, v1, v2).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecV128AnyTrue: + if state.unreachable { + break + } + v1 := state.pop() + ret := builder.AllocateInstruction().AsVanyTrue(v1).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16AllTrue, wasm.OpcodeVecI16x8AllTrue, wasm.OpcodeVecI32x4AllTrue, wasm.OpcodeVecI64x2AllTrue: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16AllTrue: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8AllTrue: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4AllTrue: + lane = ssa.VecLaneI32x4 + case wasm.OpcodeVecI64x2AllTrue: + lane = ssa.VecLaneI64x2 + } + v1 := state.pop() + ret := builder.AllocateInstruction().AsVallTrue(v1, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16BitMask, wasm.OpcodeVecI16x8BitMask, wasm.OpcodeVecI32x4BitMask, wasm.OpcodeVecI64x2BitMask: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16BitMask: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8BitMask: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4BitMask: + lane = ssa.VecLaneI32x4 + case wasm.OpcodeVecI64x2BitMask: + lane = ssa.VecLaneI64x2 + } + v1 := state.pop() + ret := builder.AllocateInstruction().AsVhighBits(v1, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16Abs, wasm.OpcodeVecI16x8Abs, wasm.OpcodeVecI32x4Abs, wasm.OpcodeVecI64x2Abs: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16Abs: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8Abs: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4Abs: + lane = ssa.VecLaneI32x4 + case wasm.OpcodeVecI64x2Abs: + lane = ssa.VecLaneI64x2 + } + v1 := state.pop() + ret := builder.AllocateInstruction().AsVIabs(v1, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16Neg, wasm.OpcodeVecI16x8Neg, wasm.OpcodeVecI32x4Neg, wasm.OpcodeVecI64x2Neg: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16Neg: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8Neg: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4Neg: + lane = ssa.VecLaneI32x4 + case wasm.OpcodeVecI64x2Neg: + lane = ssa.VecLaneI64x2 + } + v1 := state.pop() + ret := builder.AllocateInstruction().AsVIneg(v1, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16Popcnt: + if state.unreachable { + break + } + lane := ssa.VecLaneI8x16 + v1 := state.pop() + + ret := builder.AllocateInstruction().AsVIpopcnt(v1, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16Add, wasm.OpcodeVecI16x8Add, wasm.OpcodeVecI32x4Add, wasm.OpcodeVecI64x2Add: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16Add: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8Add: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4Add: + lane = ssa.VecLaneI32x4 + case wasm.OpcodeVecI64x2Add: + lane = ssa.VecLaneI64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVIadd(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16AddSatS, wasm.OpcodeVecI16x8AddSatS: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16AddSatS: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8AddSatS: + lane = ssa.VecLaneI16x8 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVSaddSat(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16AddSatU, wasm.OpcodeVecI16x8AddSatU: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16AddSatU: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8AddSatU: + lane = ssa.VecLaneI16x8 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVUaddSat(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16SubSatS, wasm.OpcodeVecI16x8SubSatS: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16SubSatS: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8SubSatS: + lane = ssa.VecLaneI16x8 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVSsubSat(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16SubSatU, wasm.OpcodeVecI16x8SubSatU: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16SubSatU: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8SubSatU: + lane = ssa.VecLaneI16x8 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVUsubSat(v1, v2, lane).Insert(builder).Return() + state.push(ret) + + case wasm.OpcodeVecI8x16Sub, wasm.OpcodeVecI16x8Sub, wasm.OpcodeVecI32x4Sub, wasm.OpcodeVecI64x2Sub: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16Sub: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8Sub: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4Sub: + lane = ssa.VecLaneI32x4 + case wasm.OpcodeVecI64x2Sub: + lane = ssa.VecLaneI64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVIsub(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16MinS, wasm.OpcodeVecI16x8MinS, wasm.OpcodeVecI32x4MinS: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16MinS: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8MinS: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4MinS: + lane = ssa.VecLaneI32x4 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVImin(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16MinU, wasm.OpcodeVecI16x8MinU, wasm.OpcodeVecI32x4MinU: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16MinU: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8MinU: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4MinU: + lane = ssa.VecLaneI32x4 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVUmin(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16MaxS, wasm.OpcodeVecI16x8MaxS, wasm.OpcodeVecI32x4MaxS: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16MaxS: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8MaxS: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4MaxS: + lane = ssa.VecLaneI32x4 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVImax(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16MaxU, wasm.OpcodeVecI16x8MaxU, wasm.OpcodeVecI32x4MaxU: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16MaxU: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8MaxU: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4MaxU: + lane = ssa.VecLaneI32x4 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVUmax(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16AvgrU, wasm.OpcodeVecI16x8AvgrU: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16AvgrU: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8AvgrU: + lane = ssa.VecLaneI16x8 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVAvgRound(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI16x8Mul, wasm.OpcodeVecI32x4Mul, wasm.OpcodeVecI64x2Mul: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI16x8Mul: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4Mul: + lane = ssa.VecLaneI32x4 + case wasm.OpcodeVecI64x2Mul: + lane = ssa.VecLaneI64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVImul(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI16x8Q15mulrSatS: + if state.unreachable { + break + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsSqmulRoundSat(v1, v2, ssa.VecLaneI16x8).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16Eq, wasm.OpcodeVecI16x8Eq, wasm.OpcodeVecI32x4Eq, wasm.OpcodeVecI64x2Eq: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16Eq: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8Eq: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4Eq: + lane = ssa.VecLaneI32x4 + case wasm.OpcodeVecI64x2Eq: + lane = ssa.VecLaneI64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsVIcmp(v1, v2, ssa.IntegerCmpCondEqual, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16Ne, wasm.OpcodeVecI16x8Ne, wasm.OpcodeVecI32x4Ne, wasm.OpcodeVecI64x2Ne: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16Ne: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8Ne: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4Ne: + lane = ssa.VecLaneI32x4 + case wasm.OpcodeVecI64x2Ne: + lane = ssa.VecLaneI64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsVIcmp(v1, v2, ssa.IntegerCmpCondNotEqual, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16LtS, wasm.OpcodeVecI16x8LtS, wasm.OpcodeVecI32x4LtS, wasm.OpcodeVecI64x2LtS: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16LtS: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8LtS: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4LtS: + lane = ssa.VecLaneI32x4 + case wasm.OpcodeVecI64x2LtS: + lane = ssa.VecLaneI64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsVIcmp(v1, v2, ssa.IntegerCmpCondSignedLessThan, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16LtU, wasm.OpcodeVecI16x8LtU, wasm.OpcodeVecI32x4LtU: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16LtU: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8LtU: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4LtU: + lane = ssa.VecLaneI32x4 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsVIcmp(v1, v2, ssa.IntegerCmpCondUnsignedLessThan, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16LeS, wasm.OpcodeVecI16x8LeS, wasm.OpcodeVecI32x4LeS, wasm.OpcodeVecI64x2LeS: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16LeS: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8LeS: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4LeS: + lane = ssa.VecLaneI32x4 + case wasm.OpcodeVecI64x2LeS: + lane = ssa.VecLaneI64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsVIcmp(v1, v2, ssa.IntegerCmpCondSignedLessThanOrEqual, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16LeU, wasm.OpcodeVecI16x8LeU, wasm.OpcodeVecI32x4LeU: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16LeU: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8LeU: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4LeU: + lane = ssa.VecLaneI32x4 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsVIcmp(v1, v2, ssa.IntegerCmpCondUnsignedLessThanOrEqual, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16GtS, wasm.OpcodeVecI16x8GtS, wasm.OpcodeVecI32x4GtS, wasm.OpcodeVecI64x2GtS: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16GtS: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8GtS: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4GtS: + lane = ssa.VecLaneI32x4 + case wasm.OpcodeVecI64x2GtS: + lane = ssa.VecLaneI64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsVIcmp(v1, v2, ssa.IntegerCmpCondSignedGreaterThan, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16GtU, wasm.OpcodeVecI16x8GtU, wasm.OpcodeVecI32x4GtU: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16GtU: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8GtU: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4GtU: + lane = ssa.VecLaneI32x4 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsVIcmp(v1, v2, ssa.IntegerCmpCondUnsignedGreaterThan, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16GeS, wasm.OpcodeVecI16x8GeS, wasm.OpcodeVecI32x4GeS, wasm.OpcodeVecI64x2GeS: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16GeS: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8GeS: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4GeS: + lane = ssa.VecLaneI32x4 + case wasm.OpcodeVecI64x2GeS: + lane = ssa.VecLaneI64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsVIcmp(v1, v2, ssa.IntegerCmpCondSignedGreaterThanOrEqual, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16GeU, wasm.OpcodeVecI16x8GeU, wasm.OpcodeVecI32x4GeU: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16GeU: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8GeU: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4GeU: + lane = ssa.VecLaneI32x4 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsVIcmp(v1, v2, ssa.IntegerCmpCondUnsignedGreaterThanOrEqual, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4Max, wasm.OpcodeVecF64x2Max: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Max: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Max: + lane = ssa.VecLaneF64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVFmax(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4Abs, wasm.OpcodeVecF64x2Abs: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Abs: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Abs: + lane = ssa.VecLaneF64x2 + } + v1 := state.pop() + ret := builder.AllocateInstruction().AsVFabs(v1, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4Min, wasm.OpcodeVecF64x2Min: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Min: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Min: + lane = ssa.VecLaneF64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVFmin(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4Neg, wasm.OpcodeVecF64x2Neg: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Neg: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Neg: + lane = ssa.VecLaneF64x2 + } + v1 := state.pop() + ret := builder.AllocateInstruction().AsVFneg(v1, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4Sqrt, wasm.OpcodeVecF64x2Sqrt: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Sqrt: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Sqrt: + lane = ssa.VecLaneF64x2 + } + v1 := state.pop() + ret := builder.AllocateInstruction().AsVSqrt(v1, lane).Insert(builder).Return() + state.push(ret) + + case wasm.OpcodeVecF32x4Add, wasm.OpcodeVecF64x2Add: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Add: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Add: + lane = ssa.VecLaneF64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVFadd(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4Sub, wasm.OpcodeVecF64x2Sub: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Sub: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Sub: + lane = ssa.VecLaneF64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVFsub(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4Mul, wasm.OpcodeVecF64x2Mul: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Mul: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Mul: + lane = ssa.VecLaneF64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVFmul(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4Div, wasm.OpcodeVecF64x2Div: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Div: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Div: + lane = ssa.VecLaneF64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVFdiv(v1, v2, lane).Insert(builder).Return() + state.push(ret) + + case wasm.OpcodeVecI16x8ExtaddPairwiseI8x16S, wasm.OpcodeVecI16x8ExtaddPairwiseI8x16U: + if state.unreachable { + break + } + v := state.pop() + signed := vecOp == wasm.OpcodeVecI16x8ExtaddPairwiseI8x16S + ret := builder.AllocateInstruction().AsExtIaddPairwise(v, ssa.VecLaneI8x16, signed).Insert(builder).Return() + state.push(ret) + + case wasm.OpcodeVecI32x4ExtaddPairwiseI16x8S, wasm.OpcodeVecI32x4ExtaddPairwiseI16x8U: + if state.unreachable { + break + } + v := state.pop() + signed := vecOp == wasm.OpcodeVecI32x4ExtaddPairwiseI16x8S + ret := builder.AllocateInstruction().AsExtIaddPairwise(v, ssa.VecLaneI16x8, signed).Insert(builder).Return() + state.push(ret) + + case wasm.OpcodeVecI16x8ExtMulLowI8x16S, wasm.OpcodeVecI16x8ExtMulLowI8x16U: + if state.unreachable { + break + } + v2 := state.pop() + v1 := state.pop() + ret := c.lowerExtMul( + v1, v2, + ssa.VecLaneI8x16, ssa.VecLaneI16x8, + vecOp == wasm.OpcodeVecI16x8ExtMulLowI8x16S, true) + state.push(ret) + + case wasm.OpcodeVecI16x8ExtMulHighI8x16S, wasm.OpcodeVecI16x8ExtMulHighI8x16U: + if state.unreachable { + break + } + v2 := state.pop() + v1 := state.pop() + ret := c.lowerExtMul( + v1, v2, + ssa.VecLaneI8x16, ssa.VecLaneI16x8, + vecOp == wasm.OpcodeVecI16x8ExtMulHighI8x16S, false) + state.push(ret) + + case wasm.OpcodeVecI32x4ExtMulLowI16x8S, wasm.OpcodeVecI32x4ExtMulLowI16x8U: + if state.unreachable { + break + } + v2 := state.pop() + v1 := state.pop() + ret := c.lowerExtMul( + v1, v2, + ssa.VecLaneI16x8, ssa.VecLaneI32x4, + vecOp == wasm.OpcodeVecI32x4ExtMulLowI16x8S, true) + state.push(ret) + + case wasm.OpcodeVecI32x4ExtMulHighI16x8S, wasm.OpcodeVecI32x4ExtMulHighI16x8U: + if state.unreachable { + break + } + v2 := state.pop() + v1 := state.pop() + ret := c.lowerExtMul( + v1, v2, + ssa.VecLaneI16x8, ssa.VecLaneI32x4, + vecOp == wasm.OpcodeVecI32x4ExtMulHighI16x8S, false) + state.push(ret) + case wasm.OpcodeVecI64x2ExtMulLowI32x4S, wasm.OpcodeVecI64x2ExtMulLowI32x4U: + if state.unreachable { + break + } + v2 := state.pop() + v1 := state.pop() + ret := c.lowerExtMul( + v1, v2, + ssa.VecLaneI32x4, ssa.VecLaneI64x2, + vecOp == wasm.OpcodeVecI64x2ExtMulLowI32x4S, true) + state.push(ret) + + case wasm.OpcodeVecI64x2ExtMulHighI32x4S, wasm.OpcodeVecI64x2ExtMulHighI32x4U: + if state.unreachable { + break + } + v2 := state.pop() + v1 := state.pop() + ret := c.lowerExtMul( + v1, v2, + ssa.VecLaneI32x4, ssa.VecLaneI64x2, + vecOp == wasm.OpcodeVecI64x2ExtMulHighI32x4S, false) + state.push(ret) + + case wasm.OpcodeVecI32x4DotI16x8S: + if state.unreachable { + break + } + v2 := state.pop() + v1 := state.pop() + + ret := builder.AllocateInstruction().AsWideningPairwiseDotProductS(v1, v2).Insert(builder).Return() + state.push(ret) + + case wasm.OpcodeVecF32x4Eq, wasm.OpcodeVecF64x2Eq: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Eq: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Eq: + lane = ssa.VecLaneF64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsVFcmp(v1, v2, ssa.FloatCmpCondEqual, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4Ne, wasm.OpcodeVecF64x2Ne: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Ne: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Ne: + lane = ssa.VecLaneF64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsVFcmp(v1, v2, ssa.FloatCmpCondNotEqual, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4Lt, wasm.OpcodeVecF64x2Lt: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Lt: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Lt: + lane = ssa.VecLaneF64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsVFcmp(v1, v2, ssa.FloatCmpCondLessThan, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4Le, wasm.OpcodeVecF64x2Le: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Le: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Le: + lane = ssa.VecLaneF64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsVFcmp(v1, v2, ssa.FloatCmpCondLessThanOrEqual, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4Gt, wasm.OpcodeVecF64x2Gt: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Gt: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Gt: + lane = ssa.VecLaneF64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsVFcmp(v1, v2, ssa.FloatCmpCondGreaterThan, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4Ge, wasm.OpcodeVecF64x2Ge: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Ge: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Ge: + lane = ssa.VecLaneF64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsVFcmp(v1, v2, ssa.FloatCmpCondGreaterThanOrEqual, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4Ceil, wasm.OpcodeVecF64x2Ceil: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Ceil: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Ceil: + lane = ssa.VecLaneF64x2 + } + v1 := state.pop() + ret := builder.AllocateInstruction().AsVCeil(v1, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4Floor, wasm.OpcodeVecF64x2Floor: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Floor: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Floor: + lane = ssa.VecLaneF64x2 + } + v1 := state.pop() + ret := builder.AllocateInstruction().AsVFloor(v1, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4Trunc, wasm.OpcodeVecF64x2Trunc: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Trunc: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Trunc: + lane = ssa.VecLaneF64x2 + } + v1 := state.pop() + ret := builder.AllocateInstruction().AsVTrunc(v1, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4Nearest, wasm.OpcodeVecF64x2Nearest: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Nearest: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Nearest: + lane = ssa.VecLaneF64x2 + } + v1 := state.pop() + ret := builder.AllocateInstruction().AsVNearest(v1, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4Pmin, wasm.OpcodeVecF64x2Pmin: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Pmin: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Pmin: + lane = ssa.VecLaneF64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVMinPseudo(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4Pmax, wasm.OpcodeVecF64x2Pmax: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecF32x4Pmax: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Pmax: + lane = ssa.VecLaneF64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVMaxPseudo(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI32x4TruncSatF32x4S, wasm.OpcodeVecI32x4TruncSatF32x4U: + if state.unreachable { + break + } + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsVFcvtToIntSat(v1, ssa.VecLaneF32x4, vecOp == wasm.OpcodeVecI32x4TruncSatF32x4S).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI32x4TruncSatF64x2SZero, wasm.OpcodeVecI32x4TruncSatF64x2UZero: + if state.unreachable { + break + } + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsVFcvtToIntSat(v1, ssa.VecLaneF64x2, vecOp == wasm.OpcodeVecI32x4TruncSatF64x2SZero).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4ConvertI32x4S, wasm.OpcodeVecF32x4ConvertI32x4U: + if state.unreachable { + break + } + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsVFcvtFromInt(v1, ssa.VecLaneF32x4, vecOp == wasm.OpcodeVecF32x4ConvertI32x4S).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF64x2ConvertLowI32x4S, wasm.OpcodeVecF64x2ConvertLowI32x4U: + if state.unreachable { + break + } + v1 := state.pop() + if runtime.GOARCH == "arm64" { + // TODO: this is weird. fix. + v1 = builder.AllocateInstruction(). + AsWiden(v1, ssa.VecLaneI32x4, vecOp == wasm.OpcodeVecF64x2ConvertLowI32x4S, true).Insert(builder).Return() + } + ret := builder.AllocateInstruction(). + AsVFcvtFromInt(v1, ssa.VecLaneF64x2, vecOp == wasm.OpcodeVecF64x2ConvertLowI32x4S). + Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16NarrowI16x8S, wasm.OpcodeVecI8x16NarrowI16x8U: + if state.unreachable { + break + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsNarrow(v1, v2, ssa.VecLaneI16x8, vecOp == wasm.OpcodeVecI8x16NarrowI16x8S). + Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI16x8NarrowI32x4S, wasm.OpcodeVecI16x8NarrowI32x4U: + if state.unreachable { + break + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsNarrow(v1, v2, ssa.VecLaneI32x4, vecOp == wasm.OpcodeVecI16x8NarrowI32x4S). + Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI16x8ExtendLowI8x16S, wasm.OpcodeVecI16x8ExtendLowI8x16U: + if state.unreachable { + break + } + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsWiden(v1, ssa.VecLaneI8x16, vecOp == wasm.OpcodeVecI16x8ExtendLowI8x16S, true). + Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI16x8ExtendHighI8x16S, wasm.OpcodeVecI16x8ExtendHighI8x16U: + if state.unreachable { + break + } + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsWiden(v1, ssa.VecLaneI8x16, vecOp == wasm.OpcodeVecI16x8ExtendHighI8x16S, false). + Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI32x4ExtendLowI16x8S, wasm.OpcodeVecI32x4ExtendLowI16x8U: + if state.unreachable { + break + } + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsWiden(v1, ssa.VecLaneI16x8, vecOp == wasm.OpcodeVecI32x4ExtendLowI16x8S, true). + Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI32x4ExtendHighI16x8S, wasm.OpcodeVecI32x4ExtendHighI16x8U: + if state.unreachable { + break + } + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsWiden(v1, ssa.VecLaneI16x8, vecOp == wasm.OpcodeVecI32x4ExtendHighI16x8S, false). + Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI64x2ExtendLowI32x4S, wasm.OpcodeVecI64x2ExtendLowI32x4U: + if state.unreachable { + break + } + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsWiden(v1, ssa.VecLaneI32x4, vecOp == wasm.OpcodeVecI64x2ExtendLowI32x4S, true). + Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI64x2ExtendHighI32x4S, wasm.OpcodeVecI64x2ExtendHighI32x4U: + if state.unreachable { + break + } + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsWiden(v1, ssa.VecLaneI32x4, vecOp == wasm.OpcodeVecI64x2ExtendHighI32x4S, false). + Insert(builder).Return() + state.push(ret) + + case wasm.OpcodeVecF64x2PromoteLowF32x4Zero: + if state.unreachable { + break + } + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsFvpromoteLow(v1, ssa.VecLaneF32x4). + Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecF32x4DemoteF64x2Zero: + if state.unreachable { + break + } + v1 := state.pop() + ret := builder.AllocateInstruction(). + AsFvdemote(v1, ssa.VecLaneF64x2). + Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16Shl, wasm.OpcodeVecI16x8Shl, wasm.OpcodeVecI32x4Shl, wasm.OpcodeVecI64x2Shl: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16Shl: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8Shl: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4Shl: + lane = ssa.VecLaneI32x4 + case wasm.OpcodeVecI64x2Shl: + lane = ssa.VecLaneI64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVIshl(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16ShrS, wasm.OpcodeVecI16x8ShrS, wasm.OpcodeVecI32x4ShrS, wasm.OpcodeVecI64x2ShrS: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16ShrS: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8ShrS: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4ShrS: + lane = ssa.VecLaneI32x4 + case wasm.OpcodeVecI64x2ShrS: + lane = ssa.VecLaneI64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVSshr(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16ShrU, wasm.OpcodeVecI16x8ShrU, wasm.OpcodeVecI32x4ShrU, wasm.OpcodeVecI64x2ShrU: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16ShrU: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8ShrU: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4ShrU: + lane = ssa.VecLaneI32x4 + case wasm.OpcodeVecI64x2ShrU: + lane = ssa.VecLaneI64x2 + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsVUshr(v1, v2, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecI8x16ExtractLaneS, wasm.OpcodeVecI16x8ExtractLaneS: + state.pc++ + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16ExtractLaneS: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8ExtractLaneS: + lane = ssa.VecLaneI16x8 + } + v1 := state.pop() + index := c.wasmFunctionBody[state.pc] + ext := builder.AllocateInstruction().AsExtractlane(v1, index, lane, true).Insert(builder).Return() + state.push(ext) + case wasm.OpcodeVecI8x16ExtractLaneU, wasm.OpcodeVecI16x8ExtractLaneU, + wasm.OpcodeVecI32x4ExtractLane, wasm.OpcodeVecI64x2ExtractLane, + wasm.OpcodeVecF32x4ExtractLane, wasm.OpcodeVecF64x2ExtractLane: + state.pc++ // Skip the immediate value. + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16ExtractLaneU: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8ExtractLaneU: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4ExtractLane: + lane = ssa.VecLaneI32x4 + case wasm.OpcodeVecI64x2ExtractLane: + lane = ssa.VecLaneI64x2 + case wasm.OpcodeVecF32x4ExtractLane: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2ExtractLane: + lane = ssa.VecLaneF64x2 + } + v1 := state.pop() + index := c.wasmFunctionBody[state.pc] + ext := builder.AllocateInstruction().AsExtractlane(v1, index, lane, false).Insert(builder).Return() + state.push(ext) + case wasm.OpcodeVecI8x16ReplaceLane, wasm.OpcodeVecI16x8ReplaceLane, + wasm.OpcodeVecI32x4ReplaceLane, wasm.OpcodeVecI64x2ReplaceLane, + wasm.OpcodeVecF32x4ReplaceLane, wasm.OpcodeVecF64x2ReplaceLane: + state.pc++ + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16ReplaceLane: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8ReplaceLane: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4ReplaceLane: + lane = ssa.VecLaneI32x4 + case wasm.OpcodeVecI64x2ReplaceLane: + lane = ssa.VecLaneI64x2 + case wasm.OpcodeVecF32x4ReplaceLane: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2ReplaceLane: + lane = ssa.VecLaneF64x2 + } + v2 := state.pop() + v1 := state.pop() + index := c.wasmFunctionBody[state.pc] + ret := builder.AllocateInstruction().AsInsertlane(v1, v2, index, lane).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeVecV128i8x16Shuffle: + state.pc++ + laneIndexes := c.wasmFunctionBody[state.pc : state.pc+16] + state.pc += 15 + if state.unreachable { + break + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsShuffle(v1, v2, laneIndexes).Insert(builder).Return() + state.push(ret) + + case wasm.OpcodeVecI8x16Swizzle: + if state.unreachable { + break + } + v2 := state.pop() + v1 := state.pop() + ret := builder.AllocateInstruction().AsSwizzle(v1, v2, ssa.VecLaneI8x16).Insert(builder).Return() + state.push(ret) + + case wasm.OpcodeVecI8x16Splat, + wasm.OpcodeVecI16x8Splat, + wasm.OpcodeVecI32x4Splat, + wasm.OpcodeVecI64x2Splat, + wasm.OpcodeVecF32x4Splat, + wasm.OpcodeVecF64x2Splat: + if state.unreachable { + break + } + var lane ssa.VecLane + switch vecOp { + case wasm.OpcodeVecI8x16Splat: + lane = ssa.VecLaneI8x16 + case wasm.OpcodeVecI16x8Splat: + lane = ssa.VecLaneI16x8 + case wasm.OpcodeVecI32x4Splat: + lane = ssa.VecLaneI32x4 + case wasm.OpcodeVecI64x2Splat: + lane = ssa.VecLaneI64x2 + case wasm.OpcodeVecF32x4Splat: + lane = ssa.VecLaneF32x4 + case wasm.OpcodeVecF64x2Splat: + lane = ssa.VecLaneF64x2 + } + v1 := state.pop() + ret := builder.AllocateInstruction().AsSplat(v1, lane).Insert(builder).Return() + state.push(ret) + + default: + panic("TODO: unsupported vector instruction: " + wasm.VectorInstructionName(vecOp)) + } + case wasm.OpcodeAtomicPrefix: + state.pc++ + atomicOp := c.wasmFunctionBody[state.pc] + switch atomicOp { + case wasm.OpcodeAtomicMemoryWait32, wasm.OpcodeAtomicMemoryWait64: + _, offset := c.readMemArg() + if state.unreachable { + break + } + + c.storeCallerModuleContext() + + var opSize uint64 + var trampoline wazevoapi.Offset + var sig *ssa.Signature + switch atomicOp { + case wasm.OpcodeAtomicMemoryWait32: + opSize = 4 + trampoline = wazevoapi.ExecutionContextOffsetMemoryWait32TrampolineAddress + sig = &c.memoryWait32Sig + case wasm.OpcodeAtomicMemoryWait64: + opSize = 8 + trampoline = wazevoapi.ExecutionContextOffsetMemoryWait64TrampolineAddress + sig = &c.memoryWait64Sig + } + + timeout := state.pop() + exp := state.pop() + baseAddr := state.pop() + addr := c.atomicMemOpSetup(baseAddr, uint64(offset), opSize) + + memoryWaitPtr := builder.AllocateInstruction(). + AsLoad(c.execCtxPtrValue, + trampoline.U32(), + ssa.TypeI64, + ).Insert(builder).Return() + + args := c.allocateVarLengthValues(4, c.execCtxPtrValue, timeout, exp, addr) + memoryWaitRet := builder.AllocateInstruction(). + AsCallIndirect(memoryWaitPtr, sig, args). + Insert(builder).Return() + state.push(memoryWaitRet) + case wasm.OpcodeAtomicMemoryNotify: + _, offset := c.readMemArg() + if state.unreachable { + break + } + + c.storeCallerModuleContext() + count := state.pop() + baseAddr := state.pop() + addr := c.atomicMemOpSetup(baseAddr, uint64(offset), 4) + + memoryNotifyPtr := builder.AllocateInstruction(). + AsLoad(c.execCtxPtrValue, + wazevoapi.ExecutionContextOffsetMemoryNotifyTrampolineAddress.U32(), + ssa.TypeI64, + ).Insert(builder).Return() + args := c.allocateVarLengthValues(3, c.execCtxPtrValue, count, addr) + memoryNotifyRet := builder.AllocateInstruction(). + AsCallIndirect(memoryNotifyPtr, &c.memoryNotifySig, args). + Insert(builder).Return() + state.push(memoryNotifyRet) + case wasm.OpcodeAtomicI32Load, wasm.OpcodeAtomicI64Load, wasm.OpcodeAtomicI32Load8U, wasm.OpcodeAtomicI32Load16U, wasm.OpcodeAtomicI64Load8U, wasm.OpcodeAtomicI64Load16U, wasm.OpcodeAtomicI64Load32U: + _, offset := c.readMemArg() + if state.unreachable { + break + } + + baseAddr := state.pop() + + var size uint64 + switch atomicOp { + case wasm.OpcodeAtomicI64Load: + size = 8 + case wasm.OpcodeAtomicI32Load, wasm.OpcodeAtomicI64Load32U: + size = 4 + case wasm.OpcodeAtomicI32Load16U, wasm.OpcodeAtomicI64Load16U: + size = 2 + case wasm.OpcodeAtomicI32Load8U, wasm.OpcodeAtomicI64Load8U: + size = 1 + } + + var typ ssa.Type + switch atomicOp { + case wasm.OpcodeAtomicI64Load, wasm.OpcodeAtomicI64Load32U, wasm.OpcodeAtomicI64Load16U, wasm.OpcodeAtomicI64Load8U: + typ = ssa.TypeI64 + case wasm.OpcodeAtomicI32Load, wasm.OpcodeAtomicI32Load16U, wasm.OpcodeAtomicI32Load8U: + typ = ssa.TypeI32 + } + + addr := c.atomicMemOpSetup(baseAddr, uint64(offset), size) + res := builder.AllocateInstruction().AsAtomicLoad(addr, size, typ).Insert(builder).Return() + state.push(res) + case wasm.OpcodeAtomicI32Store, wasm.OpcodeAtomicI64Store, wasm.OpcodeAtomicI32Store8, wasm.OpcodeAtomicI32Store16, wasm.OpcodeAtomicI64Store8, wasm.OpcodeAtomicI64Store16, wasm.OpcodeAtomicI64Store32: + _, offset := c.readMemArg() + if state.unreachable { + break + } + + val := state.pop() + baseAddr := state.pop() + + var size uint64 + switch atomicOp { + case wasm.OpcodeAtomicI64Store: + size = 8 + case wasm.OpcodeAtomicI32Store, wasm.OpcodeAtomicI64Store32: + size = 4 + case wasm.OpcodeAtomicI32Store16, wasm.OpcodeAtomicI64Store16: + size = 2 + case wasm.OpcodeAtomicI32Store8, wasm.OpcodeAtomicI64Store8: + size = 1 + } + + addr := c.atomicMemOpSetup(baseAddr, uint64(offset), size) + builder.AllocateInstruction().AsAtomicStore(addr, val, size).Insert(builder) + case wasm.OpcodeAtomicI32RmwAdd, wasm.OpcodeAtomicI64RmwAdd, wasm.OpcodeAtomicI32Rmw8AddU, wasm.OpcodeAtomicI32Rmw16AddU, wasm.OpcodeAtomicI64Rmw8AddU, wasm.OpcodeAtomicI64Rmw16AddU, wasm.OpcodeAtomicI64Rmw32AddU, + wasm.OpcodeAtomicI32RmwSub, wasm.OpcodeAtomicI64RmwSub, wasm.OpcodeAtomicI32Rmw8SubU, wasm.OpcodeAtomicI32Rmw16SubU, wasm.OpcodeAtomicI64Rmw8SubU, wasm.OpcodeAtomicI64Rmw16SubU, wasm.OpcodeAtomicI64Rmw32SubU, + wasm.OpcodeAtomicI32RmwAnd, wasm.OpcodeAtomicI64RmwAnd, wasm.OpcodeAtomicI32Rmw8AndU, wasm.OpcodeAtomicI32Rmw16AndU, wasm.OpcodeAtomicI64Rmw8AndU, wasm.OpcodeAtomicI64Rmw16AndU, wasm.OpcodeAtomicI64Rmw32AndU, + wasm.OpcodeAtomicI32RmwOr, wasm.OpcodeAtomicI64RmwOr, wasm.OpcodeAtomicI32Rmw8OrU, wasm.OpcodeAtomicI32Rmw16OrU, wasm.OpcodeAtomicI64Rmw8OrU, wasm.OpcodeAtomicI64Rmw16OrU, wasm.OpcodeAtomicI64Rmw32OrU, + wasm.OpcodeAtomicI32RmwXor, wasm.OpcodeAtomicI64RmwXor, wasm.OpcodeAtomicI32Rmw8XorU, wasm.OpcodeAtomicI32Rmw16XorU, wasm.OpcodeAtomicI64Rmw8XorU, wasm.OpcodeAtomicI64Rmw16XorU, wasm.OpcodeAtomicI64Rmw32XorU, + wasm.OpcodeAtomicI32RmwXchg, wasm.OpcodeAtomicI64RmwXchg, wasm.OpcodeAtomicI32Rmw8XchgU, wasm.OpcodeAtomicI32Rmw16XchgU, wasm.OpcodeAtomicI64Rmw8XchgU, wasm.OpcodeAtomicI64Rmw16XchgU, wasm.OpcodeAtomicI64Rmw32XchgU: + _, offset := c.readMemArg() + if state.unreachable { + break + } + + val := state.pop() + baseAddr := state.pop() + + var rmwOp ssa.AtomicRmwOp + var size uint64 + switch atomicOp { + case wasm.OpcodeAtomicI32RmwAdd, wasm.OpcodeAtomicI64RmwAdd, wasm.OpcodeAtomicI32Rmw8AddU, wasm.OpcodeAtomicI32Rmw16AddU, wasm.OpcodeAtomicI64Rmw8AddU, wasm.OpcodeAtomicI64Rmw16AddU, wasm.OpcodeAtomicI64Rmw32AddU: + rmwOp = ssa.AtomicRmwOpAdd + switch atomicOp { + case wasm.OpcodeAtomicI64RmwAdd: + size = 8 + case wasm.OpcodeAtomicI32RmwAdd, wasm.OpcodeAtomicI64Rmw32AddU: + size = 4 + case wasm.OpcodeAtomicI32Rmw16AddU, wasm.OpcodeAtomicI64Rmw16AddU: + size = 2 + case wasm.OpcodeAtomicI32Rmw8AddU, wasm.OpcodeAtomicI64Rmw8AddU: + size = 1 + } + case wasm.OpcodeAtomicI32RmwSub, wasm.OpcodeAtomicI64RmwSub, wasm.OpcodeAtomicI32Rmw8SubU, wasm.OpcodeAtomicI32Rmw16SubU, wasm.OpcodeAtomicI64Rmw8SubU, wasm.OpcodeAtomicI64Rmw16SubU, wasm.OpcodeAtomicI64Rmw32SubU: + rmwOp = ssa.AtomicRmwOpSub + switch atomicOp { + case wasm.OpcodeAtomicI64RmwSub: + size = 8 + case wasm.OpcodeAtomicI32RmwSub, wasm.OpcodeAtomicI64Rmw32SubU: + size = 4 + case wasm.OpcodeAtomicI32Rmw16SubU, wasm.OpcodeAtomicI64Rmw16SubU: + size = 2 + case wasm.OpcodeAtomicI32Rmw8SubU, wasm.OpcodeAtomicI64Rmw8SubU: + size = 1 + } + case wasm.OpcodeAtomicI32RmwAnd, wasm.OpcodeAtomicI64RmwAnd, wasm.OpcodeAtomicI32Rmw8AndU, wasm.OpcodeAtomicI32Rmw16AndU, wasm.OpcodeAtomicI64Rmw8AndU, wasm.OpcodeAtomicI64Rmw16AndU, wasm.OpcodeAtomicI64Rmw32AndU: + rmwOp = ssa.AtomicRmwOpAnd + switch atomicOp { + case wasm.OpcodeAtomicI64RmwAnd: + size = 8 + case wasm.OpcodeAtomicI32RmwAnd, wasm.OpcodeAtomicI64Rmw32AndU: + size = 4 + case wasm.OpcodeAtomicI32Rmw16AndU, wasm.OpcodeAtomicI64Rmw16AndU: + size = 2 + case wasm.OpcodeAtomicI32Rmw8AndU, wasm.OpcodeAtomicI64Rmw8AndU: + size = 1 + } + case wasm.OpcodeAtomicI32RmwOr, wasm.OpcodeAtomicI64RmwOr, wasm.OpcodeAtomicI32Rmw8OrU, wasm.OpcodeAtomicI32Rmw16OrU, wasm.OpcodeAtomicI64Rmw8OrU, wasm.OpcodeAtomicI64Rmw16OrU, wasm.OpcodeAtomicI64Rmw32OrU: + rmwOp = ssa.AtomicRmwOpOr + switch atomicOp { + case wasm.OpcodeAtomicI64RmwOr: + size = 8 + case wasm.OpcodeAtomicI32RmwOr, wasm.OpcodeAtomicI64Rmw32OrU: + size = 4 + case wasm.OpcodeAtomicI32Rmw16OrU, wasm.OpcodeAtomicI64Rmw16OrU: + size = 2 + case wasm.OpcodeAtomicI32Rmw8OrU, wasm.OpcodeAtomicI64Rmw8OrU: + size = 1 + } + case wasm.OpcodeAtomicI32RmwXor, wasm.OpcodeAtomicI64RmwXor, wasm.OpcodeAtomicI32Rmw8XorU, wasm.OpcodeAtomicI32Rmw16XorU, wasm.OpcodeAtomicI64Rmw8XorU, wasm.OpcodeAtomicI64Rmw16XorU, wasm.OpcodeAtomicI64Rmw32XorU: + rmwOp = ssa.AtomicRmwOpXor + switch atomicOp { + case wasm.OpcodeAtomicI64RmwXor: + size = 8 + case wasm.OpcodeAtomicI32RmwXor, wasm.OpcodeAtomicI64Rmw32XorU: + size = 4 + case wasm.OpcodeAtomicI32Rmw16XorU, wasm.OpcodeAtomicI64Rmw16XorU: + size = 2 + case wasm.OpcodeAtomicI32Rmw8XorU, wasm.OpcodeAtomicI64Rmw8XorU: + size = 1 + } + case wasm.OpcodeAtomicI32RmwXchg, wasm.OpcodeAtomicI64RmwXchg, wasm.OpcodeAtomicI32Rmw8XchgU, wasm.OpcodeAtomicI32Rmw16XchgU, wasm.OpcodeAtomicI64Rmw8XchgU, wasm.OpcodeAtomicI64Rmw16XchgU, wasm.OpcodeAtomicI64Rmw32XchgU: + rmwOp = ssa.AtomicRmwOpXchg + switch atomicOp { + case wasm.OpcodeAtomicI64RmwXchg: + size = 8 + case wasm.OpcodeAtomicI32RmwXchg, wasm.OpcodeAtomicI64Rmw32XchgU: + size = 4 + case wasm.OpcodeAtomicI32Rmw16XchgU, wasm.OpcodeAtomicI64Rmw16XchgU: + size = 2 + case wasm.OpcodeAtomicI32Rmw8XchgU, wasm.OpcodeAtomicI64Rmw8XchgU: + size = 1 + } + } + + addr := c.atomicMemOpSetup(baseAddr, uint64(offset), size) + res := builder.AllocateInstruction().AsAtomicRmw(rmwOp, addr, val, size).Insert(builder).Return() + state.push(res) + case wasm.OpcodeAtomicI32RmwCmpxchg, wasm.OpcodeAtomicI64RmwCmpxchg, wasm.OpcodeAtomicI32Rmw8CmpxchgU, wasm.OpcodeAtomicI32Rmw16CmpxchgU, wasm.OpcodeAtomicI64Rmw8CmpxchgU, wasm.OpcodeAtomicI64Rmw16CmpxchgU, wasm.OpcodeAtomicI64Rmw32CmpxchgU: + _, offset := c.readMemArg() + if state.unreachable { + break + } + + repl := state.pop() + exp := state.pop() + baseAddr := state.pop() + + var size uint64 + switch atomicOp { + case wasm.OpcodeAtomicI64RmwCmpxchg: + size = 8 + case wasm.OpcodeAtomicI32RmwCmpxchg, wasm.OpcodeAtomicI64Rmw32CmpxchgU: + size = 4 + case wasm.OpcodeAtomicI32Rmw16CmpxchgU, wasm.OpcodeAtomicI64Rmw16CmpxchgU: + size = 2 + case wasm.OpcodeAtomicI32Rmw8CmpxchgU, wasm.OpcodeAtomicI64Rmw8CmpxchgU: + size = 1 + } + addr := c.atomicMemOpSetup(baseAddr, uint64(offset), size) + res := builder.AllocateInstruction().AsAtomicCas(addr, exp, repl, size).Insert(builder).Return() + state.push(res) + case wasm.OpcodeAtomicFence: + order := c.readByte() + if state.unreachable { + break + } + if c.needMemory { + builder.AllocateInstruction().AsFence(order).Insert(builder) + } + default: + panic("TODO: unsupported atomic instruction: " + wasm.AtomicInstructionName(atomicOp)) + } + case wasm.OpcodeRefFunc: + funcIndex := c.readI32u() + if state.unreachable { + break + } + + c.storeCallerModuleContext() + + funcIndexVal := builder.AllocateInstruction().AsIconst32(funcIndex).Insert(builder).Return() + + refFuncPtr := builder.AllocateInstruction(). + AsLoad(c.execCtxPtrValue, + wazevoapi.ExecutionContextOffsetRefFuncTrampolineAddress.U32(), + ssa.TypeI64, + ).Insert(builder).Return() + + args := c.allocateVarLengthValues(2, c.execCtxPtrValue, funcIndexVal) + refFuncRet := builder. + AllocateInstruction(). + AsCallIndirect(refFuncPtr, &c.refFuncSig, args). + Insert(builder).Return() + state.push(refFuncRet) + + case wasm.OpcodeRefNull: + c.loweringState.pc++ // skips the reference type as we treat both of them as i64(0). + if state.unreachable { + break + } + ret := builder.AllocateInstruction().AsIconst64(0).Insert(builder).Return() + state.push(ret) + case wasm.OpcodeRefIsNull: + if state.unreachable { + break + } + r := state.pop() + zero := builder.AllocateInstruction().AsIconst64(0).Insert(builder) + icmp := builder.AllocateInstruction(). + AsIcmp(r, zero.Return(), ssa.IntegerCmpCondEqual). + Insert(builder). + Return() + state.push(icmp) + case wasm.OpcodeTableSet: + tableIndex := c.readI32u() + if state.unreachable { + break + } + r := state.pop() + targetOffsetInTable := state.pop() + + elementAddr := c.lowerAccessTableWithBoundsCheck(tableIndex, targetOffsetInTable) + builder.AllocateInstruction().AsStore(ssa.OpcodeStore, r, elementAddr, 0).Insert(builder) + + case wasm.OpcodeTableGet: + tableIndex := c.readI32u() + if state.unreachable { + break + } + targetOffsetInTable := state.pop() + elementAddr := c.lowerAccessTableWithBoundsCheck(tableIndex, targetOffsetInTable) + loaded := builder.AllocateInstruction().AsLoad(elementAddr, 0, ssa.TypeI64).Insert(builder).Return() + state.push(loaded) + + case wasm.OpcodeTailCallReturnCallIndirect: + typeIndex := c.readI32u() + tableIndex := c.readI32u() + if state.unreachable { + break + } + _, _ = typeIndex, tableIndex + c.lowerTailCallReturnCallIndirect(typeIndex, tableIndex) + state.unreachable = true + + case wasm.OpcodeTailCallReturnCall: + fnIndex := c.readI32u() + if state.unreachable { + break + } + c.lowerTailCallReturnCall(fnIndex) + state.unreachable = true + + default: + panic("TODO: unsupported in wazevo yet: " + wasm.InstructionName(op)) + } + + if wazevoapi.FrontEndLoggingEnabled { + fmt.Println("--------- Translated " + wasm.InstructionName(op) + " --------") + fmt.Println("state: " + c.loweringState.String()) + fmt.Println(c.formatBuilder()) + fmt.Println("--------------------------") + } + c.loweringState.pc++ +} + +func (c *Compiler) lowerReturn(builder ssa.Builder) { + results := c.nPeekDup(c.results()) + instr := builder.AllocateInstruction() + + instr.AsReturn(results) + builder.InsertInstruction(instr) +} + +func (c *Compiler) lowerExtMul(v1, v2 ssa.Value, from, to ssa.VecLane, signed, low bool) ssa.Value { + // TODO: The sequence `Widen; Widen; VIMul` can be substituted for a single instruction on some ISAs. + builder := c.ssaBuilder + + v1lo := builder.AllocateInstruction().AsWiden(v1, from, signed, low).Insert(builder).Return() + v2lo := builder.AllocateInstruction().AsWiden(v2, from, signed, low).Insert(builder).Return() + + return builder.AllocateInstruction().AsVImul(v1lo, v2lo, to).Insert(builder).Return() +} + +const ( + tableInstanceBaseAddressOffset = 0 + tableInstanceLenOffset = tableInstanceBaseAddressOffset + 8 +) + +func (c *Compiler) lowerAccessTableWithBoundsCheck(tableIndex uint32, elementOffsetInTable ssa.Value) (elementAddress ssa.Value) { + builder := c.ssaBuilder + + // Load the table. + loadTableInstancePtr := builder.AllocateInstruction() + loadTableInstancePtr.AsLoad(c.moduleCtxPtrValue, c.offset.TableOffset(int(tableIndex)).U32(), ssa.TypeI64) + builder.InsertInstruction(loadTableInstancePtr) + tableInstancePtr := loadTableInstancePtr.Return() + + // Load the table's length. + loadTableLen := builder.AllocateInstruction() + loadTableLen.AsLoad(tableInstancePtr, tableInstanceLenOffset, ssa.TypeI32) + builder.InsertInstruction(loadTableLen) + tableLen := loadTableLen.Return() + + // Compare the length and the target, and trap if out of bounds. + checkOOB := builder.AllocateInstruction() + checkOOB.AsIcmp(elementOffsetInTable, tableLen, ssa.IntegerCmpCondUnsignedGreaterThanOrEqual) + builder.InsertInstruction(checkOOB) + exitIfOOB := builder.AllocateInstruction() + exitIfOOB.AsExitIfTrueWithCode(c.execCtxPtrValue, checkOOB.Return(), wazevoapi.ExitCodeTableOutOfBounds) + builder.InsertInstruction(exitIfOOB) + + // Get the base address of wasm.TableInstance.References. + loadTableBaseAddress := builder.AllocateInstruction() + loadTableBaseAddress.AsLoad(tableInstancePtr, tableInstanceBaseAddressOffset, ssa.TypeI64) + builder.InsertInstruction(loadTableBaseAddress) + tableBase := loadTableBaseAddress.Return() + + // Calculate the address of the target function. First we need to multiply targetOffsetInTable by 8 (pointer size). + multiplyBy8 := builder.AllocateInstruction() + three := builder.AllocateInstruction() + three.AsIconst64(3) + builder.InsertInstruction(three) + multiplyBy8.AsIshl(elementOffsetInTable, three.Return()) + builder.InsertInstruction(multiplyBy8) + targetOffsetInTableMultipliedBy8 := multiplyBy8.Return() + + // Then add the multiplied value to the base which results in the address of the target function (*wazevo.functionInstance) + calcElementAddressInTable := builder.AllocateInstruction() + calcElementAddressInTable.AsIadd(tableBase, targetOffsetInTableMultipliedBy8) + builder.InsertInstruction(calcElementAddressInTable) + return calcElementAddressInTable.Return() +} + +func (c *Compiler) prepareCall(fnIndex uint32) (isIndirect bool, sig *ssa.Signature, args ssa.Values, funcRefOrPtrValue uint64) { + builder := c.ssaBuilder + state := c.state() + var typIndex wasm.Index + if fnIndex < c.m.ImportFunctionCount { + // Before transfer the control to the callee, we have to store the current module's moduleContextPtr + // into execContext.callerModuleContextPtr in case when the callee is a Go function. + c.storeCallerModuleContext() + var fi int + for i := range c.m.ImportSection { + imp := &c.m.ImportSection[i] + if imp.Type == wasm.ExternTypeFunc { + if fi == int(fnIndex) { + typIndex = imp.DescFunc + break + } + fi++ + } + } + } else { + typIndex = c.m.FunctionSection[fnIndex-c.m.ImportFunctionCount] + } + typ := &c.m.TypeSection[typIndex] + + argN := len(typ.Params) + tail := len(state.values) - argN + vs := state.values[tail:] + state.values = state.values[:tail] + args = c.allocateVarLengthValues(2+len(vs), c.execCtxPtrValue) + + sig = c.signatures[typ] + if fnIndex >= c.m.ImportFunctionCount { + args = args.Append(builder.VarLengthPool(), c.moduleCtxPtrValue) // This case the callee module is itself. + args = args.Append(builder.VarLengthPool(), vs...) + return false, sig, args, uint64(FunctionIndexToFuncRef(fnIndex)) + } else { + // This case we have to read the address of the imported function from the module context. + moduleCtx := c.moduleCtxPtrValue + loadFuncPtr, loadModuleCtxPtr := builder.AllocateInstruction(), builder.AllocateInstruction() + funcPtrOffset, moduleCtxPtrOffset, _ := c.offset.ImportedFunctionOffset(fnIndex) + loadFuncPtr.AsLoad(moduleCtx, funcPtrOffset.U32(), ssa.TypeI64) + loadModuleCtxPtr.AsLoad(moduleCtx, moduleCtxPtrOffset.U32(), ssa.TypeI64) + builder.InsertInstruction(loadFuncPtr) + builder.InsertInstruction(loadModuleCtxPtr) + + args = args.Append(builder.VarLengthPool(), loadModuleCtxPtr.Return()) + args = args.Append(builder.VarLengthPool(), vs...) + + return true, sig, args, uint64(loadFuncPtr.Return()) + } +} + +func (c *Compiler) lowerCall(fnIndex uint32) { + builder := c.ssaBuilder + state := c.state() + isIndirect, sig, args, funcRefOrPtrValue := c.prepareCall(fnIndex) + + call := builder.AllocateInstruction() + if isIndirect { + call.AsCallIndirect(ssa.Value(funcRefOrPtrValue), sig, args) + } else { + call.AsCall(ssa.FuncRef(funcRefOrPtrValue), sig, args) + } + builder.InsertInstruction(call) + + first, rest := call.Returns() + if first.Valid() { + state.push(first) + } + for _, v := range rest { + state.push(v) + } + + c.reloadAfterCall() +} + +func (c *Compiler) prepareCallIndirect(typeIndex, tableIndex uint32) (ssa.Value, *wasm.FunctionType, ssa.Values) { + builder := c.ssaBuilder + state := c.state() + + elementOffsetInTable := state.pop() + functionInstancePtrAddress := c.lowerAccessTableWithBoundsCheck(tableIndex, elementOffsetInTable) + loadFunctionInstancePtr := builder.AllocateInstruction() + loadFunctionInstancePtr.AsLoad(functionInstancePtrAddress, 0, ssa.TypeI64) + builder.InsertInstruction(loadFunctionInstancePtr) + functionInstancePtr := loadFunctionInstancePtr.Return() + + // Check if it is not the null pointer. + zero := builder.AllocateInstruction() + zero.AsIconst64(0) + builder.InsertInstruction(zero) + checkNull := builder.AllocateInstruction() + checkNull.AsIcmp(functionInstancePtr, zero.Return(), ssa.IntegerCmpCondEqual) + builder.InsertInstruction(checkNull) + exitIfNull := builder.AllocateInstruction() + exitIfNull.AsExitIfTrueWithCode(c.execCtxPtrValue, checkNull.Return(), wazevoapi.ExitCodeIndirectCallNullPointer) + builder.InsertInstruction(exitIfNull) + + // We need to do the type check. First, load the target function instance's typeID. + loadTypeID := builder.AllocateInstruction() + loadTypeID.AsLoad(functionInstancePtr, wazevoapi.FunctionInstanceTypeIDOffset, ssa.TypeI32) + builder.InsertInstruction(loadTypeID) + actualTypeID := loadTypeID.Return() + + // Next, we load the expected TypeID: + loadTypeIDsBegin := builder.AllocateInstruction() + loadTypeIDsBegin.AsLoad(c.moduleCtxPtrValue, c.offset.TypeIDs1stElement.U32(), ssa.TypeI64) + builder.InsertInstruction(loadTypeIDsBegin) + typeIDsBegin := loadTypeIDsBegin.Return() + + loadExpectedTypeID := builder.AllocateInstruction() + loadExpectedTypeID.AsLoad(typeIDsBegin, uint32(typeIndex)*4 /* size of wasm.FunctionTypeID */, ssa.TypeI32) + builder.InsertInstruction(loadExpectedTypeID) + expectedTypeID := loadExpectedTypeID.Return() + + // Check if the type ID matches. + checkTypeID := builder.AllocateInstruction() + checkTypeID.AsIcmp(actualTypeID, expectedTypeID, ssa.IntegerCmpCondNotEqual) + builder.InsertInstruction(checkTypeID) + exitIfNotMatch := builder.AllocateInstruction() + exitIfNotMatch.AsExitIfTrueWithCode(c.execCtxPtrValue, checkTypeID.Return(), wazevoapi.ExitCodeIndirectCallTypeMismatch) + builder.InsertInstruction(exitIfNotMatch) + + // Now ready to call the function. Load the executable and moduleContextOpaquePtr from the function instance. + loadExecutablePtr := builder.AllocateInstruction() + loadExecutablePtr.AsLoad(functionInstancePtr, wazevoapi.FunctionInstanceExecutableOffset, ssa.TypeI64) + builder.InsertInstruction(loadExecutablePtr) + executablePtr := loadExecutablePtr.Return() + loadModuleContextOpaquePtr := builder.AllocateInstruction() + loadModuleContextOpaquePtr.AsLoad(functionInstancePtr, wazevoapi.FunctionInstanceModuleContextOpaquePtrOffset, ssa.TypeI64) + builder.InsertInstruction(loadModuleContextOpaquePtr) + moduleContextOpaquePtr := loadModuleContextOpaquePtr.Return() + + typ := &c.m.TypeSection[typeIndex] + tail := len(state.values) - len(typ.Params) + vs := state.values[tail:] + state.values = state.values[:tail] + args := c.allocateVarLengthValues(2+len(vs), c.execCtxPtrValue, moduleContextOpaquePtr) + args = args.Append(builder.VarLengthPool(), vs...) + + // Before transfer the control to the callee, we have to store the current module's moduleContextPtr + // into execContext.callerModuleContextPtr in case when the callee is a Go function. + c.storeCallerModuleContext() + + return executablePtr, typ, args +} + +func (c *Compiler) lowerCallIndirect(typeIndex, tableIndex uint32) { + builder := c.ssaBuilder + state := c.state() + executablePtr, typ, args := c.prepareCallIndirect(typeIndex, tableIndex) + + call := builder.AllocateInstruction() + call.AsCallIndirect(executablePtr, c.signatures[typ], args) + builder.InsertInstruction(call) + + first, rest := call.Returns() + if first.Valid() { + state.push(first) + } + for _, v := range rest { + state.push(v) + } + + c.reloadAfterCall() +} + +func (c *Compiler) lowerTailCallReturnCall(fnIndex uint32) { + isIndirect, sig, args, funcRefOrPtrValue := c.prepareCall(fnIndex) + builder := c.ssaBuilder + state := c.state() + + call := builder.AllocateInstruction() + if isIndirect { + call.AsTailCallReturnCallIndirect(ssa.Value(funcRefOrPtrValue), sig, args) + } else { + call.AsTailCallReturnCall(ssa.FuncRef(funcRefOrPtrValue), sig, args) + } + builder.InsertInstruction(call) + + // In a proper tail call, the following code is unreachable since execution + // transfers to the callee. However, sometimes the backend might need to fall back to + // a regular call, so we include return handling and let the backend delete it + // when redundant. + // For details, see internal/engine/RATIONALE.md + first, rest := call.Returns() + if first.Valid() { + state.push(first) + } + for _, v := range rest { + state.push(v) + } + + c.reloadAfterCall() + c.lowerReturn(builder) +} + +func (c *Compiler) lowerTailCallReturnCallIndirect(typeIndex, tableIndex uint32) { + builder := c.ssaBuilder + state := c.state() + executablePtr, typ, args := c.prepareCallIndirect(typeIndex, tableIndex) + + call := builder.AllocateInstruction() + call.AsTailCallReturnCallIndirect(executablePtr, c.signatures[typ], args) + builder.InsertInstruction(call) + + // In a proper tail call, the following code is unreachable since execution + // transfers to the callee. However, sometimes the backend might need to fall back to + // a regular call, so we include return handling and let the backend delete it + // when redundant. + // For details, see internal/engine/RATIONALE.md + first, rest := call.Returns() + if first.Valid() { + state.push(first) + } + for _, v := range rest { + state.push(v) + } + + c.reloadAfterCall() + c.lowerReturn(builder) +} + +// memOpSetup inserts the bounds check and calculates the address of the memory operation (loads/stores). +func (c *Compiler) memOpSetup(baseAddr ssa.Value, constOffset, operationSizeInBytes uint64) (address ssa.Value) { + address = ssa.ValueInvalid + builder := c.ssaBuilder + + baseAddrID := baseAddr.ID() + ceil := constOffset + operationSizeInBytes + if known := c.getKnownSafeBound(baseAddrID); known.valid() { + // We reuse the calculated absolute address even if the bound is not known to be safe. + address = known.absoluteAddr + if ceil <= known.bound { + if !address.Valid() { + // This means that, the bound is known to be safe, but the memory base might have changed. + // So, we re-calculate the address. + memBase := c.getMemoryBaseValue(false) + extBaseAddr := builder.AllocateInstruction(). + AsUExtend(baseAddr, 32, 64). + Insert(builder). + Return() + address = builder.AllocateInstruction(). + AsIadd(memBase, extBaseAddr).Insert(builder).Return() + known.absoluteAddr = address // Update the absolute address for the subsequent memory access. + } + return + } + } + + ceilConst := builder.AllocateInstruction() + ceilConst.AsIconst64(ceil) + builder.InsertInstruction(ceilConst) + + // We calculate the offset in 64-bit space. + extBaseAddr := builder.AllocateInstruction(). + AsUExtend(baseAddr, 32, 64). + Insert(builder). + Return() + + // Note: memLen is already zero extended to 64-bit space at the load time. + memLen := c.getMemoryLenValue(false) + + // baseAddrPlusCeil = baseAddr + ceil + baseAddrPlusCeil := builder.AllocateInstruction() + baseAddrPlusCeil.AsIadd(extBaseAddr, ceilConst.Return()) + builder.InsertInstruction(baseAddrPlusCeil) + + // Check for out of bounds memory access: `memLen >= baseAddrPlusCeil`. + cmp := builder.AllocateInstruction() + cmp.AsIcmp(memLen, baseAddrPlusCeil.Return(), ssa.IntegerCmpCondUnsignedLessThan) + builder.InsertInstruction(cmp) + exitIfNZ := builder.AllocateInstruction() + exitIfNZ.AsExitIfTrueWithCode(c.execCtxPtrValue, cmp.Return(), wazevoapi.ExitCodeMemoryOutOfBounds) + builder.InsertInstruction(exitIfNZ) + + // Load the value from memBase + extBaseAddr. + if address == ssa.ValueInvalid { // Reuse the value if the memBase is already calculated at this point. + memBase := c.getMemoryBaseValue(false) + address = builder.AllocateInstruction(). + AsIadd(memBase, extBaseAddr).Insert(builder).Return() + } + + // Record the bound ceil for this baseAddr is known to be safe for the subsequent memory access in the same block. + c.recordKnownSafeBound(baseAddrID, ceil, address) + return +} + +// atomicMemOpSetup inserts the bounds check and calculates the address of the memory operation (loads/stores), including +// the constant offset and performs an alignment check on the final address. +func (c *Compiler) atomicMemOpSetup(baseAddr ssa.Value, constOffset, operationSizeInBytes uint64) (address ssa.Value) { + builder := c.ssaBuilder + + addrWithoutOffset := c.memOpSetup(baseAddr, constOffset, operationSizeInBytes) + var addr ssa.Value + if constOffset == 0 { + addr = addrWithoutOffset + } else { + offset := builder.AllocateInstruction().AsIconst64(constOffset).Insert(builder).Return() + addr = builder.AllocateInstruction().AsIadd(addrWithoutOffset, offset).Insert(builder).Return() + } + + c.memAlignmentCheck(addr, operationSizeInBytes) + + return addr +} + +func (c *Compiler) memAlignmentCheck(addr ssa.Value, operationSizeInBytes uint64) { + if operationSizeInBytes == 1 { + return // No alignment restrictions when accessing a byte + } + var checkBits uint64 + switch operationSizeInBytes { + case 2: + checkBits = 0b1 + case 4: + checkBits = 0b11 + case 8: + checkBits = 0b111 + } + + builder := c.ssaBuilder + + mask := builder.AllocateInstruction().AsIconst64(checkBits).Insert(builder).Return() + masked := builder.AllocateInstruction().AsBand(addr, mask).Insert(builder).Return() + zero := builder.AllocateInstruction().AsIconst64(0).Insert(builder).Return() + cmp := builder.AllocateInstruction().AsIcmp(masked, zero, ssa.IntegerCmpCondNotEqual).Insert(builder).Return() + builder.AllocateInstruction().AsExitIfTrueWithCode(c.execCtxPtrValue, cmp, wazevoapi.ExitCodeUnalignedAtomic).Insert(builder) +} + +func (c *Compiler) callMemmove(dst, src, size ssa.Value) { + args := c.allocateVarLengthValues(3, dst, src, size) + if size.Type() != ssa.TypeI64 { + panic("TODO: memmove size must be i64") + } + + builder := c.ssaBuilder + memmovePtr := builder.AllocateInstruction(). + AsLoad(c.execCtxPtrValue, + wazevoapi.ExecutionContextOffsetMemmoveAddress.U32(), + ssa.TypeI64, + ).Insert(builder).Return() + builder.AllocateInstruction().AsCallGoRuntimeMemmove(memmovePtr, &c.memmoveSig, args).Insert(builder) +} + +func (c *Compiler) reloadAfterCall() { + // Note that when these are not used in the following instructions, they will be optimized out. + // So in any ways, we define them! + + // After calling any function, memory buffer might have changed. So we need to re-define the variable. + // However, if the memory is shared, we don't need to reload the memory base and length as the base will never change. + if c.needMemory && !c.memoryShared { + c.reloadMemoryBaseLen() + } + + // Also, any mutable Global can change. + for _, index := range c.mutableGlobalVariablesIndexes { + _ = c.getWasmGlobalValue(index, true) + } +} + +func (c *Compiler) reloadMemoryBaseLen() { + _ = c.getMemoryBaseValue(true) + _ = c.getMemoryLenValue(true) + + // This function being called means that the memory base might have changed. + // Therefore, we need to clear the absolute addresses recorded in the known safe bounds + // because we cache the absolute address of the memory access per each base offset. + c.resetAbsoluteAddressInSafeBounds() +} + +func (c *Compiler) setWasmGlobalValue(index wasm.Index, v ssa.Value) { + variable := c.globalVariables[index] + opaqueOffset := c.offset.GlobalInstanceOffset(index) + + builder := c.ssaBuilder + if index < c.m.ImportGlobalCount { + loadGlobalInstPtr := builder.AllocateInstruction() + loadGlobalInstPtr.AsLoad(c.moduleCtxPtrValue, uint32(opaqueOffset), ssa.TypeI64) + builder.InsertInstruction(loadGlobalInstPtr) + + store := builder.AllocateInstruction() + store.AsStore(ssa.OpcodeStore, v, loadGlobalInstPtr.Return(), uint32(0)) + builder.InsertInstruction(store) + + } else { + store := builder.AllocateInstruction() + store.AsStore(ssa.OpcodeStore, v, c.moduleCtxPtrValue, uint32(opaqueOffset)) + builder.InsertInstruction(store) + } + + // The value has changed to `v`, so we record it. + builder.DefineVariableInCurrentBB(variable, v) +} + +func (c *Compiler) getWasmGlobalValue(index wasm.Index, forceLoad bool) ssa.Value { + variable := c.globalVariables[index] + typ := c.globalVariablesTypes[index] + opaqueOffset := c.offset.GlobalInstanceOffset(index) + + builder := c.ssaBuilder + if !forceLoad { + if v := builder.FindValueInLinearPath(variable); v.Valid() { + return v + } + } + + var load *ssa.Instruction + if index < c.m.ImportGlobalCount { + loadGlobalInstPtr := builder.AllocateInstruction() + loadGlobalInstPtr.AsLoad(c.moduleCtxPtrValue, uint32(opaqueOffset), ssa.TypeI64) + builder.InsertInstruction(loadGlobalInstPtr) + load = builder.AllocateInstruction(). + AsLoad(loadGlobalInstPtr.Return(), uint32(0), typ) + } else { + load = builder.AllocateInstruction(). + AsLoad(c.moduleCtxPtrValue, uint32(opaqueOffset), typ) + } + + v := load.Insert(builder).Return() + builder.DefineVariableInCurrentBB(variable, v) + return v +} + +const ( + memoryInstanceBufOffset = 0 + memoryInstanceBufSizeOffset = memoryInstanceBufOffset + 8 +) + +func (c *Compiler) getMemoryBaseValue(forceReload bool) ssa.Value { + builder := c.ssaBuilder + variable := c.memoryBaseVariable + if !forceReload { + if v := builder.FindValueInLinearPath(variable); v.Valid() { + return v + } + } + + var ret ssa.Value + if c.offset.LocalMemoryBegin < 0 { + loadMemInstPtr := builder.AllocateInstruction() + loadMemInstPtr.AsLoad(c.moduleCtxPtrValue, c.offset.ImportedMemoryBegin.U32(), ssa.TypeI64) + builder.InsertInstruction(loadMemInstPtr) + memInstPtr := loadMemInstPtr.Return() + + loadBufPtr := builder.AllocateInstruction() + loadBufPtr.AsLoad(memInstPtr, memoryInstanceBufOffset, ssa.TypeI64) + builder.InsertInstruction(loadBufPtr) + ret = loadBufPtr.Return() + } else { + load := builder.AllocateInstruction() + load.AsLoad(c.moduleCtxPtrValue, c.offset.LocalMemoryBase().U32(), ssa.TypeI64) + builder.InsertInstruction(load) + ret = load.Return() + } + + builder.DefineVariableInCurrentBB(variable, ret) + return ret +} + +func (c *Compiler) getMemoryLenValue(forceReload bool) ssa.Value { + variable := c.memoryLenVariable + builder := c.ssaBuilder + if !forceReload && !c.memoryShared { + if v := builder.FindValueInLinearPath(variable); v.Valid() { + return v + } + } + + var ret ssa.Value + if c.offset.LocalMemoryBegin < 0 { + loadMemInstPtr := builder.AllocateInstruction() + loadMemInstPtr.AsLoad(c.moduleCtxPtrValue, c.offset.ImportedMemoryBegin.U32(), ssa.TypeI64) + builder.InsertInstruction(loadMemInstPtr) + memInstPtr := loadMemInstPtr.Return() + + loadBufSizePtr := builder.AllocateInstruction() + if c.memoryShared { + sizeOffset := builder.AllocateInstruction().AsIconst64(memoryInstanceBufSizeOffset).Insert(builder).Return() + addr := builder.AllocateInstruction().AsIadd(memInstPtr, sizeOffset).Insert(builder).Return() + loadBufSizePtr.AsAtomicLoad(addr, 8, ssa.TypeI64) + } else { + loadBufSizePtr.AsLoad(memInstPtr, memoryInstanceBufSizeOffset, ssa.TypeI64) + } + builder.InsertInstruction(loadBufSizePtr) + + ret = loadBufSizePtr.Return() + } else { + load := builder.AllocateInstruction() + if c.memoryShared { + lenOffset := builder.AllocateInstruction().AsIconst64(c.offset.LocalMemoryLen().U64()).Insert(builder).Return() + addr := builder.AllocateInstruction().AsIadd(c.moduleCtxPtrValue, lenOffset).Insert(builder).Return() + load.AsAtomicLoad(addr, 8, ssa.TypeI64) + } else { + load.AsExtLoad(ssa.OpcodeUload32, c.moduleCtxPtrValue, c.offset.LocalMemoryLen().U32(), true) + } + builder.InsertInstruction(load) + ret = load.Return() + } + + builder.DefineVariableInCurrentBB(variable, ret) + return ret +} + +func (c *Compiler) insertIcmp(cond ssa.IntegerCmpCond) { + state, builder := c.state(), c.ssaBuilder + y, x := state.pop(), state.pop() + cmp := builder.AllocateInstruction() + cmp.AsIcmp(x, y, cond) + builder.InsertInstruction(cmp) + value := cmp.Return() + state.push(value) +} + +func (c *Compiler) insertFcmp(cond ssa.FloatCmpCond) { + state, builder := c.state(), c.ssaBuilder + y, x := state.pop(), state.pop() + cmp := builder.AllocateInstruction() + cmp.AsFcmp(x, y, cond) + builder.InsertInstruction(cmp) + value := cmp.Return() + state.push(value) +} + +// storeCallerModuleContext stores the current module's moduleContextPtr into execContext.callerModuleContextPtr. +func (c *Compiler) storeCallerModuleContext() { + builder := c.ssaBuilder + execCtx := c.execCtxPtrValue + store := builder.AllocateInstruction() + store.AsStore(ssa.OpcodeStore, + c.moduleCtxPtrValue, execCtx, wazevoapi.ExecutionContextOffsetCallerModuleContextPtr.U32()) + builder.InsertInstruction(store) +} + +func (c *Compiler) readByte() byte { + v := c.wasmFunctionBody[c.loweringState.pc+1] + c.loweringState.pc++ + return v +} + +func (c *Compiler) readI32u() uint32 { + v, n, err := leb128.LoadUint32(c.wasmFunctionBody[c.loweringState.pc+1:]) + if err != nil { + panic(err) // shouldn't be reached since compilation comes after validation. + } + c.loweringState.pc += int(n) + return v +} + +func (c *Compiler) readI32s() int32 { + v, n, err := leb128.LoadInt32(c.wasmFunctionBody[c.loweringState.pc+1:]) + if err != nil { + panic(err) // shouldn't be reached since compilation comes after validation. + } + c.loweringState.pc += int(n) + return v +} + +func (c *Compiler) readI64s() int64 { + v, n, err := leb128.LoadInt64(c.wasmFunctionBody[c.loweringState.pc+1:]) + if err != nil { + panic(err) // shouldn't be reached since compilation comes after validation. + } + c.loweringState.pc += int(n) + return v +} + +func (c *Compiler) readF32() float32 { + v := math.Float32frombits(binary.LittleEndian.Uint32(c.wasmFunctionBody[c.loweringState.pc+1:])) + c.loweringState.pc += 4 + return v +} + +func (c *Compiler) readF64() float64 { + v := math.Float64frombits(binary.LittleEndian.Uint64(c.wasmFunctionBody[c.loweringState.pc+1:])) + c.loweringState.pc += 8 + return v +} + +// readBlockType reads the block type from the current position of the bytecode reader. +func (c *Compiler) readBlockType() *wasm.FunctionType { + state := c.state() + + c.br.Reset(c.wasmFunctionBody[state.pc+1:]) + bt, num, err := wasm.DecodeBlockType(c.m.TypeSection, c.br, api.CoreFeaturesV2) + if err != nil { + panic(err) // shouldn't be reached since compilation comes after validation. + } + state.pc += int(num) + + return bt +} + +func (c *Compiler) readMemArg() (align, offset uint32) { + state := c.state() + + align, num, err := leb128.LoadUint32(c.wasmFunctionBody[state.pc+1:]) + if err != nil { + panic(fmt.Errorf("read memory align: %v", err)) + } + + state.pc += int(num) + offset, num, err = leb128.LoadUint32(c.wasmFunctionBody[state.pc+1:]) + if err != nil { + panic(fmt.Errorf("read memory offset: %v", err)) + } + + state.pc += int(num) + return align, offset +} + +// insertJumpToBlock inserts a jump instruction to the given block in the current block. +func (c *Compiler) insertJumpToBlock(args ssa.Values, targetBlk ssa.BasicBlock) { + if targetBlk.ReturnBlock() { + if c.needListener { + c.callListenerAfter() + } + } + + builder := c.ssaBuilder + jmp := builder.AllocateInstruction() + jmp.AsJump(args, targetBlk) + builder.InsertInstruction(jmp) +} + +func (c *Compiler) insertIntegerExtend(signed bool, from, to byte) { + state := c.state() + builder := c.ssaBuilder + v := state.pop() + extend := builder.AllocateInstruction() + if signed { + extend.AsSExtend(v, from, to) + } else { + extend.AsUExtend(v, from, to) + } + builder.InsertInstruction(extend) + value := extend.Return() + state.push(value) +} + +func (c *Compiler) switchTo(originalStackLen int, targetBlk ssa.BasicBlock) { + if targetBlk.Preds() == 0 { + c.loweringState.unreachable = true + } + + // Now we should adjust the stack and start translating the continuation block. + c.loweringState.values = c.loweringState.values[:originalStackLen] + + c.ssaBuilder.SetCurrentBlock(targetBlk) + + // At this point, blocks params consist only of the Wasm-level parameters, + // (since it's added only when we are trying to resolve variable *inside* this block). + for i := 0; i < targetBlk.Params(); i++ { + value := targetBlk.Param(i) + c.loweringState.push(value) + } +} + +// results returns the number of results of the current function. +func (c *Compiler) results() int { + return len(c.wasmFunctionTyp.Results) +} + +func (c *Compiler) lowerBrTable(labels []uint32, index ssa.Value) { + state := c.state() + builder := c.ssaBuilder + + f := state.ctrlPeekAt(int(labels[0])) + var numArgs int + if f.isLoop() { + numArgs = len(f.blockType.Params) + } else { + numArgs = len(f.blockType.Results) + } + + varPool := builder.VarLengthPool() + trampolineBlockIDs := varPool.Allocate(len(labels)) + + // We need trampoline blocks since depending on the target block structure, we might end up inserting moves before jumps, + // which cannot be done with br_table. Instead, we can do such per-block moves in the trampoline blocks. + // At the linking phase (very end of the backend), we can remove the unnecessary jumps, and therefore no runtime overhead. + currentBlk := builder.CurrentBlock() + for _, l := range labels { + // Args are always on the top of the stack. Note that we should not share the args slice + // among the jump instructions since the args are modified during passes (e.g. redundant phi elimination). + args := c.nPeekDup(numArgs) + targetBlk, _ := state.brTargetArgNumFor(l) + trampoline := builder.AllocateBasicBlock() + builder.SetCurrentBlock(trampoline) + c.insertJumpToBlock(args, targetBlk) + trampolineBlockIDs = trampolineBlockIDs.Append(builder.VarLengthPool(), ssa.Value(trampoline.ID())) + } + builder.SetCurrentBlock(currentBlk) + + // If the target block has no arguments, we can just jump to the target block. + brTable := builder.AllocateInstruction() + brTable.AsBrTable(index, trampolineBlockIDs) + builder.InsertInstruction(brTable) + + for _, trampolineID := range trampolineBlockIDs.View() { + builder.Seal(builder.BasicBlock(ssa.BasicBlockID(trampolineID))) + } +} + +func (l *loweringState) brTargetArgNumFor(labelIndex uint32) (targetBlk ssa.BasicBlock, argNum int) { + targetFrame := l.ctrlPeekAt(int(labelIndex)) + if targetFrame.isLoop() { + targetBlk, argNum = targetFrame.blk, len(targetFrame.blockType.Params) + } else { + targetBlk, argNum = targetFrame.followingBlock, len(targetFrame.blockType.Results) + } + return +} + +func (c *Compiler) callListenerBefore() { + c.storeCallerModuleContext() + + builder := c.ssaBuilder + beforeListeners1stElement := builder.AllocateInstruction(). + AsLoad(c.moduleCtxPtrValue, + c.offset.BeforeListenerTrampolines1stElement.U32(), + ssa.TypeI64, + ).Insert(builder).Return() + + beforeListenerPtr := builder.AllocateInstruction(). + AsLoad(beforeListeners1stElement, uint32(c.wasmFunctionTypeIndex)*8 /* 8 bytes per index */, ssa.TypeI64).Insert(builder).Return() + + entry := builder.EntryBlock() + ps := entry.Params() + + args := c.allocateVarLengthValues(ps, c.execCtxPtrValue, + builder.AllocateInstruction().AsIconst32(c.wasmLocalFunctionIndex).Insert(builder).Return()) + for i := 2; i < ps; i++ { + args = args.Append(builder.VarLengthPool(), entry.Param(i)) + } + + beforeSig := c.listenerSignatures[c.wasmFunctionTyp][0] + builder.AllocateInstruction(). + AsCallIndirect(beforeListenerPtr, beforeSig, args). + Insert(builder) +} + +func (c *Compiler) callListenerAfter() { + c.storeCallerModuleContext() + + builder := c.ssaBuilder + afterListeners1stElement := builder.AllocateInstruction(). + AsLoad(c.moduleCtxPtrValue, + c.offset.AfterListenerTrampolines1stElement.U32(), + ssa.TypeI64, + ).Insert(builder).Return() + + afterListenerPtr := builder.AllocateInstruction(). + AsLoad(afterListeners1stElement, + uint32(c.wasmFunctionTypeIndex)*8 /* 8 bytes per index */, ssa.TypeI64). + Insert(builder). + Return() + + afterSig := c.listenerSignatures[c.wasmFunctionTyp][1] + args := c.allocateVarLengthValues( + c.results()+2, + c.execCtxPtrValue, + builder.AllocateInstruction().AsIconst32(c.wasmLocalFunctionIndex).Insert(builder).Return(), + ) + + l := c.state() + tail := len(l.values) + args = args.Append(c.ssaBuilder.VarLengthPool(), l.values[tail-c.results():tail]...) + builder.AllocateInstruction(). + AsCallIndirect(afterListenerPtr, afterSig, args). + Insert(builder) +} + +const ( + elementOrDataInstanceLenOffset = 8 + elementOrDataInstanceSize = 24 +) + +// dropInstance inserts instructions to drop the element/data instance specified by the given index. +func (c *Compiler) dropDataOrElementInstance(index uint32, firstItemOffset wazevoapi.Offset) { + builder := c.ssaBuilder + instPtr := c.dataOrElementInstanceAddr(index, firstItemOffset) + + zero := builder.AllocateInstruction().AsIconst64(0).Insert(builder).Return() + + // Clear the instance. + builder.AllocateInstruction().AsStore(ssa.OpcodeStore, zero, instPtr, 0).Insert(builder) + builder.AllocateInstruction().AsStore(ssa.OpcodeStore, zero, instPtr, elementOrDataInstanceLenOffset).Insert(builder) + builder.AllocateInstruction().AsStore(ssa.OpcodeStore, zero, instPtr, elementOrDataInstanceLenOffset+8).Insert(builder) +} + +func (c *Compiler) dataOrElementInstanceAddr(index uint32, firstItemOffset wazevoapi.Offset) ssa.Value { + builder := c.ssaBuilder + + _1stItemPtr := builder. + AllocateInstruction(). + AsLoad(c.moduleCtxPtrValue, firstItemOffset.U32(), ssa.TypeI64). + Insert(builder).Return() + + // Each data/element instance is a slice, so we need to multiply index by 16 to get the offset of the target instance. + index = index * elementOrDataInstanceSize + indexExt := builder.AllocateInstruction().AsIconst64(uint64(index)).Insert(builder).Return() + // Then, add the offset to the address of the instance. + instPtr := builder.AllocateInstruction().AsIadd(_1stItemPtr, indexExt).Insert(builder).Return() + return instPtr +} + +func (c *Compiler) boundsCheckInDataOrElementInstance(instPtr, offsetInInstance, copySize ssa.Value, exitCode wazevoapi.ExitCode) { + builder := c.ssaBuilder + dataInstLen := builder.AllocateInstruction(). + AsLoad(instPtr, elementOrDataInstanceLenOffset, ssa.TypeI64). + Insert(builder).Return() + ceil := builder.AllocateInstruction().AsIadd(offsetInInstance, copySize).Insert(builder).Return() + cmp := builder.AllocateInstruction(). + AsIcmp(dataInstLen, ceil, ssa.IntegerCmpCondUnsignedLessThan). + Insert(builder). + Return() + builder.AllocateInstruction(). + AsExitIfTrueWithCode(c.execCtxPtrValue, cmp, exitCode). + Insert(builder) +} + +func (c *Compiler) boundsCheckInTable(tableIndex uint32, offset, size ssa.Value) (tableInstancePtr ssa.Value) { + builder := c.ssaBuilder + dstCeil := builder.AllocateInstruction().AsIadd(offset, size).Insert(builder).Return() + + // Load the table. + tableInstancePtr = builder.AllocateInstruction(). + AsLoad(c.moduleCtxPtrValue, c.offset.TableOffset(int(tableIndex)).U32(), ssa.TypeI64). + Insert(builder).Return() + + // Load the table's length. + tableLen := builder.AllocateInstruction(). + AsLoad(tableInstancePtr, tableInstanceLenOffset, ssa.TypeI32).Insert(builder).Return() + tableLenExt := builder.AllocateInstruction().AsUExtend(tableLen, 32, 64).Insert(builder).Return() + + // Compare the length and the target, and trap if out of bounds. + checkOOB := builder.AllocateInstruction() + checkOOB.AsIcmp(tableLenExt, dstCeil, ssa.IntegerCmpCondUnsignedLessThan) + builder.InsertInstruction(checkOOB) + exitIfOOB := builder.AllocateInstruction() + exitIfOOB.AsExitIfTrueWithCode(c.execCtxPtrValue, checkOOB.Return(), wazevoapi.ExitCodeTableOutOfBounds) + builder.InsertInstruction(exitIfOOB) + return +} + +func (c *Compiler) loadTableBaseAddr(tableInstancePtr ssa.Value) ssa.Value { + builder := c.ssaBuilder + loadTableBaseAddress := builder. + AllocateInstruction(). + AsLoad(tableInstancePtr, tableInstanceBaseAddressOffset, ssa.TypeI64). + Insert(builder) + return loadTableBaseAddress.Return() +} + +func (c *Compiler) boundsCheckInMemory(memLen, offset, size ssa.Value) { + builder := c.ssaBuilder + ceil := builder.AllocateInstruction().AsIadd(offset, size).Insert(builder).Return() + cmp := builder.AllocateInstruction(). + AsIcmp(memLen, ceil, ssa.IntegerCmpCondUnsignedLessThan). + Insert(builder). + Return() + builder.AllocateInstruction(). + AsExitIfTrueWithCode(c.execCtxPtrValue, cmp, wazevoapi.ExitCodeMemoryOutOfBounds). + Insert(builder) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/misc.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/misc.go new file mode 100644 index 00000000000..2db2b892c28 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/misc.go @@ -0,0 +1,10 @@ +package frontend + +import ( + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" + "github.com/tetratelabs/wazero/internal/wasm" +) + +func FunctionIndexToFuncRef(idx wasm.Index) ssa.FuncRef { + return ssa.FuncRef(idx) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/sort_id.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/sort_id.go new file mode 100644 index 00000000000..5b055d1272d --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/sort_id.go @@ -0,0 +1,13 @@ +package frontend + +import ( + "slices" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" +) + +func sortSSAValueIDs(IDs []ssa.ValueID) { + slices.SortFunc(IDs, func(i, j ssa.ValueID) int { + return int(i) - int(j) + }) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/hostmodule.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/hostmodule.go new file mode 100644 index 00000000000..800a5d2a814 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/hostmodule.go @@ -0,0 +1,88 @@ +package wazevo + +import ( + "encoding/binary" + "reflect" + "unsafe" + + "github.com/tetratelabs/wazero/experimental" + "github.com/tetratelabs/wazero/internal/wasm" +) + +func buildHostModuleOpaque(m *wasm.Module, listeners []experimental.FunctionListener) moduleContextOpaque { + size := len(m.CodeSection)*16 + 32 + ret := newAlignedOpaque(size) + + binary.LittleEndian.PutUint64(ret[0:], uint64(uintptr(unsafe.Pointer(m)))) + + if len(listeners) > 0 { + //nolint:staticcheck + sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&listeners)) + binary.LittleEndian.PutUint64(ret[8:], uint64(sliceHeader.Data)) + binary.LittleEndian.PutUint64(ret[16:], uint64(sliceHeader.Len)) + binary.LittleEndian.PutUint64(ret[24:], uint64(sliceHeader.Cap)) + } + + offset := 32 + for i := range m.CodeSection { + goFn := m.CodeSection[i].GoFunc + writeIface(goFn, ret[offset:]) + offset += 16 + } + return ret +} + +func hostModuleFromOpaque(opaqueBegin uintptr) *wasm.Module { + var opaqueViewOverSlice []byte + //nolint:staticcheck + sh := (*reflect.SliceHeader)(unsafe.Pointer(&opaqueViewOverSlice)) + sh.Data = opaqueBegin + sh.Len = 32 + sh.Cap = 32 + return *(**wasm.Module)(unsafe.Pointer(&opaqueViewOverSlice[0])) +} + +func hostModuleListenersSliceFromOpaque(opaqueBegin uintptr) []experimental.FunctionListener { + var opaqueViewOverSlice []byte + //nolint:staticcheck + sh := (*reflect.SliceHeader)(unsafe.Pointer(&opaqueViewOverSlice)) + sh.Data = opaqueBegin + sh.Len = 32 + sh.Cap = 32 + + b := binary.LittleEndian.Uint64(opaqueViewOverSlice[8:]) + l := binary.LittleEndian.Uint64(opaqueViewOverSlice[16:]) + c := binary.LittleEndian.Uint64(opaqueViewOverSlice[24:]) + var ret []experimental.FunctionListener + //nolint:staticcheck + sh = (*reflect.SliceHeader)(unsafe.Pointer(&ret)) + sh.Data = uintptr(b) + sh.Len = int(l) + sh.Cap = int(c) + return ret +} + +func hostModuleGoFuncFromOpaque[T any](index int, opaqueBegin uintptr) T { + offset := uintptr(index*16) + 32 + ptr := opaqueBegin + offset + + var opaqueViewOverFunction []byte + //nolint:staticcheck + sh := (*reflect.SliceHeader)(unsafe.Pointer(&opaqueViewOverFunction)) + sh.Data = ptr + sh.Len = 16 + sh.Cap = 16 + return readIface(opaqueViewOverFunction).(T) +} + +func writeIface(goFn interface{}, buf []byte) { + goFnIface := *(*[2]uint64)(unsafe.Pointer(&goFn)) + binary.LittleEndian.PutUint64(buf, goFnIface[0]) + binary.LittleEndian.PutUint64(buf[8:], goFnIface[1]) +} + +func readIface(buf []byte) interface{} { + b := binary.LittleEndian.Uint64(buf) + s := binary.LittleEndian.Uint64(buf[8:]) + return *(*interface{})(unsafe.Pointer(&[2]uint64{b, s})) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/isa_amd64.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/isa_amd64.go new file mode 100644 index 00000000000..da27cc10804 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/isa_amd64.go @@ -0,0 +1,30 @@ +//go:build amd64 + +package wazevo + +import ( + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64" +) + +func newMachine() backend.Machine { + return amd64.NewBackend() +} + +// unwindStack is a function to unwind the stack, and appends return addresses to `returnAddresses` slice. +// The implementation must be aligned with the ABI/Calling convention. +func unwindStack(sp, fp, top uintptr, returnAddresses []uintptr) []uintptr { + return amd64.UnwindStack(sp, fp, top, returnAddresses) +} + +// goCallStackView is a function to get a view of the stack before a Go call, which +// is the view of the stack allocated in CompileGoFunctionTrampoline. +func goCallStackView(stackPointerBeforeGoCall *uint64) []uint64 { + return amd64.GoCallStackView(stackPointerBeforeGoCall) +} + +// adjustClonedStack is a function to adjust the stack after it is grown. +// More precisely, absolute addresses (frame pointers) in the stack must be adjusted. +func adjustClonedStack(oldsp, oldTop, sp, fp, top uintptr) { + amd64.AdjustClonedStack(oldsp, oldTop, sp, fp, top) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/isa_arm64.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/isa_arm64.go new file mode 100644 index 00000000000..e7a8465486f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/isa_arm64.go @@ -0,0 +1,32 @@ +//go:build arm64 + +package wazevo + +import ( + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64" +) + +func newMachine() backend.Machine { + return arm64.NewBackend() +} + +// unwindStack is a function to unwind the stack, and appends return addresses to `returnAddresses` slice. +// The implementation must be aligned with the ABI/Calling convention. +func unwindStack(sp, fp, top uintptr, returnAddresses []uintptr) []uintptr { + return arm64.UnwindStack(sp, fp, top, returnAddresses) +} + +// goCallStackView is a function to get a view of the stack before a Go call, which +// is the view of the stack allocated in CompileGoFunctionTrampoline. +func goCallStackView(stackPointerBeforeGoCall *uint64) []uint64 { + return arm64.GoCallStackView(stackPointerBeforeGoCall) +} + +// adjustClonedStack is a function to adjust the stack after it is grown. +// More precisely, absolute addresses (frame pointers) in the stack must be adjusted. +func adjustClonedStack(oldsp, oldTop, sp, fp, top uintptr) { + // TODO: currently, the frame pointers are not used, and saved old sps are relative to the current stack pointer, + // so no need to adjustment on arm64. However, when we make it absolute, which in my opinion is better perf-wise + // at the expense of slightly costly stack growth, we need to adjust the pushed frame pointers. +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/isa_other.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/isa_other.go new file mode 100644 index 00000000000..c5afc6314f7 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/isa_other.go @@ -0,0 +1,29 @@ +//go:build !(amd64 || arm64) + +package wazevo + +import ( + "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" +) + +func newMachine() backend.Machine { + panic("unsupported architecture") +} + +// unwindStack is a function to unwind the stack, and appends return addresses to `returnAddresses` slice. +// The implementation must be aligned with the ABI/Calling convention. +func unwindStack(sp, fp, top uintptr, returnAddresses []uintptr) []uintptr { + panic("unsupported architecture") +} + +// goCallStackView is a function to get a view of the stack before a Go call, which +// is the view of the stack allocated in CompileGoFunctionTrampoline. +func goCallStackView(stackPointerBeforeGoCall *uint64) []uint64 { + panic("unsupported architecture") +} + +// adjustClonedStack is a function to adjust the stack after it is grown. +// More precisely, absolute addresses (frame pointers) in the stack must be adjusted. +func adjustClonedStack(oldsp, oldTop, sp, fp, top uintptr) { + panic("unsupported architecture") +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/memmove.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/memmove.go new file mode 100644 index 00000000000..889922107db --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/memmove.go @@ -0,0 +1,11 @@ +package wazevo + +import ( + "reflect" + "unsafe" +) + +//go:linkname memmove runtime.memmove +func memmove(_, _ unsafe.Pointer, _ uintptr) + +var memmovPtr = reflect.ValueOf(memmove).Pointer() diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/module_engine.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/module_engine.go new file mode 100644 index 00000000000..53206f1cccb --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/module_engine.go @@ -0,0 +1,332 @@ +package wazevo + +import ( + "encoding/binary" + "unsafe" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental" + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" + "github.com/tetratelabs/wazero/internal/wasm" + "github.com/tetratelabs/wazero/internal/wasmruntime" +) + +type ( + // moduleEngine implements wasm.ModuleEngine. + moduleEngine struct { + // opaquePtr equals &opaque[0]. + opaquePtr *byte + parent *compiledModule + module *wasm.ModuleInstance + opaque moduleContextOpaque + localFunctionInstances []*functionInstance + importedFunctions []importedFunction + listeners []experimental.FunctionListener + } + + functionInstance struct { + executable *byte + moduleContextOpaquePtr *byte + typeID wasm.FunctionTypeID + indexInModule wasm.Index + } + + importedFunction struct { + me *moduleEngine + indexInModule wasm.Index + } + + // moduleContextOpaque is the opaque byte slice of Module instance specific contents whose size + // is only Wasm-compile-time known, hence dynamic. Its contents are basically the pointers to the module instance, + // specific objects as well as functions. This is sometimes called "VMContext" in other Wasm runtimes. + // + // Internally, the buffer is structured as follows: + // + // type moduleContextOpaque struct { + // moduleInstance *wasm.ModuleInstance + // localMemoryBufferPtr *byte (optional) + // localMemoryLength uint64 (optional) + // importedMemoryInstance *wasm.MemoryInstance (optional) + // importedMemoryOwnerOpaqueCtx *byte (optional) + // importedFunctions [# of importedFunctions]functionInstance + // importedGlobals []ImportedGlobal (optional) + // localGlobals []Global (optional) + // typeIDsBegin &wasm.ModuleInstance.TypeIDs[0] (optional) + // tables []*wasm.TableInstance (optional) + // beforeListenerTrampolines1stElement **byte (optional) + // afterListenerTrampolines1stElement **byte (optional) + // dataInstances1stElement []wasm.DataInstance (optional) + // elementInstances1stElement []wasm.ElementInstance (optional) + // } + // + // type ImportedGlobal struct { + // *Global + // _ uint64 // padding + // } + // + // type Global struct { + // Val, ValHi uint64 + // } + // + // See wazevoapi.NewModuleContextOffsetData for the details of the offsets. + // + // Note that for host modules, the structure is entirely different. See buildHostModuleOpaque. + moduleContextOpaque []byte +) + +func newAlignedOpaque(size int) moduleContextOpaque { + // Check if the size is a multiple of 16. + if size%16 != 0 { + panic("size must be a multiple of 16") + } + buf := make([]byte, size+16) + // Align the buffer to 16 bytes. + rem := uintptr(unsafe.Pointer(&buf[0])) % 16 + buf = buf[16-rem:] + return buf +} + +func (m *moduleEngine) setupOpaque() { + inst := m.module + offsets := &m.parent.offsets + opaque := m.opaque + + binary.LittleEndian.PutUint64(opaque[offsets.ModuleInstanceOffset:], + uint64(uintptr(unsafe.Pointer(m.module))), + ) + + if lm := offsets.LocalMemoryBegin; lm >= 0 { + m.putLocalMemory() + } + + // Note: imported memory is resolved in ResolveImportedFunction. + + // Note: imported functions are resolved in ResolveImportedFunction. + + if globalOffset := offsets.GlobalsBegin; globalOffset >= 0 { + for i, g := range inst.Globals { + if i < int(inst.Source.ImportGlobalCount) { + importedME := g.Me.(*moduleEngine) + offset := importedME.parent.offsets.GlobalInstanceOffset(g.Index) + importedMEOpaque := importedME.opaque + binary.LittleEndian.PutUint64(opaque[globalOffset:], + uint64(uintptr(unsafe.Pointer(&importedMEOpaque[offset])))) + } else { + binary.LittleEndian.PutUint64(opaque[globalOffset:], g.Val) + binary.LittleEndian.PutUint64(opaque[globalOffset+8:], g.ValHi) + } + globalOffset += 16 + } + } + + if tableOffset := offsets.TablesBegin; tableOffset >= 0 { + // First we write the first element's address of typeIDs. + if len(inst.TypeIDs) > 0 { + binary.LittleEndian.PutUint64(opaque[offsets.TypeIDs1stElement:], uint64(uintptr(unsafe.Pointer(&inst.TypeIDs[0])))) + } + + // Then we write the table addresses. + for _, table := range inst.Tables { + binary.LittleEndian.PutUint64(opaque[tableOffset:], uint64(uintptr(unsafe.Pointer(table)))) + tableOffset += 8 + } + } + + if beforeListenerOffset := offsets.BeforeListenerTrampolines1stElement; beforeListenerOffset >= 0 { + binary.LittleEndian.PutUint64(opaque[beforeListenerOffset:], uint64(uintptr(unsafe.Pointer(&m.parent.listenerBeforeTrampolines[0])))) + } + if afterListenerOffset := offsets.AfterListenerTrampolines1stElement; afterListenerOffset >= 0 { + binary.LittleEndian.PutUint64(opaque[afterListenerOffset:], uint64(uintptr(unsafe.Pointer(&m.parent.listenerAfterTrampolines[0])))) + } + if len(inst.DataInstances) > 0 { + binary.LittleEndian.PutUint64(opaque[offsets.DataInstances1stElement:], uint64(uintptr(unsafe.Pointer(&inst.DataInstances[0])))) + } + if len(inst.ElementInstances) > 0 { + binary.LittleEndian.PutUint64(opaque[offsets.ElementInstances1stElement:], uint64(uintptr(unsafe.Pointer(&inst.ElementInstances[0])))) + } +} + +// NewFunction implements wasm.ModuleEngine. +func (m *moduleEngine) NewFunction(index wasm.Index) api.Function { + if wazevoapi.PrintMachineCodeHexPerFunctionDisassemblable { + panic("When PrintMachineCodeHexPerFunctionDisassemblable enabled, functions must not be called") + } + + localIndex := index + if importedFnCount := m.module.Source.ImportFunctionCount; index < importedFnCount { + imported := &m.importedFunctions[index] + return imported.me.NewFunction(imported.indexInModule) + } else { + localIndex -= importedFnCount + } + + src := m.module.Source + typIndex := src.FunctionSection[localIndex] + typ := src.TypeSection[typIndex] + sizeOfParamResultSlice := typ.ResultNumInUint64 + if ps := typ.ParamNumInUint64; ps > sizeOfParamResultSlice { + sizeOfParamResultSlice = ps + } + p := m.parent + offset := p.functionOffsets[localIndex] + + ce := &callEngine{ + indexInModule: index, + executable: &p.executable[offset], + parent: m, + preambleExecutable: p.entryPreamblesPtrs[typIndex], + sizeOfParamResultSlice: sizeOfParamResultSlice, + requiredParams: typ.ParamNumInUint64, + numberOfResults: typ.ResultNumInUint64, + } + + sharedFunctions := p.sharedFunctions + ce.execCtx.memoryGrowTrampolineAddress = sharedFunctions.memoryGrowAddress + ce.execCtx.stackGrowCallTrampolineAddress = sharedFunctions.stackGrowAddress + ce.execCtx.checkModuleExitCodeTrampolineAddress = sharedFunctions.checkModuleExitCodeAddress + ce.execCtx.tableGrowTrampolineAddress = sharedFunctions.tableGrowAddress + ce.execCtx.refFuncTrampolineAddress = sharedFunctions.refFuncAddress + ce.execCtx.memoryWait32TrampolineAddress = sharedFunctions.memoryWait32Address + ce.execCtx.memoryWait64TrampolineAddress = sharedFunctions.memoryWait64Address + ce.execCtx.memoryNotifyTrampolineAddress = sharedFunctions.memoryNotifyAddress + ce.execCtx.memmoveAddress = memmovPtr + ce.init() + return ce +} + +// GetGlobalValue implements the same method as documented on wasm.ModuleEngine. +func (m *moduleEngine) GetGlobalValue(i wasm.Index) (lo, hi uint64) { + offset := m.parent.offsets.GlobalInstanceOffset(i) + buf := m.opaque[offset:] + if i < m.module.Source.ImportGlobalCount { + panic("GetGlobalValue should not be called for imported globals") + } + return binary.LittleEndian.Uint64(buf), binary.LittleEndian.Uint64(buf[8:]) +} + +// SetGlobalValue implements the same method as documented on wasm.ModuleEngine. +func (m *moduleEngine) SetGlobalValue(i wasm.Index, lo, hi uint64) { + offset := m.parent.offsets.GlobalInstanceOffset(i) + buf := m.opaque[offset:] + if i < m.module.Source.ImportGlobalCount { + panic("GetGlobalValue should not be called for imported globals") + } + binary.LittleEndian.PutUint64(buf, lo) + binary.LittleEndian.PutUint64(buf[8:], hi) +} + +// OwnsGlobals implements the same method as documented on wasm.ModuleEngine. +func (m *moduleEngine) OwnsGlobals() bool { return true } + +// MemoryGrown implements wasm.ModuleEngine. +func (m *moduleEngine) MemoryGrown() { + m.putLocalMemory() +} + +// putLocalMemory writes the local memory buffer pointer and length to the opaque buffer. +func (m *moduleEngine) putLocalMemory() { + mem := m.module.MemoryInstance + offset := m.parent.offsets.LocalMemoryBegin + + s := uint64(len(mem.Buffer)) + var b uint64 + if len(mem.Buffer) > 0 { + b = uint64(uintptr(unsafe.Pointer(&mem.Buffer[0]))) + } + binary.LittleEndian.PutUint64(m.opaque[offset:], b) + binary.LittleEndian.PutUint64(m.opaque[offset+8:], s) +} + +// ResolveImportedFunction implements wasm.ModuleEngine. +func (m *moduleEngine) ResolveImportedFunction(index, descFunc, indexInImportedModule wasm.Index, importedModuleEngine wasm.ModuleEngine) { + executableOffset, moduleCtxOffset, typeIDOffset := m.parent.offsets.ImportedFunctionOffset(index) + importedME := importedModuleEngine.(*moduleEngine) + + if int(indexInImportedModule) >= len(importedME.importedFunctions) { + indexInImportedModule -= wasm.Index(len(importedME.importedFunctions)) + } else { + imported := &importedME.importedFunctions[indexInImportedModule] + m.ResolveImportedFunction(index, descFunc, imported.indexInModule, imported.me) + return // Recursively resolve the imported function. + } + + offset := importedME.parent.functionOffsets[indexInImportedModule] + typeID := m.module.TypeIDs[descFunc] + executable := &importedME.parent.executable[offset] + // Write functionInstance. + binary.LittleEndian.PutUint64(m.opaque[executableOffset:], uint64(uintptr(unsafe.Pointer(executable)))) + binary.LittleEndian.PutUint64(m.opaque[moduleCtxOffset:], uint64(uintptr(unsafe.Pointer(importedME.opaquePtr)))) + binary.LittleEndian.PutUint64(m.opaque[typeIDOffset:], uint64(typeID)) + + // Write importedFunction so that it can be used by NewFunction. + m.importedFunctions[index] = importedFunction{me: importedME, indexInModule: indexInImportedModule} +} + +// ResolveImportedMemory implements wasm.ModuleEngine. +func (m *moduleEngine) ResolveImportedMemory(importedModuleEngine wasm.ModuleEngine) { + importedME := importedModuleEngine.(*moduleEngine) + inst := importedME.module + + var memInstPtr uint64 + var memOwnerOpaquePtr uint64 + if offs := importedME.parent.offsets; offs.ImportedMemoryBegin >= 0 { + offset := offs.ImportedMemoryBegin + memInstPtr = binary.LittleEndian.Uint64(importedME.opaque[offset:]) + memOwnerOpaquePtr = binary.LittleEndian.Uint64(importedME.opaque[offset+8:]) + } else { + memInstPtr = uint64(uintptr(unsafe.Pointer(inst.MemoryInstance))) + memOwnerOpaquePtr = uint64(uintptr(unsafe.Pointer(importedME.opaquePtr))) + } + offset := m.parent.offsets.ImportedMemoryBegin + binary.LittleEndian.PutUint64(m.opaque[offset:], memInstPtr) + binary.LittleEndian.PutUint64(m.opaque[offset+8:], memOwnerOpaquePtr) +} + +// DoneInstantiation implements wasm.ModuleEngine. +func (m *moduleEngine) DoneInstantiation() { + if !m.module.Source.IsHostModule { + m.setupOpaque() + } +} + +// FunctionInstanceReference implements wasm.ModuleEngine. +func (m *moduleEngine) FunctionInstanceReference(funcIndex wasm.Index) wasm.Reference { + if funcIndex < m.module.Source.ImportFunctionCount { + begin, _, _ := m.parent.offsets.ImportedFunctionOffset(funcIndex) + return uintptr(unsafe.Pointer(&m.opaque[begin])) + } + localIndex := funcIndex - m.module.Source.ImportFunctionCount + p := m.parent + executable := &p.executable[p.functionOffsets[localIndex]] + typeID := m.module.TypeIDs[m.module.Source.FunctionSection[localIndex]] + + lf := &functionInstance{ + executable: executable, + moduleContextOpaquePtr: m.opaquePtr, + typeID: typeID, + indexInModule: funcIndex, + } + m.localFunctionInstances = append(m.localFunctionInstances, lf) + return uintptr(unsafe.Pointer(lf)) +} + +// LookupFunction implements wasm.ModuleEngine. +func (m *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.FunctionTypeID, tableOffset wasm.Index) (*wasm.ModuleInstance, wasm.Index) { + if tableOffset >= uint32(len(t.References)) || t.Type != wasm.RefTypeFuncref { + panic(wasmruntime.ErrRuntimeInvalidTableAccess) + } + rawPtr := t.References[tableOffset] + if rawPtr == 0 { + panic(wasmruntime.ErrRuntimeInvalidTableAccess) + } + + tf := wazevoapi.PtrFromUintptr[functionInstance](rawPtr) + if tf.typeID != typeId { + panic(wasmruntime.ErrRuntimeIndirectCallTypeMismatch) + } + return moduleInstanceFromOpaquePtr(tf.moduleContextOpaquePtr), tf.indexInModule +} + +func moduleInstanceFromOpaquePtr(ptr *byte) *wasm.ModuleInstance { + return *(**wasm.ModuleInstance)(unsafe.Pointer(ptr)) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/basic_block.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/basic_block.go new file mode 100644 index 00000000000..cf7f14d3b1f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/basic_block.go @@ -0,0 +1,379 @@ +package ssa + +import ( + "fmt" + "strconv" + "strings" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +// BasicBlock represents the Basic Block of an SSA function. +// Each BasicBlock always ends with branching instructions (e.g. Branch, Return, etc.), +// and at most two branches are allowed. If there's two branches, these two are placed together at the end of the block. +// In other words, there's no branching instruction in the middle of the block. +// +// Note: we use the "block argument" variant of SSA, instead of PHI functions. See the package level doc comments. +// +// Note: we use "parameter/param" as a placeholder which represents a variant of PHI, and "argument/arg" as an actual +// Value passed to that "parameter/param". +type BasicBlock interface { + // ID returns the unique ID of this block. + ID() BasicBlockID + + // Name returns the unique string ID of this block. e.g. blk0, blk1, ... + Name() string + + // AddParam adds the parameter to the block whose type specified by `t`. + AddParam(b Builder, t Type) Value + + // Params returns the number of parameters to this block. + Params() int + + // Param returns (Variable, Value) which corresponds to the i-th parameter of this block. + // The returned Value is the definition of the param in this block. + Param(i int) Value + + // Root returns the root instruction of this block. + Root() *Instruction + + // Tail returns the tail instruction of this block. + Tail() *Instruction + + // EntryBlock returns true if this block represents the function entry. + EntryBlock() bool + + // ReturnBlock returns ture if this block represents the function return. + ReturnBlock() bool + + // Valid is true if this block is still valid even after optimizations. + Valid() bool + + // Sealed is true if this block has been sealed. + Sealed() bool + + // Preds returns the number of predecessors of this block. + Preds() int + + // Pred returns the i-th predecessor of this block. + Pred(i int) BasicBlock + + // Succs returns the number of successors of this block. + Succs() int + + // Succ returns the i-th successor of this block. + Succ(i int) BasicBlock + + // LoopHeader returns true if this block is a loop header. + LoopHeader() bool + + // LoopNestingForestChildren returns the children of this block in the loop nesting forest. + LoopNestingForestChildren() []BasicBlock +} + +type ( + // basicBlock is a basic block in a SSA-transformed function. + basicBlock struct { + id BasicBlockID + rootInstr, currentInstr *Instruction + // params are Values that represent parameters to a basicBlock. + // Each parameter can be considered as an output of PHI instruction in traditional SSA. + params Values + preds []basicBlockPredecessorInfo + success []*basicBlock + // singlePred is the alias to preds[0] for fast lookup, and only set after Seal is called. + singlePred *basicBlock + // lastDefinitions maps Variable to its last definition in this block. + lastDefinitions map[Variable]Value + // unknownsValues are used in builder.findValue. The usage is well-described in the paper. + unknownValues []unknownValue + // invalid is true if this block is made invalid during optimizations. + invalid bool + // sealed is true if this is sealed (all the predecessors are known). + sealed bool + // loopHeader is true if this block is a loop header: + // + // > A loop header (sometimes called the entry point of the loop) is a dominator that is the target + // > of a loop-forming back edge. The loop header dominates all blocks in the loop body. + // > A block may be a loop header for more than one loop. A loop may have multiple entry points, + // > in which case it has no "loop header". + // + // See https://en.wikipedia.org/wiki/Control-flow_graph for more details. + // + // This is modified during the subPassLoopDetection pass. + loopHeader bool + + // loopNestingForestChildren holds the children of this block in the loop nesting forest. + // Non-empty if and only if this block is a loop header (i.e. loopHeader=true) + loopNestingForestChildren wazevoapi.VarLength[BasicBlock] + + // reversePostOrder is used to sort all the blocks in the function in reverse post order. + // This is used in builder.LayoutBlocks. + reversePostOrder int32 + + // visited is used during various traversals. + visited int32 + + // child and sibling are the ones in the dominator tree. + child, sibling *basicBlock + } + // BasicBlockID is the unique ID of a basicBlock. + BasicBlockID uint32 + + unknownValue struct { + // variable is the variable that this unknownValue represents. + variable Variable + // value is the value that this unknownValue represents. + value Value + } +) + +// basicBlockVarLengthNil is the default nil value for basicBlock.loopNestingForestChildren. +var basicBlockVarLengthNil = wazevoapi.NewNilVarLength[BasicBlock]() + +const basicBlockIDReturnBlock = 0xffffffff + +// Name implements BasicBlock.Name. +func (bb *basicBlock) Name() string { + if bb.id == basicBlockIDReturnBlock { + return "blk_ret" + } else { + return fmt.Sprintf("blk%d", bb.id) + } +} + +// String implements fmt.Stringer for debugging. +func (bid BasicBlockID) String() string { + if bid == basicBlockIDReturnBlock { + return "blk_ret" + } else { + return fmt.Sprintf("blk%d", bid) + } +} + +// ID implements BasicBlock.ID. +func (bb *basicBlock) ID() BasicBlockID { + return bb.id +} + +// basicBlockPredecessorInfo is the information of a predecessor of a basicBlock. +// predecessor is determined by a pair of block and the branch instruction used to jump to the successor. +type basicBlockPredecessorInfo struct { + blk *basicBlock + branch *Instruction +} + +// EntryBlock implements BasicBlock.EntryBlock. +func (bb *basicBlock) EntryBlock() bool { + return bb.id == 0 +} + +// ReturnBlock implements BasicBlock.ReturnBlock. +func (bb *basicBlock) ReturnBlock() bool { + return bb.id == basicBlockIDReturnBlock +} + +// AddParam implements BasicBlock.AddParam. +func (bb *basicBlock) AddParam(b Builder, typ Type) Value { + paramValue := b.allocateValue(typ) + bb.params = bb.params.Append(&b.(*builder).varLengthPool, paramValue) + return paramValue +} + +// addParamOn adds a parameter to this block whose value is already allocated. +func (bb *basicBlock) addParamOn(b *builder, value Value) { + bb.params = bb.params.Append(&b.varLengthPool, value) +} + +// Params implements BasicBlock.Params. +func (bb *basicBlock) Params() int { + return len(bb.params.View()) +} + +// Param implements BasicBlock.Param. +func (bb *basicBlock) Param(i int) Value { + return bb.params.View()[i] +} + +// Valid implements BasicBlock.Valid. +func (bb *basicBlock) Valid() bool { + return !bb.invalid +} + +// Sealed implements BasicBlock.Sealed. +func (bb *basicBlock) Sealed() bool { + return bb.sealed +} + +// insertInstruction implements BasicBlock.InsertInstruction. +func (bb *basicBlock) insertInstruction(b *builder, next *Instruction) { + current := bb.currentInstr + if current != nil { + current.next = next + next.prev = current + } else { + bb.rootInstr = next + } + bb.currentInstr = next + + switch next.opcode { + case OpcodeJump, OpcodeBrz, OpcodeBrnz: + target := BasicBlockID(next.rValue) + b.basicBlock(target).addPred(bb, next) + case OpcodeBrTable: + for _, _target := range next.rValues.View() { + target := BasicBlockID(_target) + b.basicBlock(target).addPred(bb, next) + } + } +} + +// NumPreds implements BasicBlock.NumPreds. +func (bb *basicBlock) NumPreds() int { + return len(bb.preds) +} + +// Preds implements BasicBlock.Preds. +func (bb *basicBlock) Preds() int { + return len(bb.preds) +} + +// Pred implements BasicBlock.Pred. +func (bb *basicBlock) Pred(i int) BasicBlock { + return bb.preds[i].blk +} + +// Succs implements BasicBlock.Succs. +func (bb *basicBlock) Succs() int { + return len(bb.success) +} + +// Succ implements BasicBlock.Succ. +func (bb *basicBlock) Succ(i int) BasicBlock { + return bb.success[i] +} + +// Root implements BasicBlock.Root. +func (bb *basicBlock) Root() *Instruction { + return bb.rootInstr +} + +// Tail implements BasicBlock.Tail. +func (bb *basicBlock) Tail() *Instruction { + return bb.currentInstr +} + +// reset resets the basicBlock to its initial state so that it can be reused for another function. +func resetBasicBlock(bb *basicBlock) { + bb.params = ValuesNil + bb.rootInstr, bb.currentInstr = nil, nil + bb.preds = bb.preds[:0] + bb.success = bb.success[:0] + bb.invalid, bb.sealed = false, false + bb.singlePred = nil + bb.unknownValues = bb.unknownValues[:0] + bb.lastDefinitions = wazevoapi.ResetMap(bb.lastDefinitions) + bb.reversePostOrder = -1 + bb.visited = 0 + bb.loopNestingForestChildren = basicBlockVarLengthNil + bb.loopHeader = false + bb.sibling = nil + bb.child = nil +} + +// addPred adds a predecessor to this block specified by the branch instruction. +func (bb *basicBlock) addPred(blk BasicBlock, branch *Instruction) { + if bb.sealed { + panic("BUG: trying to add predecessor to a sealed block: " + bb.Name()) + } + + pred := blk.(*basicBlock) + for i := range bb.preds { + existingPred := &bb.preds[i] + if existingPred.blk == pred && existingPred.branch != branch { + // If the target is already added, then this must come from the same BrTable, + // otherwise such redundant branch should be eliminated by the frontend. (which should be simpler). + panic(fmt.Sprintf("BUG: redundant non BrTable jumps in %s whose targes are the same", bb.Name())) + } + } + + bb.preds = append(bb.preds, basicBlockPredecessorInfo{ + blk: pred, + branch: branch, + }) + + pred.success = append(pred.success, bb) +} + +// formatHeader returns the string representation of the header of the basicBlock. +func (bb *basicBlock) formatHeader(b Builder) string { + ps := make([]string, len(bb.params.View())) + for i, p := range bb.params.View() { + ps[i] = p.formatWithType(b) + } + + if len(bb.preds) > 0 { + preds := make([]string, 0, len(bb.preds)) + for _, pred := range bb.preds { + if pred.blk.invalid { + continue + } + preds = append(preds, fmt.Sprintf("blk%d", pred.blk.id)) + + } + return fmt.Sprintf("blk%d: (%s) <-- (%s)", + bb.id, strings.Join(ps, ","), strings.Join(preds, ",")) + } else { + return fmt.Sprintf("blk%d: (%s)", bb.id, strings.Join(ps, ", ")) + } +} + +// validates validates the basicBlock for debugging purpose. +func (bb *basicBlock) validate(b *builder) { + if bb.invalid { + panic("BUG: trying to validate an invalid block: " + bb.Name()) + } + if len(bb.preds) > 0 { + for _, pred := range bb.preds { + if pred.branch.opcode != OpcodeBrTable { + blockID := int(pred.branch.rValue) + target := b.basicBlocksPool.View(blockID) + if target != bb { + panic(fmt.Sprintf("BUG: '%s' is not branch to %s, but to %s", + pred.branch.Format(b), bb.Name(), target.Name())) + } + } + + var exp int + if bb.ReturnBlock() { + exp = len(b.currentSignature.Results) + } else { + exp = len(bb.params.View()) + } + + if len(pred.branch.vs.View()) != exp { + panic(fmt.Sprintf( + "BUG: len(argument at %s) != len(params at %s): %d != %d: %s", + pred.blk.Name(), bb.Name(), + len(pred.branch.vs.View()), len(bb.params.View()), pred.branch.Format(b), + )) + } + + } + } +} + +// String implements fmt.Stringer for debugging purpose only. +func (bb *basicBlock) String() string { + return strconv.Itoa(int(bb.id)) +} + +// LoopNestingForestChildren implements BasicBlock.LoopNestingForestChildren. +func (bb *basicBlock) LoopNestingForestChildren() []BasicBlock { + return bb.loopNestingForestChildren.View() +} + +// LoopHeader implements BasicBlock.LoopHeader. +func (bb *basicBlock) LoopHeader() bool { + return bb.loopHeader +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/basic_block_sort.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/basic_block_sort.go new file mode 100644 index 00000000000..fb98298f7f7 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/basic_block_sort.go @@ -0,0 +1,32 @@ +package ssa + +import ( + "slices" +) + +func sortBlocks(blocks []*basicBlock) { + slices.SortFunc(blocks, func(i, j *basicBlock) int { + jIsReturn := j.ReturnBlock() + iIsReturn := i.ReturnBlock() + if iIsReturn && jIsReturn { + return 0 + } + if jIsReturn { + return 1 + } + if iIsReturn { + return -1 + } + iRoot, jRoot := i.rootInstr, j.rootInstr + if iRoot == nil && jRoot == nil { // For testing. + return 0 + } + if jRoot == nil { + return 1 + } + if iRoot == nil { + return -1 + } + return i.rootInstr.id - j.rootInstr.id + }) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/builder.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/builder.go new file mode 100644 index 00000000000..43dd7d29286 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/builder.go @@ -0,0 +1,790 @@ +package ssa + +import ( + "fmt" + "sort" + "strings" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +// Builder is used to builds SSA consisting of Basic Blocks per function. +type Builder interface { + // Init must be called to reuse this builder for the next function. + Init(typ *Signature) + + // Signature returns the Signature of the currently-compiled function. + Signature() *Signature + + // BlockIDMax returns the maximum value of BasicBlocksID existing in the currently-compiled function. + BlockIDMax() BasicBlockID + + // AllocateBasicBlock creates a basic block in SSA function. + AllocateBasicBlock() BasicBlock + + // CurrentBlock returns the currently handled BasicBlock which is set by the latest call to SetCurrentBlock. + CurrentBlock() BasicBlock + + // EntryBlock returns the entry BasicBlock of the currently-compiled function. + EntryBlock() BasicBlock + + // SetCurrentBlock sets the instruction insertion target to the BasicBlock `b`. + SetCurrentBlock(b BasicBlock) + + // DeclareVariable declares a Variable of the given Type. + DeclareVariable(Type) Variable + + // DefineVariable defines a variable in the `block` with value. + // The defining instruction will be inserted into the `block`. + DefineVariable(variable Variable, value Value, block BasicBlock) + + // DefineVariableInCurrentBB is the same as DefineVariable except the definition is + // inserted into the current BasicBlock. Alias to DefineVariable(x, y, CurrentBlock()). + DefineVariableInCurrentBB(variable Variable, value Value) + + // AllocateInstruction returns a new Instruction. + AllocateInstruction() *Instruction + + // InsertInstruction executes BasicBlock.InsertInstruction for the currently handled basic block. + InsertInstruction(raw *Instruction) + + // allocateValue allocates an unused Value. + allocateValue(typ Type) Value + + // MustFindValue searches the latest definition of the given Variable and returns the result. + MustFindValue(variable Variable) Value + + // FindValueInLinearPath tries to find the latest definition of the given Variable in the linear path to the current BasicBlock. + // If it cannot find the definition, or it's not sealed yet, it returns ValueInvalid. + FindValueInLinearPath(variable Variable) Value + + // Seal declares that we've known all the predecessors to this block and were added via AddPred. + // After calling this, AddPred will be forbidden. + Seal(blk BasicBlock) + + // AnnotateValue is for debugging purpose. + AnnotateValue(value Value, annotation string) + + // DeclareSignature appends the *Signature to be referenced by various instructions (e.g. OpcodeCall). + DeclareSignature(signature *Signature) + + // Signatures returns the slice of declared Signatures. + Signatures() []*Signature + + // ResolveSignature returns the Signature which corresponds to SignatureID. + ResolveSignature(id SignatureID) *Signature + + // RunPasses runs various passes on the constructed SSA function. + RunPasses() + + // Format returns the debugging string of the SSA function. + Format() string + + // BlockIteratorBegin initializes the state to iterate over all the valid BasicBlock(s) compiled. + // Combined with BlockIteratorNext, we can use this like: + // + // for blk := builder.BlockIteratorBegin(); blk != nil; blk = builder.BlockIteratorNext() { + // // ... + // } + // + // The returned blocks are ordered in the order of AllocateBasicBlock being called. + BlockIteratorBegin() BasicBlock + + // BlockIteratorNext advances the state for iteration initialized by BlockIteratorBegin. + // Returns nil if there's no unseen BasicBlock. + BlockIteratorNext() BasicBlock + + // ValuesInfo returns the data per Value used to lower the SSA in backend. + // This is indexed by ValueID. + ValuesInfo() []ValueInfo + + // BlockIteratorReversePostOrderBegin is almost the same as BlockIteratorBegin except it returns the BasicBlock in the reverse post-order. + // This is available after RunPasses is run. + BlockIteratorReversePostOrderBegin() BasicBlock + + // BlockIteratorReversePostOrderNext is almost the same as BlockIteratorPostOrderNext except it returns the BasicBlock in the reverse post-order. + // This is available after RunPasses is run. + BlockIteratorReversePostOrderNext() BasicBlock + + // ReturnBlock returns the BasicBlock which is used to return from the function. + ReturnBlock() BasicBlock + + // InsertUndefined inserts an undefined instruction at the current position. + InsertUndefined() + + // SetCurrentSourceOffset sets the current source offset. The incoming instruction will be annotated with this offset. + SetCurrentSourceOffset(line SourceOffset) + + // LoopNestingForestRoots returns the roots of the loop nesting forest. + LoopNestingForestRoots() []BasicBlock + + // LowestCommonAncestor returns the lowest common ancestor in the dominator tree of the given BasicBlock(s). + LowestCommonAncestor(blk1, blk2 BasicBlock) BasicBlock + + // Idom returns the immediate dominator of the given BasicBlock. + Idom(blk BasicBlock) BasicBlock + + // VarLengthPool returns the VarLengthPool of Value. + VarLengthPool() *wazevoapi.VarLengthPool[Value] + + // InsertZeroValue inserts a zero value constant instruction of the given type. + InsertZeroValue(t Type) + + // BasicBlock returns the BasicBlock of the given ID. + BasicBlock(id BasicBlockID) BasicBlock + + // InstructionOfValue returns the Instruction that produces the given Value or nil if the Value is not produced by any Instruction. + InstructionOfValue(v Value) *Instruction +} + +// NewBuilder returns a new Builder implementation. +func NewBuilder() Builder { + return &builder{ + instructionsPool: wazevoapi.NewPool[Instruction](resetInstruction), + basicBlocksPool: wazevoapi.NewPool[basicBlock](resetBasicBlock), + varLengthBasicBlockPool: wazevoapi.NewVarLengthPool[BasicBlock](), + varLengthPool: wazevoapi.NewVarLengthPool[Value](), + valueAnnotations: make(map[ValueID]string), + signatures: make(map[SignatureID]*Signature), + returnBlk: &basicBlock{id: basicBlockIDReturnBlock}, + } +} + +// builder implements Builder interface. +type builder struct { + basicBlocksPool wazevoapi.Pool[basicBlock] + instructionsPool wazevoapi.Pool[Instruction] + varLengthPool wazevoapi.VarLengthPool[Value] + signatures map[SignatureID]*Signature + currentSignature *Signature + + // reversePostOrderedBasicBlocks are the BasicBlock(s) ordered in the reverse post-order after passCalculateImmediateDominators. + reversePostOrderedBasicBlocks []*basicBlock + currentBB *basicBlock + returnBlk *basicBlock + + // nextValueID is used by builder.AllocateValue. + nextValueID ValueID + // nextVariable is used by builder.AllocateVariable. + nextVariable Variable + + // valueAnnotations contains the annotations for each Value, only used for debugging. + valueAnnotations map[ValueID]string + + // valuesInfo contains the data per Value used to lower the SSA in backend. This is indexed by ValueID. + valuesInfo []ValueInfo + + // dominators stores the immediate dominator of each BasicBlock. + // The index is blockID of the BasicBlock. + dominators []*basicBlock + sparseTree dominatorSparseTree + + varLengthBasicBlockPool wazevoapi.VarLengthPool[BasicBlock] + + // loopNestingForestRoots are the roots of the loop nesting forest. + loopNestingForestRoots []BasicBlock + + // The followings are used for optimization passes/deterministic compilation. + instStack []*Instruction + blkStack []*basicBlock + blkStack2 []*basicBlock + redundantParams []redundantParam + + // blockIterCur is used to implement blockIteratorBegin and blockIteratorNext. + blockIterCur int + + // donePreBlockLayoutPasses is true if all the passes before LayoutBlocks are called. + donePreBlockLayoutPasses bool + // doneBlockLayout is true if LayoutBlocks is called. + doneBlockLayout bool + // donePostBlockLayoutPasses is true if all the passes after LayoutBlocks are called. + donePostBlockLayoutPasses bool + + currentSourceOffset SourceOffset + + // zeros are the zero value constants for each type. + zeros [typeEnd]Value +} + +// ValueInfo contains the data per Value used to lower the SSA in backend. +type ValueInfo struct { + // RefCount is the reference count of the Value. + RefCount uint32 + alias Value +} + +// redundantParam is a pair of the index of the redundant parameter and the Value. +// This is used to eliminate the redundant parameters in the optimization pass. +type redundantParam struct { + // index is the index of the redundant parameter in the basicBlock. + index int + // uniqueValue is the Value which is passed to the redundant parameter. + uniqueValue Value +} + +// BasicBlock implements Builder.BasicBlock. +func (b *builder) BasicBlock(id BasicBlockID) BasicBlock { + return b.basicBlock(id) +} + +func (b *builder) basicBlock(id BasicBlockID) *basicBlock { + if id == basicBlockIDReturnBlock { + return b.returnBlk + } + return b.basicBlocksPool.View(int(id)) +} + +// InsertZeroValue implements Builder.InsertZeroValue. +func (b *builder) InsertZeroValue(t Type) { + if b.zeros[t].Valid() { + return + } + zeroInst := b.AllocateInstruction() + switch t { + case TypeI32: + zeroInst.AsIconst32(0) + case TypeI64: + zeroInst.AsIconst64(0) + case TypeF32: + zeroInst.AsF32const(0) + case TypeF64: + zeroInst.AsF64const(0) + case TypeV128: + zeroInst.AsVconst(0, 0) + default: + panic("TODO: " + t.String()) + } + b.zeros[t] = zeroInst.Insert(b).Return() +} + +func (b *builder) VarLengthPool() *wazevoapi.VarLengthPool[Value] { + return &b.varLengthPool +} + +// ReturnBlock implements Builder.ReturnBlock. +func (b *builder) ReturnBlock() BasicBlock { + return b.returnBlk +} + +// Init implements Builder.Reset. +func (b *builder) Init(s *Signature) { + b.nextVariable = 0 + b.currentSignature = s + b.zeros = [typeEnd]Value{ValueInvalid, ValueInvalid, ValueInvalid, ValueInvalid, ValueInvalid, ValueInvalid} + resetBasicBlock(b.returnBlk) + b.instructionsPool.Reset() + b.basicBlocksPool.Reset() + b.varLengthPool.Reset() + b.varLengthBasicBlockPool.Reset() + b.donePreBlockLayoutPasses = false + b.doneBlockLayout = false + b.donePostBlockLayoutPasses = false + for _, sig := range b.signatures { + sig.used = false + } + + b.redundantParams = b.redundantParams[:0] + b.blkStack = b.blkStack[:0] + b.blkStack2 = b.blkStack2[:0] + b.dominators = b.dominators[:0] + b.loopNestingForestRoots = b.loopNestingForestRoots[:0] + b.basicBlocksPool.Reset() + + for v := ValueID(0); v < b.nextValueID; v++ { + delete(b.valueAnnotations, v) + b.valuesInfo[v] = ValueInfo{alias: ValueInvalid} + } + b.nextValueID = 0 + b.reversePostOrderedBasicBlocks = b.reversePostOrderedBasicBlocks[:0] + b.doneBlockLayout = false + b.currentSourceOffset = sourceOffsetUnknown +} + +// Signature implements Builder.Signature. +func (b *builder) Signature() *Signature { + return b.currentSignature +} + +// AnnotateValue implements Builder.AnnotateValue. +func (b *builder) AnnotateValue(value Value, a string) { + b.valueAnnotations[value.ID()] = a +} + +// AllocateInstruction implements Builder.AllocateInstruction. +func (b *builder) AllocateInstruction() *Instruction { + instr := b.instructionsPool.Allocate() + instr.id = b.instructionsPool.Allocated() + return instr +} + +// DeclareSignature implements Builder.AnnotateValue. +func (b *builder) DeclareSignature(s *Signature) { + b.signatures[s.ID] = s + s.used = false +} + +// Signatures implements Builder.Signatures. +func (b *builder) Signatures() (ret []*Signature) { + for _, sig := range b.signatures { + ret = append(ret, sig) + } + sort.Slice(ret, func(i, j int) bool { + return ret[i].ID < ret[j].ID + }) + return +} + +// SetCurrentSourceOffset implements Builder.SetCurrentSourceOffset. +func (b *builder) SetCurrentSourceOffset(l SourceOffset) { + b.currentSourceOffset = l +} + +func (b *builder) usedSignatures() (ret []*Signature) { + for _, sig := range b.signatures { + if sig.used { + ret = append(ret, sig) + } + } + sort.Slice(ret, func(i, j int) bool { + return ret[i].ID < ret[j].ID + }) + return +} + +// ResolveSignature implements Builder.ResolveSignature. +func (b *builder) ResolveSignature(id SignatureID) *Signature { + return b.signatures[id] +} + +// AllocateBasicBlock implements Builder.AllocateBasicBlock. +func (b *builder) AllocateBasicBlock() BasicBlock { + return b.allocateBasicBlock() +} + +// allocateBasicBlock allocates a new basicBlock. +func (b *builder) allocateBasicBlock() *basicBlock { + id := BasicBlockID(b.basicBlocksPool.Allocated()) + blk := b.basicBlocksPool.Allocate() + blk.id = id + return blk +} + +// Idom implements Builder.Idom. +func (b *builder) Idom(blk BasicBlock) BasicBlock { + return b.dominators[blk.ID()] +} + +// InsertInstruction implements Builder.InsertInstruction. +func (b *builder) InsertInstruction(instr *Instruction) { + b.currentBB.insertInstruction(b, instr) + + if l := b.currentSourceOffset; l.Valid() { + // Emit the source offset info only when the instruction has side effect because + // these are the only instructions that are accessed by stack unwinding. + // This reduces the significant amount of the offset info in the binary. + if instr.sideEffect() != sideEffectNone { + instr.annotateSourceOffset(l) + } + } + + resultTypesFn := instructionReturnTypes[instr.opcode] + if resultTypesFn == nil { + panic("TODO: " + instr.Format(b)) + } + + t1, ts := resultTypesFn(b, instr) + if t1.invalid() { + return + } + + r1 := b.allocateValue(t1) + instr.rValue = r1.setInstructionID(instr.id) + + tsl := len(ts) + if tsl == 0 { + return + } + + rValues := b.varLengthPool.Allocate(tsl) + for i := 0; i < tsl; i++ { + rn := b.allocateValue(ts[i]) + rValues = rValues.Append(&b.varLengthPool, rn.setInstructionID(instr.id)) + } + instr.rValues = rValues +} + +// DefineVariable implements Builder.DefineVariable. +func (b *builder) DefineVariable(variable Variable, value Value, block BasicBlock) { + bb := block.(*basicBlock) + bb.lastDefinitions[variable] = value +} + +// DefineVariableInCurrentBB implements Builder.DefineVariableInCurrentBB. +func (b *builder) DefineVariableInCurrentBB(variable Variable, value Value) { + b.DefineVariable(variable, value, b.currentBB) +} + +// SetCurrentBlock implements Builder.SetCurrentBlock. +func (b *builder) SetCurrentBlock(bb BasicBlock) { + b.currentBB = bb.(*basicBlock) +} + +// CurrentBlock implements Builder.CurrentBlock. +func (b *builder) CurrentBlock() BasicBlock { + return b.currentBB +} + +// EntryBlock implements Builder.EntryBlock. +func (b *builder) EntryBlock() BasicBlock { + return b.entryBlk() +} + +// DeclareVariable implements Builder.DeclareVariable. +func (b *builder) DeclareVariable(typ Type) Variable { + v := b.nextVariable + b.nextVariable++ + return v.setType(typ) +} + +// allocateValue implements Builder.AllocateValue. +func (b *builder) allocateValue(typ Type) (v Value) { + v = Value(b.nextValueID) + v = v.setType(typ) + b.nextValueID++ + return +} + +// FindValueInLinearPath implements Builder.FindValueInLinearPath. +func (b *builder) FindValueInLinearPath(variable Variable) Value { + return b.findValueInLinearPath(variable, b.currentBB) +} + +func (b *builder) findValueInLinearPath(variable Variable, blk *basicBlock) Value { + if val, ok := blk.lastDefinitions[variable]; ok { + return val + } else if !blk.sealed { + return ValueInvalid + } + + if pred := blk.singlePred; pred != nil { + // If this block is sealed and have only one predecessor, + // we can use the value in that block without ambiguity on definition. + return b.findValueInLinearPath(variable, pred) + } + if len(blk.preds) == 1 { + panic("BUG") + } + return ValueInvalid +} + +// MustFindValue implements Builder.MustFindValue. +func (b *builder) MustFindValue(variable Variable) Value { + return b.findValue(variable.getType(), variable, b.currentBB) +} + +// findValue recursively tries to find the latest definition of a `variable`. The algorithm is described in +// the section 2 of the paper https://link.springer.com/content/pdf/10.1007/978-3-642-37051-9_6.pdf. +// +// TODO: reimplement this in iterative, not recursive, to avoid stack overflow. +func (b *builder) findValue(typ Type, variable Variable, blk *basicBlock) Value { + if val, ok := blk.lastDefinitions[variable]; ok { + // The value is already defined in this block! + return val + } else if !blk.sealed { // Incomplete CFG as in the paper. + // If this is not sealed, that means it might have additional unknown predecessor later on. + // So we temporarily define the placeholder value here (not add as a parameter yet!), + // and record it as unknown. + // The unknown values are resolved when we call seal this block via BasicBlock.Seal(). + value := b.allocateValue(typ) + if wazevoapi.SSALoggingEnabled { + fmt.Printf("adding unknown value placeholder for %s at %d\n", variable, blk.id) + } + blk.lastDefinitions[variable] = value + blk.unknownValues = append(blk.unknownValues, unknownValue{ + variable: variable, + value: value, + }) + return value + } else if blk.EntryBlock() { + // If this is the entry block, we reach the uninitialized variable which has zero value. + return b.zeros[variable.getType()] + } + + if pred := blk.singlePred; pred != nil { + // If this block is sealed and have only one predecessor, + // we can use the value in that block without ambiguity on definition. + return b.findValue(typ, variable, pred) + } else if len(blk.preds) == 0 { + panic("BUG: value is not defined for " + variable.String()) + } + + // If this block has multiple predecessors, we have to gather the definitions, + // and treat them as an argument to this block. + // + // But before that, we have to check if the possible definitions are the same Value. + tmpValue := b.allocateValue(typ) + // Break the cycle by defining the variable with the tmpValue. + b.DefineVariable(variable, tmpValue, blk) + // Check all the predecessors if they have the same definition. + uniqueValue := ValueInvalid + for i := range blk.preds { + predValue := b.findValue(typ, variable, blk.preds[i].blk) + if uniqueValue == ValueInvalid { + uniqueValue = predValue + } else if uniqueValue != predValue { + uniqueValue = ValueInvalid + break + } + } + + if uniqueValue != ValueInvalid { + // If all the predecessors have the same definition, we can use that value. + b.alias(tmpValue, uniqueValue) + return uniqueValue + } else { + // Otherwise, add the tmpValue to this block as a parameter which may or may not be redundant, but + // later we eliminate trivial params in an optimization pass. This must be done before finding the + // definitions in the predecessors so that we can break the cycle. + blk.addParamOn(b, tmpValue) + // After the new param is added, we have to manipulate the original branching instructions + // in predecessors so that they would pass the definition of `variable` as the argument to + // the newly added PHI. + for i := range blk.preds { + pred := &blk.preds[i] + value := b.findValue(typ, variable, pred.blk) + pred.branch.addArgumentBranchInst(b, value) + } + return tmpValue + } +} + +// Seal implements Builder.Seal. +func (b *builder) Seal(raw BasicBlock) { + blk := raw.(*basicBlock) + if len(blk.preds) == 1 { + blk.singlePred = blk.preds[0].blk + } + blk.sealed = true + + for _, v := range blk.unknownValues { + variable, phiValue := v.variable, v.value + typ := variable.getType() + blk.addParamOn(b, phiValue) + for i := range blk.preds { + pred := &blk.preds[i] + predValue := b.findValue(typ, variable, pred.blk) + if !predValue.Valid() { + panic("BUG: value is not defined anywhere in the predecessors in the CFG") + } + pred.branch.addArgumentBranchInst(b, predValue) + } + } +} + +// Format implements Builder.Format. +func (b *builder) Format() string { + str := strings.Builder{} + usedSigs := b.usedSignatures() + if len(usedSigs) > 0 { + str.WriteByte('\n') + str.WriteString("signatures:\n") + for _, sig := range usedSigs { + str.WriteByte('\t') + str.WriteString(sig.String()) + str.WriteByte('\n') + } + } + + var iterBegin, iterNext func() *basicBlock + if b.doneBlockLayout { + iterBegin, iterNext = b.blockIteratorReversePostOrderBegin, b.blockIteratorReversePostOrderNext + } else { + iterBegin, iterNext = b.blockIteratorBegin, b.blockIteratorNext + } + for bb := iterBegin(); bb != nil; bb = iterNext() { + str.WriteByte('\n') + str.WriteString(bb.formatHeader(b)) + str.WriteByte('\n') + + for cur := bb.Root(); cur != nil; cur = cur.Next() { + str.WriteByte('\t') + str.WriteString(cur.Format(b)) + str.WriteByte('\n') + } + } + return str.String() +} + +// BlockIteratorNext implements Builder.BlockIteratorNext. +func (b *builder) BlockIteratorNext() BasicBlock { + if blk := b.blockIteratorNext(); blk == nil { + return nil // BasicBlock((*basicBlock)(nil)) != BasicBlock(nil) + } else { + return blk + } +} + +// BlockIteratorNext implements Builder.BlockIteratorNext. +func (b *builder) blockIteratorNext() *basicBlock { + index := b.blockIterCur + for { + if index == b.basicBlocksPool.Allocated() { + return nil + } + ret := b.basicBlocksPool.View(index) + index++ + if !ret.invalid { + b.blockIterCur = index + return ret + } + } +} + +// BlockIteratorBegin implements Builder.BlockIteratorBegin. +func (b *builder) BlockIteratorBegin() BasicBlock { + return b.blockIteratorBegin() +} + +// BlockIteratorBegin implements Builder.BlockIteratorBegin. +func (b *builder) blockIteratorBegin() *basicBlock { + b.blockIterCur = 0 + return b.blockIteratorNext() +} + +// BlockIteratorReversePostOrderBegin implements Builder.BlockIteratorReversePostOrderBegin. +func (b *builder) BlockIteratorReversePostOrderBegin() BasicBlock { + return b.blockIteratorReversePostOrderBegin() +} + +// BlockIteratorBegin implements Builder.BlockIteratorBegin. +func (b *builder) blockIteratorReversePostOrderBegin() *basicBlock { + b.blockIterCur = 0 + return b.blockIteratorReversePostOrderNext() +} + +// BlockIteratorReversePostOrderNext implements Builder.BlockIteratorReversePostOrderNext. +func (b *builder) BlockIteratorReversePostOrderNext() BasicBlock { + if blk := b.blockIteratorReversePostOrderNext(); blk == nil { + return nil // BasicBlock((*basicBlock)(nil)) != BasicBlock(nil) + } else { + return blk + } +} + +// BlockIteratorNext implements Builder.BlockIteratorNext. +func (b *builder) blockIteratorReversePostOrderNext() *basicBlock { + if b.blockIterCur >= len(b.reversePostOrderedBasicBlocks) { + return nil + } else { + ret := b.reversePostOrderedBasicBlocks[b.blockIterCur] + b.blockIterCur++ + return ret + } +} + +// ValuesInfo implements Builder.ValuesInfo. +func (b *builder) ValuesInfo() []ValueInfo { + return b.valuesInfo +} + +// alias records the alias of the given values. The alias(es) will be +// eliminated in the optimization pass via resolveArgumentAlias. +func (b *builder) alias(dst, src Value) { + did := int(dst.ID()) + if did >= len(b.valuesInfo) { + l := did + 1 - len(b.valuesInfo) + b.valuesInfo = append(b.valuesInfo, make([]ValueInfo, l)...) + view := b.valuesInfo[len(b.valuesInfo)-l:] + for i := range view { + view[i].alias = ValueInvalid + } + } + b.valuesInfo[did].alias = src +} + +// resolveArgumentAlias resolves the alias of the arguments of the given instruction. +func (b *builder) resolveArgumentAlias(instr *Instruction) { + if instr.v.Valid() { + instr.v = b.resolveAlias(instr.v) + } + + if instr.v2.Valid() { + instr.v2 = b.resolveAlias(instr.v2) + } + + if instr.v3.Valid() { + instr.v3 = b.resolveAlias(instr.v3) + } + + view := instr.vs.View() + for i, v := range view { + view[i] = b.resolveAlias(v) + } +} + +// resolveAlias resolves the alias of the given value. +func (b *builder) resolveAlias(v Value) Value { + info := b.valuesInfo + l := ValueID(len(info)) + // Some aliases are chained, so we need to resolve them recursively. + for { + vid := v.ID() + if vid < l && info[vid].alias.Valid() { + v = info[vid].alias + } else { + break + } + } + return v +} + +// entryBlk returns the entry block of the function. +func (b *builder) entryBlk() *basicBlock { + return b.basicBlocksPool.View(0) +} + +// isDominatedBy returns true if the given block `n` is dominated by the given block `d`. +// Before calling this, the builder must pass by passCalculateImmediateDominators. +func (b *builder) isDominatedBy(n *basicBlock, d *basicBlock) bool { + if len(b.dominators) == 0 { + panic("BUG: passCalculateImmediateDominators must be called before calling isDominatedBy") + } + ent := b.entryBlk() + doms := b.dominators + for n != d && n != ent { + n = doms[n.id] + } + return n == d +} + +// BlockIDMax implements Builder.BlockIDMax. +func (b *builder) BlockIDMax() BasicBlockID { + return BasicBlockID(b.basicBlocksPool.Allocated()) +} + +// InsertUndefined implements Builder.InsertUndefined. +func (b *builder) InsertUndefined() { + instr := b.AllocateInstruction() + instr.opcode = OpcodeUndefined + b.InsertInstruction(instr) +} + +// LoopNestingForestRoots implements Builder.LoopNestingForestRoots. +func (b *builder) LoopNestingForestRoots() []BasicBlock { + return b.loopNestingForestRoots +} + +// LowestCommonAncestor implements Builder.LowestCommonAncestor. +func (b *builder) LowestCommonAncestor(blk1, blk2 BasicBlock) BasicBlock { + return b.sparseTree.findLCA(blk1.ID(), blk2.ID()) +} + +// InstructionOfValue returns the instruction that produces the given Value, or nil +// if the Value is not produced by any instruction. +func (b *builder) InstructionOfValue(v Value) *Instruction { + instrID := v.instructionID() + if instrID <= 0 { + return nil + } + return b.instructionsPool.View(instrID - 1) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/cmp.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/cmp.go new file mode 100644 index 00000000000..15b62ca8ee4 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/cmp.go @@ -0,0 +1,107 @@ +package ssa + +// IntegerCmpCond represents a condition for integer comparison. +type IntegerCmpCond byte + +const ( + // IntegerCmpCondInvalid represents an invalid condition. + IntegerCmpCondInvalid IntegerCmpCond = iota + // IntegerCmpCondEqual represents "==". + IntegerCmpCondEqual + // IntegerCmpCondNotEqual represents "!=". + IntegerCmpCondNotEqual + // IntegerCmpCondSignedLessThan represents Signed "<". + IntegerCmpCondSignedLessThan + // IntegerCmpCondSignedGreaterThanOrEqual represents Signed ">=". + IntegerCmpCondSignedGreaterThanOrEqual + // IntegerCmpCondSignedGreaterThan represents Signed ">". + IntegerCmpCondSignedGreaterThan + // IntegerCmpCondSignedLessThanOrEqual represents Signed "<=". + IntegerCmpCondSignedLessThanOrEqual + // IntegerCmpCondUnsignedLessThan represents Unsigned "<". + IntegerCmpCondUnsignedLessThan + // IntegerCmpCondUnsignedGreaterThanOrEqual represents Unsigned ">=". + IntegerCmpCondUnsignedGreaterThanOrEqual + // IntegerCmpCondUnsignedGreaterThan represents Unsigned ">". + IntegerCmpCondUnsignedGreaterThan + // IntegerCmpCondUnsignedLessThanOrEqual represents Unsigned "<=". + IntegerCmpCondUnsignedLessThanOrEqual +) + +// String implements fmt.Stringer. +func (i IntegerCmpCond) String() string { + switch i { + case IntegerCmpCondEqual: + return "eq" + case IntegerCmpCondNotEqual: + return "neq" + case IntegerCmpCondSignedLessThan: + return "lt_s" + case IntegerCmpCondSignedGreaterThanOrEqual: + return "ge_s" + case IntegerCmpCondSignedGreaterThan: + return "gt_s" + case IntegerCmpCondSignedLessThanOrEqual: + return "le_s" + case IntegerCmpCondUnsignedLessThan: + return "lt_u" + case IntegerCmpCondUnsignedGreaterThanOrEqual: + return "ge_u" + case IntegerCmpCondUnsignedGreaterThan: + return "gt_u" + case IntegerCmpCondUnsignedLessThanOrEqual: + return "le_u" + default: + panic("invalid integer comparison condition") + } +} + +// Signed returns true if the condition is signed integer comparison. +func (i IntegerCmpCond) Signed() bool { + switch i { + case IntegerCmpCondSignedLessThan, IntegerCmpCondSignedGreaterThanOrEqual, + IntegerCmpCondSignedGreaterThan, IntegerCmpCondSignedLessThanOrEqual: + return true + default: + return false + } +} + +type FloatCmpCond byte + +const ( + // FloatCmpCondInvalid represents an invalid condition. + FloatCmpCondInvalid FloatCmpCond = iota + // FloatCmpCondEqual represents "==". + FloatCmpCondEqual + // FloatCmpCondNotEqual represents "!=". + FloatCmpCondNotEqual + // FloatCmpCondLessThan represents "<". + FloatCmpCondLessThan + // FloatCmpCondLessThanOrEqual represents "<=". + FloatCmpCondLessThanOrEqual + // FloatCmpCondGreaterThan represents ">". + FloatCmpCondGreaterThan + // FloatCmpCondGreaterThanOrEqual represents ">=". + FloatCmpCondGreaterThanOrEqual +) + +// String implements fmt.Stringer. +func (f FloatCmpCond) String() string { + switch f { + case FloatCmpCondEqual: + return "eq" + case FloatCmpCondNotEqual: + return "neq" + case FloatCmpCondLessThan: + return "lt" + case FloatCmpCondLessThanOrEqual: + return "le" + case FloatCmpCondGreaterThan: + return "gt" + case FloatCmpCondGreaterThanOrEqual: + return "ge" + default: + panic("invalid float comparison condition") + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/funcref.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/funcref.go new file mode 100644 index 00000000000..d9620762aea --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/funcref.go @@ -0,0 +1,12 @@ +package ssa + +import "fmt" + +// FuncRef is a unique identifier for a function of the frontend, +// and is used to reference the function in function call. +type FuncRef uint32 + +// String implements fmt.Stringer. +func (r FuncRef) String() string { + return fmt.Sprintf("f%d", r) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/instructions.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/instructions.go new file mode 100644 index 00000000000..7b37a8afe04 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/instructions.go @@ -0,0 +1,3024 @@ +package ssa + +import ( + "fmt" + "math" + "strings" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +// Opcode represents a SSA instruction. +type Opcode uint32 + +// Instruction represents an instruction whose opcode is specified by +// Opcode. Since Go doesn't have union type, we use this flattened type +// for all instructions, and therefore each field has different meaning +// depending on Opcode. +type Instruction struct { + // id is the unique ID of this instruction which ascends from 0 following the order of program. + id int + opcode Opcode + u1, u2 uint64 + v Value + v2 Value + v3 Value + vs Values + typ Type + prev, next *Instruction + + // rValue is the (first) return value of this instruction. + // For branching instructions except for OpcodeBrTable, they hold BlockID to jump cast to Value. + rValue Value + // rValues are the rest of the return values of this instruction. + // For OpcodeBrTable, it holds the list of BlockID to jump cast to Value. + rValues Values + gid InstructionGroupID + sourceOffset SourceOffset + live bool + alreadyLowered bool +} + +// SourceOffset represents the offset of the source of an instruction. +type SourceOffset int64 + +const sourceOffsetUnknown = -1 + +// Valid returns true if this source offset is valid. +func (l SourceOffset) Valid() bool { + return l != sourceOffsetUnknown +} + +func (i *Instruction) annotateSourceOffset(line SourceOffset) { + i.sourceOffset = line +} + +// SourceOffset returns the source offset of this instruction. +func (i *Instruction) SourceOffset() SourceOffset { + return i.sourceOffset +} + +// Opcode returns the opcode of this instruction. +func (i *Instruction) Opcode() Opcode { + return i.opcode +} + +// GroupID returns the InstructionGroupID of this instruction. +func (i *Instruction) GroupID() InstructionGroupID { + return i.gid +} + +// MarkLowered marks this instruction as already lowered. +func (i *Instruction) MarkLowered() { + i.alreadyLowered = true +} + +// Lowered returns true if this instruction is already lowered. +func (i *Instruction) Lowered() bool { + return i.alreadyLowered +} + +// resetInstruction resets this instruction to the initial state. +func resetInstruction(i *Instruction) { + *i = Instruction{} + i.v = ValueInvalid + i.v2 = ValueInvalid + i.v3 = ValueInvalid + i.rValue = ValueInvalid + i.typ = typeInvalid + i.vs = ValuesNil + i.sourceOffset = sourceOffsetUnknown +} + +// InstructionGroupID is assigned to each instruction and represents a group of instructions +// where each instruction is interchangeable with others except for the last instruction +// in the group which has side effects. In short, InstructionGroupID is determined by the side effects of instructions. +// That means, if there's an instruction with side effect between two instructions, then these two instructions +// will have different instructionGroupID. Note that each block always ends with branching, which is with side effects, +// therefore, instructions in different blocks always have different InstructionGroupID(s). +// +// The notable application of this is used in lowering SSA-level instruction to a ISA specific instruction, +// where we eagerly try to merge multiple instructions into single operation etc. Such merging cannot be done +// if these instruction have different InstructionGroupID since it will change the semantics of a program. +// +// See passDeadCodeElimination. +type InstructionGroupID uint32 + +// Returns Value(s) produced by this instruction if any. +// The `first` is the first return value, and `rest` is the rest of the values. +func (i *Instruction) Returns() (first Value, rest []Value) { + if i.IsBranching() { + return ValueInvalid, nil + } + return i.rValue, i.rValues.View() +} + +// Return returns a Value(s) produced by this instruction if any. +// If there's multiple return values, only the first one is returned. +func (i *Instruction) Return() (first Value) { + return i.rValue +} + +// Args returns the arguments to this instruction. +func (i *Instruction) Args() (v1, v2, v3 Value, vs []Value) { + return i.v, i.v2, i.v3, i.vs.View() +} + +// Arg returns the first argument to this instruction. +func (i *Instruction) Arg() Value { + return i.v +} + +// Arg2 returns the first two arguments to this instruction. +func (i *Instruction) Arg2() (Value, Value) { + return i.v, i.v2 +} + +// ArgWithLane returns the first argument to this instruction, and the lane type. +func (i *Instruction) ArgWithLane() (Value, VecLane) { + return i.v, VecLane(i.u1) +} + +// Arg2WithLane returns the first two arguments to this instruction, and the lane type. +func (i *Instruction) Arg2WithLane() (Value, Value, VecLane) { + return i.v, i.v2, VecLane(i.u1) +} + +// ShuffleData returns the first two arguments to this instruction and 2 uint64s `lo`, `hi`. +// +// Note: Each uint64 encodes a sequence of 8 bytes where each byte encodes a VecLane, +// so that the 128bit integer `hi<<64|lo` packs a slice `[16]VecLane`, +// where `lane[0]` is the least significant byte, and `lane[n]` is shifted to offset `n*8`. +func (i *Instruction) ShuffleData() (v Value, v2 Value, lo uint64, hi uint64) { + return i.v, i.v2, i.u1, i.u2 +} + +// Arg3 returns the first three arguments to this instruction. +func (i *Instruction) Arg3() (Value, Value, Value) { + return i.v, i.v2, i.v3 +} + +// Next returns the next instruction laid out next to itself. +func (i *Instruction) Next() *Instruction { + return i.next +} + +// Prev returns the previous instruction laid out prior to itself. +func (i *Instruction) Prev() *Instruction { + return i.prev +} + +// IsBranching returns true if this instruction is a branching instruction. +func (i *Instruction) IsBranching() bool { + switch i.opcode { + case OpcodeJump, OpcodeBrz, OpcodeBrnz, OpcodeBrTable: + return true + default: + return false + } +} + +// TODO: complete opcode comments. +const ( + OpcodeInvalid Opcode = iota + + // OpcodeUndefined is a placeholder for undefined opcode. This can be used for debugging to intentionally + // cause a crash at certain point. + OpcodeUndefined + + // OpcodeJump takes the list of args to the `block` and unconditionally jumps to it. + OpcodeJump + + // OpcodeBrz branches into `blk` with `args` if the value `c` equals zero: `Brz c, blk, args`. + OpcodeBrz + + // OpcodeBrnz branches into `blk` with `args` if the value `c` is not zero: `Brnz c, blk, args`. + OpcodeBrnz + + // OpcodeBrTable takes the index value `index`, and branches into `labelX`. If the `index` is out of range, + // it branches into the last labelN: `BrTable index, [label1, label2, ... labelN]`. + OpcodeBrTable + + // OpcodeExitWithCode exit the execution immediately. + OpcodeExitWithCode + + // OpcodeExitIfTrueWithCode exits the execution immediately if the value `c` is not zero. + OpcodeExitIfTrueWithCode + + // OpcodeReturn returns from the function: `return rvalues`. + OpcodeReturn + + // OpcodeCall calls a function specified by the symbol FN with arguments `args`: `returnvals = Call FN, args...` + // This is a "near" call, which means the call target is known at compile time, and the target is relatively close + // to this function. If the target cannot be reached by near call, the backend fails to compile. + OpcodeCall + + // OpcodeCallIndirect calls a function specified by `callee` which is a function address: `returnvals = call_indirect SIG, callee, args`. + // Note that this is different from call_indirect in Wasm, which also does type checking, etc. + OpcodeCallIndirect + + // OpcodeSplat performs a vector splat operation: `v = Splat.lane x`. + OpcodeSplat + + // OpcodeSwizzle performs a vector swizzle operation: `v = Swizzle.lane x, y`. + OpcodeSwizzle + + // OpcodeInsertlane inserts a lane value into a vector: `v = InsertLane x, y, Idx`. + OpcodeInsertlane + + // OpcodeExtractlane extracts a lane value from a vector: `v = ExtractLane x, Idx`. + OpcodeExtractlane + + // OpcodeLoad loads a Type value from the [base + offset] address: `v = Load base, offset`. + OpcodeLoad + + // OpcodeStore stores a Type value to the [base + offset] address: `Store v, base, offset`. + OpcodeStore + + // OpcodeUload8 loads the 8-bit value from the [base + offset] address, zero-extended to 64 bits: `v = Uload8 base, offset`. + OpcodeUload8 + + // OpcodeSload8 loads the 8-bit value from the [base + offset] address, sign-extended to 64 bits: `v = Sload8 base, offset`. + OpcodeSload8 + + // OpcodeIstore8 stores the 8-bit value to the [base + offset] address, sign-extended to 64 bits: `Istore8 v, base, offset`. + OpcodeIstore8 + + // OpcodeUload16 loads the 16-bit value from the [base + offset] address, zero-extended to 64 bits: `v = Uload16 base, offset`. + OpcodeUload16 + + // OpcodeSload16 loads the 16-bit value from the [base + offset] address, sign-extended to 64 bits: `v = Sload16 base, offset`. + OpcodeSload16 + + // OpcodeIstore16 stores the 16-bit value to the [base + offset] address, zero-extended to 64 bits: `Istore16 v, base, offset`. + OpcodeIstore16 + + // OpcodeUload32 loads the 32-bit value from the [base + offset] address, zero-extended to 64 bits: `v = Uload32 base, offset`. + OpcodeUload32 + + // OpcodeSload32 loads the 32-bit value from the [base + offset] address, sign-extended to 64 bits: `v = Sload32 base, offset`. + OpcodeSload32 + + // OpcodeIstore32 stores the 32-bit value to the [base + offset] address, zero-extended to 64 bits: `Istore16 v, base, offset`. + OpcodeIstore32 + + // OpcodeLoadSplat represents a load that replicates the loaded value to all lanes `v = LoadSplat.lane p, Offset`. + OpcodeLoadSplat + + // OpcodeVZeroExtLoad loads a scalar single/double precision floating point value from the [p + Offset] address, + // and zero-extend it to the V128 value: `v = VExtLoad p, Offset`. + OpcodeVZeroExtLoad + + // OpcodeIconst represents the integer const. + OpcodeIconst + + // OpcodeF32const represents the single-precision const. + OpcodeF32const + + // OpcodeF64const represents the double-precision const. + OpcodeF64const + + // OpcodeVconst represents the 128bit vector const. + OpcodeVconst + + // OpcodeVbor computes binary or between two 128bit vectors: `v = bor x, y`. + OpcodeVbor + + // OpcodeVbxor computes binary xor between two 128bit vectors: `v = bxor x, y`. + OpcodeVbxor + + // OpcodeVband computes binary and between two 128bit vectors: `v = band x, y`. + OpcodeVband + + // OpcodeVbandnot computes binary and-not between two 128bit vectors: `v = bandnot x, y`. + OpcodeVbandnot + + // OpcodeVbnot negates a 128bit vector: `v = bnot x`. + OpcodeVbnot + + // OpcodeVbitselect uses the bits in the control mask c to select the corresponding bit from x when 1 + // and y when 0: `v = bitselect c, x, y`. + OpcodeVbitselect + + // OpcodeShuffle shuffles two vectors using the given 128-bit immediate: `v = shuffle imm, x, y`. + // For each byte in the immediate, a value i in [0, 15] selects the i-th byte in vector x; + // i in [16, 31] selects the (i-16)-th byte in vector y. + OpcodeShuffle + + // OpcodeSelect chooses between two values based on a condition `c`: `v = Select c, x, y`. + OpcodeSelect + + // OpcodeVanyTrue performs a any true operation: `s = VanyTrue a`. + OpcodeVanyTrue + + // OpcodeVallTrue performs a lane-wise all true operation: `s = VallTrue.lane a`. + OpcodeVallTrue + + // OpcodeVhighBits performs a lane-wise extract of the high bits: `v = VhighBits.lane a`. + OpcodeVhighBits + + // OpcodeIcmp compares two integer values with the given condition: `v = icmp Cond, x, y`. + OpcodeIcmp + + // OpcodeVIcmp compares two integer values with the given condition: `v = vicmp Cond, x, y` on vector. + OpcodeVIcmp + + // OpcodeIcmpImm compares an integer value with the immediate value on the given condition: `v = icmp_imm Cond, x, Y`. + OpcodeIcmpImm + + // OpcodeIadd performs an integer addition: `v = Iadd x, y`. + OpcodeIadd + + // OpcodeVIadd performs an integer addition: `v = VIadd.lane x, y` on vector. + OpcodeVIadd + + // OpcodeVSaddSat performs a signed saturating vector addition: `v = VSaddSat.lane x, y` on vector. + OpcodeVSaddSat + + // OpcodeVUaddSat performs an unsigned saturating vector addition: `v = VUaddSat.lane x, y` on vector. + OpcodeVUaddSat + + // OpcodeIsub performs an integer subtraction: `v = Isub x, y`. + OpcodeIsub + + // OpcodeVIsub performs an integer subtraction: `v = VIsub.lane x, y` on vector. + OpcodeVIsub + + // OpcodeVSsubSat performs a signed saturating vector subtraction: `v = VSsubSat.lane x, y` on vector. + OpcodeVSsubSat + + // OpcodeVUsubSat performs an unsigned saturating vector subtraction: `v = VUsubSat.lane x, y` on vector. + OpcodeVUsubSat + + // OpcodeVImin performs a signed integer min: `v = VImin.lane x, y` on vector. + OpcodeVImin + + // OpcodeVUmin performs an unsigned integer min: `v = VUmin.lane x, y` on vector. + OpcodeVUmin + + // OpcodeVImax performs a signed integer max: `v = VImax.lane x, y` on vector. + OpcodeVImax + + // OpcodeVUmax performs an unsigned integer max: `v = VUmax.lane x, y` on vector. + OpcodeVUmax + + // OpcodeVAvgRound performs an unsigned integer avg, truncating to zero: `v = VAvgRound.lane x, y` on vector. + OpcodeVAvgRound + + // OpcodeVImul performs an integer multiplication: `v = VImul.lane x, y` on vector. + OpcodeVImul + + // OpcodeVIneg negates the given integer vector value: `v = VIneg x`. + OpcodeVIneg + + // OpcodeVIpopcnt counts the number of 1-bits in the given vector: `v = VIpopcnt x`. + OpcodeVIpopcnt + + // OpcodeVIabs returns the absolute value for the given vector value: `v = VIabs.lane x`. + OpcodeVIabs + + // OpcodeVIshl shifts x left by (y mod lane-width): `v = VIshl.lane x, y` on vector. + OpcodeVIshl + + // OpcodeVUshr shifts x right by (y mod lane-width), unsigned: `v = VUshr.lane x, y` on vector. + OpcodeVUshr + + // OpcodeVSshr shifts x right by (y mod lane-width), signed: `v = VSshr.lane x, y` on vector. + OpcodeVSshr + + // OpcodeVFabs takes the absolute value of a floating point value: `v = VFabs.lane x on vector. + OpcodeVFabs + + // OpcodeVFmax takes the maximum of two floating point values: `v = VFmax.lane x, y on vector. + OpcodeVFmax + + // OpcodeVFmin takes the minimum of two floating point values: `v = VFmin.lane x, y on vector. + OpcodeVFmin + + // OpcodeVFneg negates the given floating point vector value: `v = VFneg x`. + OpcodeVFneg + + // OpcodeVFadd performs a floating point addition: `v = VFadd.lane x, y` on vector. + OpcodeVFadd + + // OpcodeVFsub performs a floating point subtraction: `v = VFsub.lane x, y` on vector. + OpcodeVFsub + + // OpcodeVFmul performs a floating point multiplication: `v = VFmul.lane x, y` on vector. + OpcodeVFmul + + // OpcodeVFdiv performs a floating point division: `v = VFdiv.lane x, y` on vector. + OpcodeVFdiv + + // OpcodeVFcmp compares two float values with the given condition: `v = VFcmp.lane Cond, x, y` on float. + OpcodeVFcmp + + // OpcodeVCeil takes the ceiling of the given floating point value: `v = ceil.lane x` on vector. + OpcodeVCeil + + // OpcodeVFloor takes the floor of the given floating point value: `v = floor.lane x` on vector. + OpcodeVFloor + + // OpcodeVTrunc takes the truncation of the given floating point value: `v = trunc.lane x` on vector. + OpcodeVTrunc + + // OpcodeVNearest takes the nearest integer of the given floating point value: `v = nearest.lane x` on vector. + OpcodeVNearest + + // OpcodeVMaxPseudo computes the lane-wise maximum value `v = VMaxPseudo.lane x, y` on vector defined as `x < y ? x : y`. + OpcodeVMaxPseudo + + // OpcodeVMinPseudo computes the lane-wise minimum value `v = VMinPseudo.lane x, y` on vector defined as `y < x ? x : y`. + OpcodeVMinPseudo + + // OpcodeVSqrt takes the minimum of two floating point values: `v = VFmin.lane x, y` on vector. + OpcodeVSqrt + + // OpcodeVFcvtToUintSat converts a floating point value to an unsigned integer: `v = FcvtToUintSat.lane x` on vector. + OpcodeVFcvtToUintSat + + // OpcodeVFcvtToSintSat converts a floating point value to a signed integer: `v = VFcvtToSintSat.lane x` on vector. + OpcodeVFcvtToSintSat + + // OpcodeVFcvtFromUint converts a floating point value from an unsigned integer: `v = FcvtFromUint.lane x` on vector. + // x is always a 32-bit integer lane, and the result is either a 32-bit or 64-bit floating point-sized vector. + OpcodeVFcvtFromUint + + // OpcodeVFcvtFromSint converts a floating point value from a signed integer: `v = VFcvtFromSint.lane x` on vector. + // x is always a 32-bit integer lane, and the result is either a 32-bit or 64-bit floating point-sized vector. + OpcodeVFcvtFromSint + + // OpcodeImul performs an integer multiplication: `v = Imul x, y`. + OpcodeImul + + // OpcodeUdiv performs the unsigned integer division `v = Udiv x, y`. + OpcodeUdiv + + // OpcodeSdiv performs the signed integer division `v = Sdiv x, y`. + OpcodeSdiv + + // OpcodeUrem computes the remainder of the unsigned integer division `v = Urem x, y`. + OpcodeUrem + + // OpcodeSrem computes the remainder of the signed integer division `v = Srem x, y`. + OpcodeSrem + + // OpcodeBand performs a binary and: `v = Band x, y`. + OpcodeBand + + // OpcodeBor performs a binary or: `v = Bor x, y`. + OpcodeBor + + // OpcodeBxor performs a binary xor: `v = Bxor x, y`. + OpcodeBxor + + // OpcodeBnot performs a binary not: `v = Bnot x`. + OpcodeBnot + + // OpcodeRotl rotates the given integer value to the left: `v = Rotl x, y`. + OpcodeRotl + + // OpcodeRotr rotates the given integer value to the right: `v = Rotr x, y`. + OpcodeRotr + + // OpcodeIshl does logical shift left: `v = Ishl x, y`. + OpcodeIshl + + // OpcodeUshr does logical shift right: `v = Ushr x, y`. + OpcodeUshr + + // OpcodeSshr does arithmetic shift right: `v = Sshr x, y`. + OpcodeSshr + + // OpcodeClz counts the number of leading zeros: `v = clz x`. + OpcodeClz + + // OpcodeCtz counts the number of trailing zeros: `v = ctz x`. + OpcodeCtz + + // OpcodePopcnt counts the number of 1-bits: `v = popcnt x`. + OpcodePopcnt + + // OpcodeFcmp compares two floating point values: `v = fcmp Cond, x, y`. + OpcodeFcmp + + // OpcodeFadd performs a floating point addition: / `v = Fadd x, y`. + OpcodeFadd + + // OpcodeFsub performs a floating point subtraction: `v = Fsub x, y`. + OpcodeFsub + + // OpcodeFmul performs a floating point multiplication: `v = Fmul x, y`. + OpcodeFmul + + // OpcodeSqmulRoundSat performs a lane-wise saturating rounding multiplication + // in Q15 format: `v = SqmulRoundSat.lane x,y` on vector. + OpcodeSqmulRoundSat + + // OpcodeFdiv performs a floating point division: `v = Fdiv x, y`. + OpcodeFdiv + + // OpcodeSqrt takes the square root of the given floating point value: `v = sqrt x`. + OpcodeSqrt + + // OpcodeFneg negates the given floating point value: `v = Fneg x`. + OpcodeFneg + + // OpcodeFabs takes the absolute value of the given floating point value: `v = fabs x`. + OpcodeFabs + + // OpcodeFcopysign copies the sign of the second floating point value to the first floating point value: + // `v = Fcopysign x, y`. + OpcodeFcopysign + + // OpcodeFmin takes the minimum of two floating point values: `v = fmin x, y`. + OpcodeFmin + + // OpcodeFmax takes the maximum of two floating point values: `v = fmax x, y`. + OpcodeFmax + + // OpcodeCeil takes the ceiling of the given floating point value: `v = ceil x`. + OpcodeCeil + + // OpcodeFloor takes the floor of the given floating point value: `v = floor x`. + OpcodeFloor + + // OpcodeTrunc takes the truncation of the given floating point value: `v = trunc x`. + OpcodeTrunc + + // OpcodeNearest takes the nearest integer of the given floating point value: `v = nearest x`. + OpcodeNearest + + // OpcodeBitcast is a bitcast operation: `v = bitcast x`. + OpcodeBitcast + + // OpcodeIreduce narrow the given integer: `v = Ireduce x`. + OpcodeIreduce + + // OpcodeSnarrow converts two input vectors x, y into a smaller lane vector by narrowing each lane, signed `v = Snarrow.lane x, y`. + OpcodeSnarrow + + // OpcodeUnarrow converts two input vectors x, y into a smaller lane vector by narrowing each lane, unsigned `v = Unarrow.lane x, y`. + OpcodeUnarrow + + // OpcodeSwidenLow converts low half of the smaller lane vector to a larger lane vector, sign extended: `v = SwidenLow.lane x`. + OpcodeSwidenLow + + // OpcodeSwidenHigh converts high half of the smaller lane vector to a larger lane vector, sign extended: `v = SwidenHigh.lane x`. + OpcodeSwidenHigh + + // OpcodeUwidenLow converts low half of the smaller lane vector to a larger lane vector, zero (unsigned) extended: `v = UwidenLow.lane x`. + OpcodeUwidenLow + + // OpcodeUwidenHigh converts high half of the smaller lane vector to a larger lane vector, zero (unsigned) extended: `v = UwidenHigh.lane x`. + OpcodeUwidenHigh + + // OpcodeExtIaddPairwise is a lane-wise integer extended pairwise addition producing extended results (twice wider results than the inputs): `v = extiadd_pairwise x, y` on vector. + OpcodeExtIaddPairwise + + // OpcodeWideningPairwiseDotProductS is a lane-wise widening pairwise dot product with signed saturation: `v = WideningPairwiseDotProductS x, y` on vector. + // Currently, the only lane is i16, and the result is i32. + OpcodeWideningPairwiseDotProductS + + // OpcodeUExtend zero-extends the given integer: `v = UExtend x, from->to`. + OpcodeUExtend + + // OpcodeSExtend sign-extends the given integer: `v = SExtend x, from->to`. + OpcodeSExtend + + // OpcodeFpromote promotes the given floating point value: `v = Fpromote x`. + OpcodeFpromote + + // OpcodeFvpromoteLow converts the two lower single-precision floating point lanes + // to the two double-precision lanes of the result: `v = FvpromoteLow.lane x` on vector. + OpcodeFvpromoteLow + + // OpcodeFdemote demotes the given float point value: `v = Fdemote x`. + OpcodeFdemote + + // OpcodeFvdemote converts the two double-precision floating point lanes + // to two lower single-precision lanes of the result `v = Fvdemote.lane x`. + OpcodeFvdemote + + // OpcodeFcvtToUint converts a floating point value to an unsigned integer: `v = FcvtToUint x`. + OpcodeFcvtToUint + + // OpcodeFcvtToSint converts a floating point value to a signed integer: `v = FcvtToSint x`. + OpcodeFcvtToSint + + // OpcodeFcvtToUintSat converts a floating point value to an unsigned integer: `v = FcvtToUintSat x` which saturates on overflow. + OpcodeFcvtToUintSat + + // OpcodeFcvtToSintSat converts a floating point value to a signed integer: `v = FcvtToSintSat x` which saturates on overflow. + OpcodeFcvtToSintSat + + // OpcodeFcvtFromUint converts an unsigned integer to a floating point value: `v = FcvtFromUint x`. + OpcodeFcvtFromUint + + // OpcodeFcvtFromSint converts a signed integer to a floating point value: `v = FcvtFromSint x`. + OpcodeFcvtFromSint + + // OpcodeAtomicRmw is atomic read-modify-write operation: `v = atomic_rmw op, p, offset, value`. + OpcodeAtomicRmw + + // OpcodeAtomicCas is atomic compare-and-swap operation. + OpcodeAtomicCas + + // OpcodeAtomicLoad is atomic load operation. + OpcodeAtomicLoad + + // OpcodeAtomicStore is atomic store operation. + OpcodeAtomicStore + + // OpcodeFence is a memory fence operation. + OpcodeFence + + // OpcodeTailCallReturnCall is the equivalent of OpcodeCall (a "near" call) + // for tail calls. Semantically, it combines Call + Return into a single operation. + OpcodeTailCallReturnCall + + // OpcodeTailCallReturnCallIndirect is the equivalent of OpcodeCallIndirect (a call to a function address) + // for tail calls. Semantically, it combines CallIndirect + Return into a single operation. + OpcodeTailCallReturnCallIndirect + + // opcodeEnd marks the end of the opcode list. + opcodeEnd +) + +// AtomicRmwOp represents the atomic read-modify-write operation. +type AtomicRmwOp byte + +const ( + // AtomicRmwOpAdd is an atomic add operation. + AtomicRmwOpAdd AtomicRmwOp = iota + // AtomicRmwOpSub is an atomic sub operation. + AtomicRmwOpSub + // AtomicRmwOpAnd is an atomic and operation. + AtomicRmwOpAnd + // AtomicRmwOpOr is an atomic or operation. + AtomicRmwOpOr + // AtomicRmwOpXor is an atomic xor operation. + AtomicRmwOpXor + // AtomicRmwOpXchg is an atomic swap operation. + AtomicRmwOpXchg +) + +// String implements the fmt.Stringer. +func (op AtomicRmwOp) String() string { + switch op { + case AtomicRmwOpAdd: + return "add" + case AtomicRmwOpSub: + return "sub" + case AtomicRmwOpAnd: + return "and" + case AtomicRmwOpOr: + return "or" + case AtomicRmwOpXor: + return "xor" + case AtomicRmwOpXchg: + return "xchg" + } + panic(fmt.Sprintf("unknown AtomicRmwOp: %d", op)) +} + +// returnTypesFn provides the info to determine the type of instruction. +// t1 is the type of the first result, ts are the types of the remaining results. +type returnTypesFn func(b *builder, instr *Instruction) (t1 Type, ts []Type) + +var ( + returnTypesFnNoReturns returnTypesFn = func(b *builder, instr *Instruction) (t1 Type, ts []Type) { return typeInvalid, nil } + returnTypesFnSingle = func(b *builder, instr *Instruction) (t1 Type, ts []Type) { return instr.typ, nil } + returnTypesFnI32 = func(b *builder, instr *Instruction) (t1 Type, ts []Type) { return TypeI32, nil } + returnTypesFnF32 = func(b *builder, instr *Instruction) (t1 Type, ts []Type) { return TypeF32, nil } + returnTypesFnF64 = func(b *builder, instr *Instruction) (t1 Type, ts []Type) { return TypeF64, nil } + returnTypesFnV128 = func(b *builder, instr *Instruction) (t1 Type, ts []Type) { return TypeV128, nil } + returnTypesFnCallIndirect = func(b *builder, instr *Instruction) (t1 Type, ts []Type) { + sigID := SignatureID(instr.u1) + sig, ok := b.signatures[sigID] + if !ok { + panic("BUG") + } + switch len(sig.Results) { + case 0: + t1 = typeInvalid + case 1: + t1 = sig.Results[0] + default: + t1, ts = sig.Results[0], sig.Results[1:] + } + return + } + returnTypesFnCall = func(b *builder, instr *Instruction) (t1 Type, ts []Type) { + sigID := SignatureID(instr.u2) + sig, ok := b.signatures[sigID] + if !ok { + panic("BUG") + } + switch len(sig.Results) { + case 0: + t1 = typeInvalid + case 1: + t1 = sig.Results[0] + default: + t1, ts = sig.Results[0], sig.Results[1:] + } + return + } +) + +// sideEffect provides the info to determine if an instruction has side effects which +// is used to determine if it can be optimized out, interchanged with others, etc. +type sideEffect byte + +const ( + sideEffectUnknown sideEffect = iota + // sideEffectStrict represents an instruction with side effects, and should be always alive plus cannot be reordered. + sideEffectStrict + // sideEffectTraps represents an instruction that can trap, and should be always alive but can be reordered within the group. + sideEffectTraps + // sideEffectNone represents an instruction without side effects, and can be eliminated if the result is not used, plus can be reordered within the group. + sideEffectNone +) + +// instructionSideEffects provides the info to determine if an instruction has side effects. +// Instructions with side effects must not be eliminated regardless whether the result is used or not. +var instructionSideEffects = [opcodeEnd]sideEffect{ + OpcodeUndefined: sideEffectStrict, + OpcodeJump: sideEffectStrict, + OpcodeIconst: sideEffectNone, + OpcodeCall: sideEffectStrict, + OpcodeCallIndirect: sideEffectStrict, + OpcodeIadd: sideEffectNone, + OpcodeImul: sideEffectNone, + OpcodeIsub: sideEffectNone, + OpcodeIcmp: sideEffectNone, + OpcodeExtractlane: sideEffectNone, + OpcodeInsertlane: sideEffectNone, + OpcodeBand: sideEffectNone, + OpcodeBor: sideEffectNone, + OpcodeBxor: sideEffectNone, + OpcodeRotl: sideEffectNone, + OpcodeRotr: sideEffectNone, + OpcodeFcmp: sideEffectNone, + OpcodeFadd: sideEffectNone, + OpcodeClz: sideEffectNone, + OpcodeCtz: sideEffectNone, + OpcodePopcnt: sideEffectNone, + OpcodeLoad: sideEffectNone, + OpcodeLoadSplat: sideEffectNone, + OpcodeUload8: sideEffectNone, + OpcodeUload16: sideEffectNone, + OpcodeUload32: sideEffectNone, + OpcodeSload8: sideEffectNone, + OpcodeSload16: sideEffectNone, + OpcodeSload32: sideEffectNone, + OpcodeSExtend: sideEffectNone, + OpcodeUExtend: sideEffectNone, + OpcodeSwidenLow: sideEffectNone, + OpcodeUwidenLow: sideEffectNone, + OpcodeSwidenHigh: sideEffectNone, + OpcodeUwidenHigh: sideEffectNone, + OpcodeSnarrow: sideEffectNone, + OpcodeUnarrow: sideEffectNone, + OpcodeSwizzle: sideEffectNone, + OpcodeShuffle: sideEffectNone, + OpcodeSplat: sideEffectNone, + OpcodeFsub: sideEffectNone, + OpcodeF32const: sideEffectNone, + OpcodeF64const: sideEffectNone, + OpcodeIshl: sideEffectNone, + OpcodeSshr: sideEffectNone, + OpcodeUshr: sideEffectNone, + OpcodeStore: sideEffectStrict, + OpcodeIstore8: sideEffectStrict, + OpcodeIstore16: sideEffectStrict, + OpcodeIstore32: sideEffectStrict, + OpcodeExitWithCode: sideEffectStrict, + OpcodeExitIfTrueWithCode: sideEffectStrict, + OpcodeReturn: sideEffectStrict, + OpcodeBrz: sideEffectStrict, + OpcodeBrnz: sideEffectStrict, + OpcodeBrTable: sideEffectStrict, + OpcodeFdiv: sideEffectNone, + OpcodeFmul: sideEffectNone, + OpcodeFmax: sideEffectNone, + OpcodeSqmulRoundSat: sideEffectNone, + OpcodeSelect: sideEffectNone, + OpcodeFmin: sideEffectNone, + OpcodeFneg: sideEffectNone, + OpcodeFcvtToSint: sideEffectTraps, + OpcodeFcvtToUint: sideEffectTraps, + OpcodeFcvtFromSint: sideEffectNone, + OpcodeFcvtFromUint: sideEffectNone, + OpcodeFcvtToSintSat: sideEffectNone, + OpcodeFcvtToUintSat: sideEffectNone, + OpcodeVFcvtFromUint: sideEffectNone, + OpcodeVFcvtFromSint: sideEffectNone, + OpcodeFdemote: sideEffectNone, + OpcodeFvpromoteLow: sideEffectNone, + OpcodeFvdemote: sideEffectNone, + OpcodeFpromote: sideEffectNone, + OpcodeBitcast: sideEffectNone, + OpcodeIreduce: sideEffectNone, + OpcodeSqrt: sideEffectNone, + OpcodeCeil: sideEffectNone, + OpcodeFloor: sideEffectNone, + OpcodeTrunc: sideEffectNone, + OpcodeNearest: sideEffectNone, + OpcodeSdiv: sideEffectTraps, + OpcodeSrem: sideEffectTraps, + OpcodeUdiv: sideEffectTraps, + OpcodeUrem: sideEffectTraps, + OpcodeFabs: sideEffectNone, + OpcodeFcopysign: sideEffectNone, + OpcodeExtIaddPairwise: sideEffectNone, + OpcodeVconst: sideEffectNone, + OpcodeVbor: sideEffectNone, + OpcodeVbxor: sideEffectNone, + OpcodeVband: sideEffectNone, + OpcodeVbandnot: sideEffectNone, + OpcodeVbnot: sideEffectNone, + OpcodeVbitselect: sideEffectNone, + OpcodeVanyTrue: sideEffectNone, + OpcodeVallTrue: sideEffectNone, + OpcodeVhighBits: sideEffectNone, + OpcodeVIadd: sideEffectNone, + OpcodeVSaddSat: sideEffectNone, + OpcodeVUaddSat: sideEffectNone, + OpcodeVIsub: sideEffectNone, + OpcodeVSsubSat: sideEffectNone, + OpcodeVUsubSat: sideEffectNone, + OpcodeVIcmp: sideEffectNone, + OpcodeVImin: sideEffectNone, + OpcodeVUmin: sideEffectNone, + OpcodeVImax: sideEffectNone, + OpcodeVUmax: sideEffectNone, + OpcodeVAvgRound: sideEffectNone, + OpcodeVImul: sideEffectNone, + OpcodeVIabs: sideEffectNone, + OpcodeVIneg: sideEffectNone, + OpcodeVIpopcnt: sideEffectNone, + OpcodeVIshl: sideEffectNone, + OpcodeVSshr: sideEffectNone, + OpcodeVUshr: sideEffectNone, + OpcodeVSqrt: sideEffectNone, + OpcodeVFabs: sideEffectNone, + OpcodeVFmin: sideEffectNone, + OpcodeVFmax: sideEffectNone, + OpcodeVFneg: sideEffectNone, + OpcodeVFadd: sideEffectNone, + OpcodeVFsub: sideEffectNone, + OpcodeVFmul: sideEffectNone, + OpcodeVFdiv: sideEffectNone, + OpcodeVFcmp: sideEffectNone, + OpcodeVCeil: sideEffectNone, + OpcodeVFloor: sideEffectNone, + OpcodeVTrunc: sideEffectNone, + OpcodeVNearest: sideEffectNone, + OpcodeVMaxPseudo: sideEffectNone, + OpcodeVMinPseudo: sideEffectNone, + OpcodeVFcvtToUintSat: sideEffectNone, + OpcodeVFcvtToSintSat: sideEffectNone, + OpcodeVZeroExtLoad: sideEffectNone, + OpcodeAtomicRmw: sideEffectStrict, + OpcodeAtomicLoad: sideEffectStrict, + OpcodeAtomicStore: sideEffectStrict, + OpcodeAtomicCas: sideEffectStrict, + OpcodeFence: sideEffectStrict, + OpcodeTailCallReturnCall: sideEffectStrict, + OpcodeTailCallReturnCallIndirect: sideEffectStrict, + OpcodeWideningPairwiseDotProductS: sideEffectNone, +} + +// sideEffect returns true if this instruction has side effects. +func (i *Instruction) sideEffect() sideEffect { + if e := instructionSideEffects[i.opcode]; e == sideEffectUnknown { + panic("BUG: side effect info not registered for " + i.opcode.String()) + } else { + return e + } +} + +// instructionReturnTypes provides the function to determine the return types of an instruction. +var instructionReturnTypes = [opcodeEnd]returnTypesFn{ + OpcodeExtIaddPairwise: returnTypesFnV128, + OpcodeVbor: returnTypesFnV128, + OpcodeVbxor: returnTypesFnV128, + OpcodeVband: returnTypesFnV128, + OpcodeVbnot: returnTypesFnV128, + OpcodeVbandnot: returnTypesFnV128, + OpcodeVbitselect: returnTypesFnV128, + OpcodeVanyTrue: returnTypesFnI32, + OpcodeVallTrue: returnTypesFnI32, + OpcodeVhighBits: returnTypesFnI32, + OpcodeVIadd: returnTypesFnV128, + OpcodeVSaddSat: returnTypesFnV128, + OpcodeVUaddSat: returnTypesFnV128, + OpcodeVIsub: returnTypesFnV128, + OpcodeVSsubSat: returnTypesFnV128, + OpcodeVUsubSat: returnTypesFnV128, + OpcodeVIcmp: returnTypesFnV128, + OpcodeVImin: returnTypesFnV128, + OpcodeVUmin: returnTypesFnV128, + OpcodeVImax: returnTypesFnV128, + OpcodeVUmax: returnTypesFnV128, + OpcodeVImul: returnTypesFnV128, + OpcodeVAvgRound: returnTypesFnV128, + OpcodeVIabs: returnTypesFnV128, + OpcodeVIneg: returnTypesFnV128, + OpcodeVIpopcnt: returnTypesFnV128, + OpcodeVIshl: returnTypesFnV128, + OpcodeVSshr: returnTypesFnV128, + OpcodeVUshr: returnTypesFnV128, + OpcodeExtractlane: returnTypesFnSingle, + OpcodeInsertlane: returnTypesFnV128, + OpcodeBand: returnTypesFnSingle, + OpcodeFcopysign: returnTypesFnSingle, + OpcodeBitcast: returnTypesFnSingle, + OpcodeBor: returnTypesFnSingle, + OpcodeBxor: returnTypesFnSingle, + OpcodeRotl: returnTypesFnSingle, + OpcodeRotr: returnTypesFnSingle, + OpcodeIshl: returnTypesFnSingle, + OpcodeSshr: returnTypesFnSingle, + OpcodeSdiv: returnTypesFnSingle, + OpcodeSrem: returnTypesFnSingle, + OpcodeUdiv: returnTypesFnSingle, + OpcodeUrem: returnTypesFnSingle, + OpcodeUshr: returnTypesFnSingle, + OpcodeJump: returnTypesFnNoReturns, + OpcodeUndefined: returnTypesFnNoReturns, + OpcodeIconst: returnTypesFnSingle, + OpcodeSelect: returnTypesFnSingle, + OpcodeSExtend: returnTypesFnSingle, + OpcodeUExtend: returnTypesFnSingle, + OpcodeSwidenLow: returnTypesFnV128, + OpcodeUwidenLow: returnTypesFnV128, + OpcodeSwidenHigh: returnTypesFnV128, + OpcodeUwidenHigh: returnTypesFnV128, + OpcodeSnarrow: returnTypesFnV128, + OpcodeUnarrow: returnTypesFnV128, + OpcodeSwizzle: returnTypesFnSingle, + OpcodeShuffle: returnTypesFnV128, + OpcodeSplat: returnTypesFnV128, + OpcodeIreduce: returnTypesFnSingle, + OpcodeFabs: returnTypesFnSingle, + OpcodeSqrt: returnTypesFnSingle, + OpcodeCeil: returnTypesFnSingle, + OpcodeFloor: returnTypesFnSingle, + OpcodeTrunc: returnTypesFnSingle, + OpcodeNearest: returnTypesFnSingle, + OpcodeCallIndirect: returnTypesFnCallIndirect, + OpcodeCall: returnTypesFnCall, + OpcodeLoad: returnTypesFnSingle, + OpcodeVZeroExtLoad: returnTypesFnV128, + OpcodeLoadSplat: returnTypesFnV128, + OpcodeIadd: returnTypesFnSingle, + OpcodeIsub: returnTypesFnSingle, + OpcodeImul: returnTypesFnSingle, + OpcodeIcmp: returnTypesFnI32, + OpcodeFcmp: returnTypesFnI32, + OpcodeFadd: returnTypesFnSingle, + OpcodeFsub: returnTypesFnSingle, + OpcodeFdiv: returnTypesFnSingle, + OpcodeFmul: returnTypesFnSingle, + OpcodeFmax: returnTypesFnSingle, + OpcodeFmin: returnTypesFnSingle, + OpcodeSqmulRoundSat: returnTypesFnV128, + OpcodeF32const: returnTypesFnF32, + OpcodeF64const: returnTypesFnF64, + OpcodeClz: returnTypesFnSingle, + OpcodeCtz: returnTypesFnSingle, + OpcodePopcnt: returnTypesFnSingle, + OpcodeStore: returnTypesFnNoReturns, + OpcodeIstore8: returnTypesFnNoReturns, + OpcodeIstore16: returnTypesFnNoReturns, + OpcodeIstore32: returnTypesFnNoReturns, + OpcodeExitWithCode: returnTypesFnNoReturns, + OpcodeExitIfTrueWithCode: returnTypesFnNoReturns, + OpcodeReturn: returnTypesFnNoReturns, + OpcodeBrz: returnTypesFnNoReturns, + OpcodeBrnz: returnTypesFnNoReturns, + OpcodeBrTable: returnTypesFnNoReturns, + OpcodeUload8: returnTypesFnSingle, + OpcodeUload16: returnTypesFnSingle, + OpcodeUload32: returnTypesFnSingle, + OpcodeSload8: returnTypesFnSingle, + OpcodeSload16: returnTypesFnSingle, + OpcodeSload32: returnTypesFnSingle, + OpcodeFcvtToSint: returnTypesFnSingle, + OpcodeFcvtToUint: returnTypesFnSingle, + OpcodeFcvtFromSint: returnTypesFnSingle, + OpcodeFcvtFromUint: returnTypesFnSingle, + OpcodeFcvtToSintSat: returnTypesFnSingle, + OpcodeFcvtToUintSat: returnTypesFnSingle, + OpcodeVFcvtFromUint: returnTypesFnV128, + OpcodeVFcvtFromSint: returnTypesFnV128, + OpcodeFneg: returnTypesFnSingle, + OpcodeFdemote: returnTypesFnF32, + OpcodeFvdemote: returnTypesFnV128, + OpcodeFvpromoteLow: returnTypesFnV128, + OpcodeFpromote: returnTypesFnF64, + OpcodeVconst: returnTypesFnV128, + OpcodeVFabs: returnTypesFnV128, + OpcodeVSqrt: returnTypesFnV128, + OpcodeVFmax: returnTypesFnV128, + OpcodeVFmin: returnTypesFnV128, + OpcodeVFneg: returnTypesFnV128, + OpcodeVFadd: returnTypesFnV128, + OpcodeVFsub: returnTypesFnV128, + OpcodeVFmul: returnTypesFnV128, + OpcodeVFdiv: returnTypesFnV128, + OpcodeVFcmp: returnTypesFnV128, + OpcodeVCeil: returnTypesFnV128, + OpcodeVFloor: returnTypesFnV128, + OpcodeVTrunc: returnTypesFnV128, + OpcodeVNearest: returnTypesFnV128, + OpcodeVMaxPseudo: returnTypesFnV128, + OpcodeVMinPseudo: returnTypesFnV128, + OpcodeVFcvtToUintSat: returnTypesFnV128, + OpcodeVFcvtToSintSat: returnTypesFnV128, + OpcodeAtomicRmw: returnTypesFnSingle, + OpcodeAtomicLoad: returnTypesFnSingle, + OpcodeAtomicStore: returnTypesFnNoReturns, + OpcodeAtomicCas: returnTypesFnSingle, + OpcodeFence: returnTypesFnNoReturns, + OpcodeTailCallReturnCallIndirect: returnTypesFnCallIndirect, + OpcodeTailCallReturnCall: returnTypesFnCall, + OpcodeWideningPairwiseDotProductS: returnTypesFnV128, +} + +// AsLoad initializes this instruction as a store instruction with OpcodeLoad. +func (i *Instruction) AsLoad(ptr Value, offset uint32, typ Type) *Instruction { + i.opcode = OpcodeLoad + i.v = ptr + i.u1 = uint64(offset) + i.typ = typ + return i +} + +// AsExtLoad initializes this instruction as a store instruction with OpcodeLoad. +func (i *Instruction) AsExtLoad(op Opcode, ptr Value, offset uint32, dst64bit bool) *Instruction { + i.opcode = op + i.v = ptr + i.u1 = uint64(offset) + if dst64bit { + i.typ = TypeI64 + } else { + i.typ = TypeI32 + } + return i +} + +// AsVZeroExtLoad initializes this instruction as a store instruction with OpcodeVExtLoad. +func (i *Instruction) AsVZeroExtLoad(ptr Value, offset uint32, scalarType Type) *Instruction { + i.opcode = OpcodeVZeroExtLoad + i.v = ptr + i.u1 = uint64(offset) + i.u2 = uint64(scalarType) + i.typ = TypeV128 + return i +} + +// VZeroExtLoadData returns the operands for a load instruction. The returned `typ` is the scalar type of the load target. +func (i *Instruction) VZeroExtLoadData() (ptr Value, offset uint32, typ Type) { + return i.v, uint32(i.u1), Type(i.u2) +} + +// AsLoadSplat initializes this instruction as a store instruction with OpcodeLoadSplat. +func (i *Instruction) AsLoadSplat(ptr Value, offset uint32, lane VecLane) *Instruction { + i.opcode = OpcodeLoadSplat + i.v = ptr + i.u1 = uint64(offset) + i.u2 = uint64(lane) + i.typ = TypeV128 + return i +} + +// LoadData returns the operands for a load instruction. +func (i *Instruction) LoadData() (ptr Value, offset uint32, typ Type) { + return i.v, uint32(i.u1), i.typ +} + +// LoadSplatData returns the operands for a load splat instruction. +func (i *Instruction) LoadSplatData() (ptr Value, offset uint32, lane VecLane) { + return i.v, uint32(i.u1), VecLane(i.u2) +} + +// AsStore initializes this instruction as a store instruction with OpcodeStore. +func (i *Instruction) AsStore(storeOp Opcode, value, ptr Value, offset uint32) *Instruction { + i.opcode = storeOp + i.v = value + i.v2 = ptr + + var dstSize uint64 + switch storeOp { + case OpcodeStore: + dstSize = uint64(value.Type().Bits()) + case OpcodeIstore8: + dstSize = 8 + case OpcodeIstore16: + dstSize = 16 + case OpcodeIstore32: + dstSize = 32 + default: + panic("invalid store opcode" + storeOp.String()) + } + i.u1 = uint64(offset) | dstSize<<32 + return i +} + +// StoreData returns the operands for a store instruction. +func (i *Instruction) StoreData() (value, ptr Value, offset uint32, storeSizeInBits byte) { + return i.v, i.v2, uint32(i.u1), byte(i.u1 >> 32) +} + +// AsIconst64 initializes this instruction as a 64-bit integer constant instruction with OpcodeIconst. +func (i *Instruction) AsIconst64(v uint64) *Instruction { + i.opcode = OpcodeIconst + i.typ = TypeI64 + i.u1 = v + return i +} + +// AsIconst32 initializes this instruction as a 32-bit integer constant instruction with OpcodeIconst. +func (i *Instruction) AsIconst32(v uint32) *Instruction { + i.opcode = OpcodeIconst + i.typ = TypeI32 + i.u1 = uint64(v) + return i +} + +// AsIadd initializes this instruction as an integer addition instruction with OpcodeIadd. +func (i *Instruction) AsIadd(x, y Value) *Instruction { + i.opcode = OpcodeIadd + i.v = x + i.v2 = y + i.typ = x.Type() + return i +} + +// AsVIadd initializes this instruction as an integer addition instruction with OpcodeVIadd on a vector. +func (i *Instruction) AsVIadd(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVIadd + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsWideningPairwiseDotProductS initializes this instruction as a lane-wise integer extended pairwise addition instruction +// with OpcodeIaddPairwise on a vector. +func (i *Instruction) AsWideningPairwiseDotProductS(x, y Value) *Instruction { + i.opcode = OpcodeWideningPairwiseDotProductS + i.v = x + i.v2 = y + i.typ = TypeV128 + return i +} + +// AsExtIaddPairwise initializes this instruction as a lane-wise integer extended pairwise addition instruction +// with OpcodeIaddPairwise on a vector. +func (i *Instruction) AsExtIaddPairwise(x Value, srcLane VecLane, signed bool) *Instruction { + i.opcode = OpcodeExtIaddPairwise + i.v = x + i.u1 = uint64(srcLane) + if signed { + i.u2 = 1 + } + i.typ = TypeV128 + return i +} + +// ExtIaddPairwiseData returns the operands for a lane-wise integer extended pairwise addition instruction. +func (i *Instruction) ExtIaddPairwiseData() (x Value, srcLane VecLane, signed bool) { + return i.v, VecLane(i.u1), i.u2 != 0 +} + +// AsVSaddSat initializes this instruction as a vector addition with saturation instruction with OpcodeVSaddSat on a vector. +func (i *Instruction) AsVSaddSat(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVSaddSat + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVUaddSat initializes this instruction as a vector addition with saturation instruction with OpcodeVUaddSat on a vector. +func (i *Instruction) AsVUaddSat(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVUaddSat + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVIsub initializes this instruction as an integer subtraction instruction with OpcodeVIsub on a vector. +func (i *Instruction) AsVIsub(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVIsub + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVSsubSat initializes this instruction as a vector addition with saturation instruction with OpcodeVSsubSat on a vector. +func (i *Instruction) AsVSsubSat(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVSsubSat + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVUsubSat initializes this instruction as a vector addition with saturation instruction with OpcodeVUsubSat on a vector. +func (i *Instruction) AsVUsubSat(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVUsubSat + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVImin initializes this instruction as a signed integer min instruction with OpcodeVImin on a vector. +func (i *Instruction) AsVImin(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVImin + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVUmin initializes this instruction as an unsigned integer min instruction with OpcodeVUmin on a vector. +func (i *Instruction) AsVUmin(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVUmin + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVImax initializes this instruction as a signed integer max instruction with OpcodeVImax on a vector. +func (i *Instruction) AsVImax(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVImax + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVUmax initializes this instruction as an unsigned integer max instruction with OpcodeVUmax on a vector. +func (i *Instruction) AsVUmax(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVUmax + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVAvgRound initializes this instruction as an unsigned integer avg instruction, truncating to zero with OpcodeVAvgRound on a vector. +func (i *Instruction) AsVAvgRound(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVAvgRound + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVImul initializes this instruction as an integer multiplication with OpcodeVImul on a vector. +func (i *Instruction) AsVImul(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVImul + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsSqmulRoundSat initializes this instruction as a lane-wise saturating rounding multiplication +// in Q15 format with OpcodeSqmulRoundSat on a vector. +func (i *Instruction) AsSqmulRoundSat(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeSqmulRoundSat + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVIabs initializes this instruction as a vector absolute value with OpcodeVIabs. +func (i *Instruction) AsVIabs(x Value, lane VecLane) *Instruction { + i.opcode = OpcodeVIabs + i.v = x + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVIneg initializes this instruction as a vector negation with OpcodeVIneg. +func (i *Instruction) AsVIneg(x Value, lane VecLane) *Instruction { + i.opcode = OpcodeVIneg + i.v = x + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVIpopcnt initializes this instruction as a Population Count instruction with OpcodeVIpopcnt on a vector. +func (i *Instruction) AsVIpopcnt(x Value, lane VecLane) *Instruction { + if lane != VecLaneI8x16 { + panic("Unsupported lane type " + lane.String()) + } + i.opcode = OpcodeVIpopcnt + i.v = x + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVSqrt initializes this instruction as a sqrt instruction with OpcodeVSqrt on a vector. +func (i *Instruction) AsVSqrt(x Value, lane VecLane) *Instruction { + i.opcode = OpcodeVSqrt + i.v = x + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVFabs initializes this instruction as a float abs instruction with OpcodeVFabs on a vector. +func (i *Instruction) AsVFabs(x Value, lane VecLane) *Instruction { + i.opcode = OpcodeVFabs + i.v = x + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVFneg initializes this instruction as a float neg instruction with OpcodeVFneg on a vector. +func (i *Instruction) AsVFneg(x Value, lane VecLane) *Instruction { + i.opcode = OpcodeVFneg + i.v = x + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVFmax initializes this instruction as a float max instruction with OpcodeVFmax on a vector. +func (i *Instruction) AsVFmax(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVFmax + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVFmin initializes this instruction as a float min instruction with OpcodeVFmin on a vector. +func (i *Instruction) AsVFmin(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVFmin + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVFadd initializes this instruction as a floating point add instruction with OpcodeVFadd on a vector. +func (i *Instruction) AsVFadd(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVFadd + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVFsub initializes this instruction as a floating point subtraction instruction with OpcodeVFsub on a vector. +func (i *Instruction) AsVFsub(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVFsub + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVFmul initializes this instruction as a floating point multiplication instruction with OpcodeVFmul on a vector. +func (i *Instruction) AsVFmul(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVFmul + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVFdiv initializes this instruction as a floating point division instruction with OpcodeVFdiv on a vector. +func (i *Instruction) AsVFdiv(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVFdiv + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsImul initializes this instruction as an integer addition instruction with OpcodeImul. +func (i *Instruction) AsImul(x, y Value) *Instruction { + i.opcode = OpcodeImul + i.v = x + i.v2 = y + i.typ = x.Type() + return i +} + +func (i *Instruction) Insert(b Builder) *Instruction { + b.InsertInstruction(i) + return i +} + +// AsIsub initializes this instruction as an integer subtraction instruction with OpcodeIsub. +func (i *Instruction) AsIsub(x, y Value) *Instruction { + i.opcode = OpcodeIsub + i.v = x + i.v2 = y + i.typ = x.Type() + return i +} + +// AsIcmp initializes this instruction as an integer comparison instruction with OpcodeIcmp. +func (i *Instruction) AsIcmp(x, y Value, c IntegerCmpCond) *Instruction { + i.opcode = OpcodeIcmp + i.v = x + i.v2 = y + i.u1 = uint64(c) + i.typ = TypeI32 + return i +} + +// AsFcmp initializes this instruction as an integer comparison instruction with OpcodeFcmp. +func (i *Instruction) AsFcmp(x, y Value, c FloatCmpCond) { + i.opcode = OpcodeFcmp + i.v = x + i.v2 = y + i.u1 = uint64(c) + i.typ = TypeI32 +} + +// AsVIcmp initializes this instruction as an integer vector comparison instruction with OpcodeVIcmp. +func (i *Instruction) AsVIcmp(x, y Value, c IntegerCmpCond, lane VecLane) *Instruction { + i.opcode = OpcodeVIcmp + i.v = x + i.v2 = y + i.u1 = uint64(c) + i.u2 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsVFcmp initializes this instruction as a float comparison instruction with OpcodeVFcmp on Vector. +func (i *Instruction) AsVFcmp(x, y Value, c FloatCmpCond, lane VecLane) *Instruction { + i.opcode = OpcodeVFcmp + i.v = x + i.v2 = y + i.u1 = uint64(c) + i.typ = TypeV128 + i.u2 = uint64(lane) + return i +} + +// AsVCeil initializes this instruction as an instruction with OpcodeCeil. +func (i *Instruction) AsVCeil(x Value, lane VecLane) *Instruction { + i.opcode = OpcodeVCeil + i.v = x + i.typ = x.Type() + i.u1 = uint64(lane) + return i +} + +// AsVFloor initializes this instruction as an instruction with OpcodeFloor. +func (i *Instruction) AsVFloor(x Value, lane VecLane) *Instruction { + i.opcode = OpcodeVFloor + i.v = x + i.typ = x.Type() + i.u1 = uint64(lane) + return i +} + +// AsVTrunc initializes this instruction as an instruction with OpcodeTrunc. +func (i *Instruction) AsVTrunc(x Value, lane VecLane) *Instruction { + i.opcode = OpcodeVTrunc + i.v = x + i.typ = x.Type() + i.u1 = uint64(lane) + return i +} + +// AsVNearest initializes this instruction as an instruction with OpcodeNearest. +func (i *Instruction) AsVNearest(x Value, lane VecLane) *Instruction { + i.opcode = OpcodeVNearest + i.v = x + i.typ = x.Type() + i.u1 = uint64(lane) + return i +} + +// AsVMaxPseudo initializes this instruction as an instruction with OpcodeVMaxPseudo. +func (i *Instruction) AsVMaxPseudo(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVMaxPseudo + i.typ = x.Type() + i.v = x + i.v2 = y + i.u1 = uint64(lane) + return i +} + +// AsVMinPseudo initializes this instruction as an instruction with OpcodeVMinPseudo. +func (i *Instruction) AsVMinPseudo(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeVMinPseudo + i.typ = x.Type() + i.v = x + i.v2 = y + i.u1 = uint64(lane) + return i +} + +// AsSDiv initializes this instruction as an integer bitwise and instruction with OpcodeSdiv. +func (i *Instruction) AsSDiv(x, y, ctx Value) *Instruction { + i.opcode = OpcodeSdiv + i.v = x + i.v2 = y + i.v3 = ctx + i.typ = x.Type() + return i +} + +// AsUDiv initializes this instruction as an integer bitwise and instruction with OpcodeUdiv. +func (i *Instruction) AsUDiv(x, y, ctx Value) *Instruction { + i.opcode = OpcodeUdiv + i.v = x + i.v2 = y + i.v3 = ctx + i.typ = x.Type() + return i +} + +// AsSRem initializes this instruction as an integer bitwise and instruction with OpcodeSrem. +func (i *Instruction) AsSRem(x, y, ctx Value) *Instruction { + i.opcode = OpcodeSrem + i.v = x + i.v2 = y + i.v3 = ctx + i.typ = x.Type() + return i +} + +// AsURem initializes this instruction as an integer bitwise and instruction with OpcodeUrem. +func (i *Instruction) AsURem(x, y, ctx Value) *Instruction { + i.opcode = OpcodeUrem + i.v = x + i.v2 = y + i.v3 = ctx + i.typ = x.Type() + return i +} + +// AsBand initializes this instruction as an integer bitwise and instruction with OpcodeBand. +func (i *Instruction) AsBand(x, amount Value) *Instruction { + i.opcode = OpcodeBand + i.v = x + i.v2 = amount + i.typ = x.Type() + return i +} + +// AsBor initializes this instruction as an integer bitwise or instruction with OpcodeBor. +func (i *Instruction) AsBor(x, amount Value) { + i.opcode = OpcodeBor + i.v = x + i.v2 = amount + i.typ = x.Type() +} + +// AsBxor initializes this instruction as an integer bitwise xor instruction with OpcodeBxor. +func (i *Instruction) AsBxor(x, amount Value) { + i.opcode = OpcodeBxor + i.v = x + i.v2 = amount + i.typ = x.Type() +} + +// AsIshl initializes this instruction as an integer shift left instruction with OpcodeIshl. +func (i *Instruction) AsIshl(x, amount Value) *Instruction { + i.opcode = OpcodeIshl + i.v = x + i.v2 = amount + i.typ = x.Type() + return i +} + +// AsVIshl initializes this instruction as an integer shift left instruction with OpcodeVIshl on vector. +func (i *Instruction) AsVIshl(x, amount Value, lane VecLane) *Instruction { + i.opcode = OpcodeVIshl + i.v = x + i.v2 = amount + i.u1 = uint64(lane) + i.typ = x.Type() + return i +} + +// AsUshr initializes this instruction as an integer unsigned shift right (logical shift right) instruction with OpcodeUshr. +func (i *Instruction) AsUshr(x, amount Value) *Instruction { + i.opcode = OpcodeUshr + i.v = x + i.v2 = amount + i.typ = x.Type() + return i +} + +// AsVUshr initializes this instruction as an integer unsigned shift right (logical shift right) instruction with OpcodeVUshr on vector. +func (i *Instruction) AsVUshr(x, amount Value, lane VecLane) *Instruction { + i.opcode = OpcodeVUshr + i.v = x + i.v2 = amount + i.u1 = uint64(lane) + i.typ = x.Type() + return i +} + +// AsSshr initializes this instruction as an integer signed shift right (arithmetic shift right) instruction with OpcodeSshr. +func (i *Instruction) AsSshr(x, amount Value) *Instruction { + i.opcode = OpcodeSshr + i.v = x + i.v2 = amount + i.typ = x.Type() + return i +} + +// AsVSshr initializes this instruction as an integer signed shift right (arithmetic shift right) instruction with OpcodeVSshr on vector. +func (i *Instruction) AsVSshr(x, amount Value, lane VecLane) *Instruction { + i.opcode = OpcodeVSshr + i.v = x + i.v2 = amount + i.u1 = uint64(lane) + i.typ = x.Type() + return i +} + +// AsExtractlane initializes this instruction as an extract lane instruction with OpcodeExtractlane on vector. +func (i *Instruction) AsExtractlane(x Value, index byte, lane VecLane, signed bool) *Instruction { + i.opcode = OpcodeExtractlane + i.v = x + // We do not have a field for signedness, but `index` is a byte, + // so we just encode the flag in the high bits of `u1`. + i.u1 = uint64(index) + if signed { + i.u1 = i.u1 | 1<<32 + } + i.u2 = uint64(lane) + switch lane { + case VecLaneI8x16, VecLaneI16x8, VecLaneI32x4: + i.typ = TypeI32 + case VecLaneI64x2: + i.typ = TypeI64 + case VecLaneF32x4: + i.typ = TypeF32 + case VecLaneF64x2: + i.typ = TypeF64 + } + return i +} + +// AsInsertlane initializes this instruction as an insert lane instruction with OpcodeInsertlane on vector. +func (i *Instruction) AsInsertlane(x, y Value, index byte, lane VecLane) *Instruction { + i.opcode = OpcodeInsertlane + i.v = x + i.v2 = y + i.u1 = uint64(index) + i.u2 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsShuffle initializes this instruction as a shuffle instruction with OpcodeShuffle on vector. +func (i *Instruction) AsShuffle(x, y Value, lane []byte) *Instruction { + i.opcode = OpcodeShuffle + i.v = x + i.v2 = y + // Encode the 16 bytes as 8 bytes in u1, and 8 bytes in u2. + i.u1 = uint64(lane[7])<<56 | uint64(lane[6])<<48 | uint64(lane[5])<<40 | uint64(lane[4])<<32 | uint64(lane[3])<<24 | uint64(lane[2])<<16 | uint64(lane[1])<<8 | uint64(lane[0]) + i.u2 = uint64(lane[15])<<56 | uint64(lane[14])<<48 | uint64(lane[13])<<40 | uint64(lane[12])<<32 | uint64(lane[11])<<24 | uint64(lane[10])<<16 | uint64(lane[9])<<8 | uint64(lane[8]) + i.typ = TypeV128 + return i +} + +// AsSwizzle initializes this instruction as an insert lane instruction with OpcodeSwizzle on vector. +func (i *Instruction) AsSwizzle(x, y Value, lane VecLane) *Instruction { + i.opcode = OpcodeSwizzle + i.v = x + i.v2 = y + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsSplat initializes this instruction as an insert lane instruction with OpcodeSplat on vector. +func (i *Instruction) AsSplat(x Value, lane VecLane) *Instruction { + i.opcode = OpcodeSplat + i.v = x + i.u1 = uint64(lane) + i.typ = TypeV128 + return i +} + +// AsRotl initializes this instruction as a word rotate left instruction with OpcodeRotl. +func (i *Instruction) AsRotl(x, amount Value) { + i.opcode = OpcodeRotl + i.v = x + i.v2 = amount + i.typ = x.Type() +} + +// AsRotr initializes this instruction as a word rotate right instruction with OpcodeRotr. +func (i *Instruction) AsRotr(x, amount Value) { + i.opcode = OpcodeRotr + i.v = x + i.v2 = amount + i.typ = x.Type() +} + +// IcmpData returns the operands and comparison condition of this integer comparison instruction. +func (i *Instruction) IcmpData() (x, y Value, c IntegerCmpCond) { + return i.v, i.v2, IntegerCmpCond(i.u1) +} + +// FcmpData returns the operands and comparison condition of this floating-point comparison instruction. +func (i *Instruction) FcmpData() (x, y Value, c FloatCmpCond) { + return i.v, i.v2, FloatCmpCond(i.u1) +} + +// VIcmpData returns the operands and comparison condition of this integer comparison instruction on vector. +func (i *Instruction) VIcmpData() (x, y Value, c IntegerCmpCond, l VecLane) { + return i.v, i.v2, IntegerCmpCond(i.u1), VecLane(i.u2) +} + +// VFcmpData returns the operands and comparison condition of this float comparison instruction on vector. +func (i *Instruction) VFcmpData() (x, y Value, c FloatCmpCond, l VecLane) { + return i.v, i.v2, FloatCmpCond(i.u1), VecLane(i.u2) +} + +// ExtractlaneData returns the operands and sign flag of Extractlane on vector. +func (i *Instruction) ExtractlaneData() (x Value, index byte, signed bool, l VecLane) { + x = i.v + index = byte(0b00001111 & i.u1) + signed = i.u1>>32 != 0 + l = VecLane(i.u2) + return +} + +// InsertlaneData returns the operands and sign flag of Insertlane on vector. +func (i *Instruction) InsertlaneData() (x, y Value, index byte, l VecLane) { + x = i.v + y = i.v2 + index = byte(i.u1) + l = VecLane(i.u2) + return +} + +// AsFadd initializes this instruction as a floating-point addition instruction with OpcodeFadd. +func (i *Instruction) AsFadd(x, y Value) { + i.opcode = OpcodeFadd + i.v = x + i.v2 = y + i.typ = x.Type() +} + +// AsFsub initializes this instruction as a floating-point subtraction instruction with OpcodeFsub. +func (i *Instruction) AsFsub(x, y Value) { + i.opcode = OpcodeFsub + i.v = x + i.v2 = y + i.typ = x.Type() +} + +// AsFmul initializes this instruction as a floating-point multiplication instruction with OpcodeFmul. +func (i *Instruction) AsFmul(x, y Value) { + i.opcode = OpcodeFmul + i.v = x + i.v2 = y + i.typ = x.Type() +} + +// AsFdiv initializes this instruction as a floating-point division instruction with OpcodeFdiv. +func (i *Instruction) AsFdiv(x, y Value) { + i.opcode = OpcodeFdiv + i.v = x + i.v2 = y + i.typ = x.Type() +} + +// AsFmin initializes this instruction to take the minimum of two floating-points with OpcodeFmin. +func (i *Instruction) AsFmin(x, y Value) { + i.opcode = OpcodeFmin + i.v = x + i.v2 = y + i.typ = x.Type() +} + +// AsFmax initializes this instruction to take the maximum of two floating-points with OpcodeFmax. +func (i *Instruction) AsFmax(x, y Value) { + i.opcode = OpcodeFmax + i.v = x + i.v2 = y + i.typ = x.Type() +} + +// AsF32const initializes this instruction as a 32-bit floating-point constant instruction with OpcodeF32const. +func (i *Instruction) AsF32const(f float32) *Instruction { + i.opcode = OpcodeF32const + i.typ = TypeF64 + i.u1 = uint64(math.Float32bits(f)) + return i +} + +// AsF64const initializes this instruction as a 64-bit floating-point constant instruction with OpcodeF64const. +func (i *Instruction) AsF64const(f float64) *Instruction { + i.opcode = OpcodeF64const + i.typ = TypeF64 + i.u1 = math.Float64bits(f) + return i +} + +// AsVconst initializes this instruction as a vector constant instruction with OpcodeVconst. +func (i *Instruction) AsVconst(lo, hi uint64) *Instruction { + i.opcode = OpcodeVconst + i.typ = TypeV128 + i.u1 = lo + i.u2 = hi + return i +} + +// AsVbnot initializes this instruction as a vector negation instruction with OpcodeVbnot. +func (i *Instruction) AsVbnot(v Value) *Instruction { + i.opcode = OpcodeVbnot + i.typ = TypeV128 + i.v = v + return i +} + +// AsVband initializes this instruction as an and vector instruction with OpcodeVband. +func (i *Instruction) AsVband(x, y Value) *Instruction { + i.opcode = OpcodeVband + i.typ = TypeV128 + i.v = x + i.v2 = y + return i +} + +// AsVbor initializes this instruction as an or vector instruction with OpcodeVbor. +func (i *Instruction) AsVbor(x, y Value) *Instruction { + i.opcode = OpcodeVbor + i.typ = TypeV128 + i.v = x + i.v2 = y + return i +} + +// AsVbxor initializes this instruction as a xor vector instruction with OpcodeVbxor. +func (i *Instruction) AsVbxor(x, y Value) *Instruction { + i.opcode = OpcodeVbxor + i.typ = TypeV128 + i.v = x + i.v2 = y + return i +} + +// AsVbandnot initializes this instruction as an and-not vector instruction with OpcodeVbandnot. +func (i *Instruction) AsVbandnot(x, y Value) *Instruction { + i.opcode = OpcodeVbandnot + i.typ = TypeV128 + i.v = x + i.v2 = y + return i +} + +// AsVbitselect initializes this instruction as a bit select vector instruction with OpcodeVbitselect. +func (i *Instruction) AsVbitselect(c, x, y Value) *Instruction { + i.opcode = OpcodeVbitselect + i.typ = TypeV128 + i.v = c + i.v2 = x + i.v3 = y + return i +} + +// AsVanyTrue initializes this instruction as an anyTrue vector instruction with OpcodeVanyTrue. +func (i *Instruction) AsVanyTrue(x Value) *Instruction { + i.opcode = OpcodeVanyTrue + i.typ = TypeI32 + i.v = x + return i +} + +// AsVallTrue initializes this instruction as an allTrue vector instruction with OpcodeVallTrue. +func (i *Instruction) AsVallTrue(x Value, lane VecLane) *Instruction { + i.opcode = OpcodeVallTrue + i.typ = TypeI32 + i.v = x + i.u1 = uint64(lane) + return i +} + +// AsVhighBits initializes this instruction as a highBits vector instruction with OpcodeVhighBits. +func (i *Instruction) AsVhighBits(x Value, lane VecLane) *Instruction { + i.opcode = OpcodeVhighBits + i.typ = TypeI32 + i.v = x + i.u1 = uint64(lane) + return i +} + +// VconstData returns the operands of this vector constant instruction. +func (i *Instruction) VconstData() (lo, hi uint64) { + return i.u1, i.u2 +} + +// AsReturn initializes this instruction as a return instruction with OpcodeReturn. +func (i *Instruction) AsReturn(vs wazevoapi.VarLength[Value]) *Instruction { + i.opcode = OpcodeReturn + i.vs = vs + return i +} + +// AsIreduce initializes this instruction as a reduction instruction with OpcodeIreduce. +func (i *Instruction) AsIreduce(v Value, dstType Type) *Instruction { + i.opcode = OpcodeIreduce + i.v = v + i.typ = dstType + return i +} + +// AsWiden initializes this instruction as a signed or unsigned widen instruction +// on low half or high half of the given vector with OpcodeSwidenLow, OpcodeUwidenLow, OpcodeSwidenHigh, OpcodeUwidenHigh. +func (i *Instruction) AsWiden(v Value, lane VecLane, signed, low bool) *Instruction { + switch { + case signed && low: + i.opcode = OpcodeSwidenLow + case !signed && low: + i.opcode = OpcodeUwidenLow + case signed && !low: + i.opcode = OpcodeSwidenHigh + case !signed && !low: + i.opcode = OpcodeUwidenHigh + } + i.v = v + i.u1 = uint64(lane) + return i +} + +// AsAtomicLoad initializes this instruction as an atomic load. +// The size is in bytes and must be 1, 2, 4, or 8. +func (i *Instruction) AsAtomicLoad(addr Value, size uint64, typ Type) *Instruction { + i.opcode = OpcodeAtomicLoad + i.u1 = size + i.v = addr + i.typ = typ + return i +} + +// AsAtomicLoad initializes this instruction as an atomic store. +// The size is in bytes and must be 1, 2, 4, or 8. +func (i *Instruction) AsAtomicStore(addr, val Value, size uint64) *Instruction { + i.opcode = OpcodeAtomicStore + i.u1 = size + i.v = addr + i.v2 = val + i.typ = val.Type() + return i +} + +// AsAtomicRmw initializes this instruction as an atomic read-modify-write. +// The size is in bytes and must be 1, 2, 4, or 8. +func (i *Instruction) AsAtomicRmw(op AtomicRmwOp, addr, val Value, size uint64) *Instruction { + i.opcode = OpcodeAtomicRmw + i.u1 = uint64(op) + i.u2 = size + i.v = addr + i.v2 = val + i.typ = val.Type() + return i +} + +// AsAtomicCas initializes this instruction as an atomic compare-and-swap. +// The size is in bytes and must be 1, 2, 4, or 8. +func (i *Instruction) AsAtomicCas(addr, exp, repl Value, size uint64) *Instruction { + i.opcode = OpcodeAtomicCas + i.u1 = size + i.v = addr + i.v2 = exp + i.v3 = repl + i.typ = repl.Type() + return i +} + +// AsFence initializes this instruction as a memory fence. +// A single byte immediate may be used to indicate fence ordering in the future +// but is currently always 0 and ignored. +func (i *Instruction) AsFence(order byte) *Instruction { + i.opcode = OpcodeFence + i.u1 = uint64(order) + return i +} + +// AtomicRmwData returns the data for this atomic read-modify-write instruction. +func (i *Instruction) AtomicRmwData() (op AtomicRmwOp, size uint64) { + return AtomicRmwOp(i.u1), i.u2 +} + +// AtomicTargetSize returns the target memory size of the atomic instruction. +func (i *Instruction) AtomicTargetSize() (size uint64) { + return i.u1 +} + +// AsTailCallReturnCall initializes this instruction as a call instruction with OpcodeTailCallReturnCall. +func (i *Instruction) AsTailCallReturnCall(ref FuncRef, sig *Signature, args Values) { + i.opcode = OpcodeTailCallReturnCall + i.u1 = uint64(ref) + i.vs = args + i.u2 = uint64(sig.ID) + sig.used = true +} + +// AsTailCallReturnCallIndirect initializes this instruction as a call-indirect instruction with OpcodeTailCallReturnCallIndirect. +func (i *Instruction) AsTailCallReturnCallIndirect(funcPtr Value, sig *Signature, args Values) *Instruction { + i.opcode = OpcodeTailCallReturnCallIndirect + i.vs = args + i.v = funcPtr + i.u1 = uint64(sig.ID) + sig.used = true + return i +} + +// ReturnVals returns the return values of OpcodeReturn. +func (i *Instruction) ReturnVals() []Value { + return i.vs.View() +} + +// AsExitWithCode initializes this instruction as a trap instruction with OpcodeExitWithCode. +func (i *Instruction) AsExitWithCode(ctx Value, code wazevoapi.ExitCode) { + i.opcode = OpcodeExitWithCode + i.v = ctx + i.u1 = uint64(code) +} + +// AsExitIfTrueWithCode initializes this instruction as a trap instruction with OpcodeExitIfTrueWithCode. +func (i *Instruction) AsExitIfTrueWithCode(ctx, c Value, code wazevoapi.ExitCode) *Instruction { + i.opcode = OpcodeExitIfTrueWithCode + i.v = ctx + i.v2 = c + i.u1 = uint64(code) + return i +} + +// ExitWithCodeData returns the context and exit code of OpcodeExitWithCode. +func (i *Instruction) ExitWithCodeData() (ctx Value, code wazevoapi.ExitCode) { + return i.v, wazevoapi.ExitCode(i.u1) +} + +// ExitIfTrueWithCodeData returns the context and exit code of OpcodeExitWithCode. +func (i *Instruction) ExitIfTrueWithCodeData() (ctx, c Value, code wazevoapi.ExitCode) { + return i.v, i.v2, wazevoapi.ExitCode(i.u1) +} + +// InvertBrx inverts either OpcodeBrz or OpcodeBrnz to the other. +func (i *Instruction) InvertBrx() { + switch i.opcode { + case OpcodeBrz: + i.opcode = OpcodeBrnz + case OpcodeBrnz: + i.opcode = OpcodeBrz + default: + panic("BUG") + } +} + +// BranchData returns the branch data for this instruction necessary for backends. +func (i *Instruction) BranchData() (condVal Value, blockArgs []Value, target BasicBlockID) { + switch i.opcode { + case OpcodeJump: + condVal = ValueInvalid + case OpcodeBrz, OpcodeBrnz: + condVal = i.v + default: + panic("BUG") + } + blockArgs = i.vs.View() + target = BasicBlockID(i.rValue) + return +} + +// BrTableData returns the branch table data for this instruction necessary for backends. +func (i *Instruction) BrTableData() (index Value, targets Values) { + if i.opcode != OpcodeBrTable { + panic("BUG: BrTableData only available for OpcodeBrTable") + } + index = i.v + targets = i.rValues + return +} + +// AsJump initializes this instruction as a jump instruction with OpcodeJump. +func (i *Instruction) AsJump(vs Values, target BasicBlock) *Instruction { + i.opcode = OpcodeJump + i.vs = vs + i.rValue = Value(target.ID()) + return i +} + +// IsFallthroughJump returns true if this instruction is a fallthrough jump. +func (i *Instruction) IsFallthroughJump() bool { + if i.opcode != OpcodeJump { + panic("BUG: IsFallthrough only available for OpcodeJump") + } + return i.opcode == OpcodeJump && i.u1 != 0 +} + +// AsFallthroughJump marks this instruction as a fallthrough jump. +func (i *Instruction) AsFallthroughJump() { + if i.opcode != OpcodeJump { + panic("BUG: AsFallthroughJump only available for OpcodeJump") + } + i.u1 = 1 +} + +// AsBrz initializes this instruction as a branch-if-zero instruction with OpcodeBrz. +func (i *Instruction) AsBrz(v Value, args Values, target BasicBlock) { + i.opcode = OpcodeBrz + i.v = v + i.vs = args + i.rValue = Value(target.ID()) +} + +// AsBrnz initializes this instruction as a branch-if-not-zero instruction with OpcodeBrnz. +func (i *Instruction) AsBrnz(v Value, args Values, target BasicBlock) *Instruction { + i.opcode = OpcodeBrnz + i.v = v + i.vs = args + i.rValue = Value(target.ID()) + return i +} + +// AsBrTable initializes this instruction as a branch-table instruction with OpcodeBrTable. +// targets is a list of basic block IDs cast to Values. +func (i *Instruction) AsBrTable(index Value, targets Values) { + i.opcode = OpcodeBrTable + i.v = index + i.rValues = targets +} + +// AsCall initializes this instruction as a call instruction with OpcodeCall. +func (i *Instruction) AsCall(ref FuncRef, sig *Signature, args Values) { + i.opcode = OpcodeCall + i.u1 = uint64(ref) + i.vs = args + i.u2 = uint64(sig.ID) + sig.used = true +} + +// CallData returns the call data for this instruction necessary for backends. +func (i *Instruction) CallData() (ref FuncRef, sigID SignatureID, args []Value) { + if i.opcode != OpcodeCall && i.opcode != OpcodeTailCallReturnCall { + panic("BUG: CallData only available for OpcodeCall") + } + ref = FuncRef(i.u1) + sigID = SignatureID(i.u2) + args = i.vs.View() + return +} + +// AsCallIndirect initializes this instruction as a call-indirect instruction with OpcodeCallIndirect. +func (i *Instruction) AsCallIndirect(funcPtr Value, sig *Signature, args Values) *Instruction { + i.opcode = OpcodeCallIndirect + i.typ = TypeF64 + i.vs = args + i.v = funcPtr + i.u1 = uint64(sig.ID) + sig.used = true + return i +} + +// AsCallGoRuntimeMemmove is the same as AsCallIndirect, but with a special flag set to indicate that it is a call to the Go runtime memmove function. +func (i *Instruction) AsCallGoRuntimeMemmove(funcPtr Value, sig *Signature, args Values) *Instruction { + i.AsCallIndirect(funcPtr, sig, args) + i.u2 = 1 + return i +} + +// CallIndirectData returns the call indirect data for this instruction necessary for backends. +func (i *Instruction) CallIndirectData() (funcPtr Value, sigID SignatureID, args []Value, isGoMemmove bool) { + if i.opcode != OpcodeCallIndirect && i.opcode != OpcodeTailCallReturnCallIndirect { + panic("BUG: CallIndirectData only available for OpcodeCallIndirect and OpcodeTailCallReturnCallIndirect") + } + funcPtr = i.v + sigID = SignatureID(i.u1) + args = i.vs.View() + isGoMemmove = i.u2 == 1 + return +} + +// AsClz initializes this instruction as a Count Leading Zeroes instruction with OpcodeClz. +func (i *Instruction) AsClz(x Value) { + i.opcode = OpcodeClz + i.v = x + i.typ = x.Type() +} + +// AsCtz initializes this instruction as a Count Trailing Zeroes instruction with OpcodeCtz. +func (i *Instruction) AsCtz(x Value) { + i.opcode = OpcodeCtz + i.v = x + i.typ = x.Type() +} + +// AsPopcnt initializes this instruction as a Population Count instruction with OpcodePopcnt. +func (i *Instruction) AsPopcnt(x Value) { + i.opcode = OpcodePopcnt + i.v = x + i.typ = x.Type() +} + +// AsFneg initializes this instruction as an instruction with OpcodeFneg. +func (i *Instruction) AsFneg(x Value) *Instruction { + i.opcode = OpcodeFneg + i.v = x + i.typ = x.Type() + return i +} + +// AsSqrt initializes this instruction as an instruction with OpcodeSqrt. +func (i *Instruction) AsSqrt(x Value) *Instruction { + i.opcode = OpcodeSqrt + i.v = x + i.typ = x.Type() + return i +} + +// AsFabs initializes this instruction as an instruction with OpcodeFabs. +func (i *Instruction) AsFabs(x Value) *Instruction { + i.opcode = OpcodeFabs + i.v = x + i.typ = x.Type() + return i +} + +// AsFcopysign initializes this instruction as an instruction with OpcodeFcopysign. +func (i *Instruction) AsFcopysign(x, y Value) *Instruction { + i.opcode = OpcodeFcopysign + i.v = x + i.v2 = y + i.typ = x.Type() + return i +} + +// AsCeil initializes this instruction as an instruction with OpcodeCeil. +func (i *Instruction) AsCeil(x Value) *Instruction { + i.opcode = OpcodeCeil + i.v = x + i.typ = x.Type() + return i +} + +// AsFloor initializes this instruction as an instruction with OpcodeFloor. +func (i *Instruction) AsFloor(x Value) *Instruction { + i.opcode = OpcodeFloor + i.v = x + i.typ = x.Type() + return i +} + +// AsTrunc initializes this instruction as an instruction with OpcodeTrunc. +func (i *Instruction) AsTrunc(x Value) *Instruction { + i.opcode = OpcodeTrunc + i.v = x + i.typ = x.Type() + return i +} + +// AsNearest initializes this instruction as an instruction with OpcodeNearest. +func (i *Instruction) AsNearest(x Value) *Instruction { + i.opcode = OpcodeNearest + i.v = x + i.typ = x.Type() + return i +} + +// AsBitcast initializes this instruction as an instruction with OpcodeBitcast. +func (i *Instruction) AsBitcast(x Value, dstType Type) *Instruction { + i.opcode = OpcodeBitcast + i.v = x + i.typ = dstType + return i +} + +// BitcastData returns the operands for a bitcast instruction. +func (i *Instruction) BitcastData() (x Value, dstType Type) { + return i.v, i.typ +} + +// AsFdemote initializes this instruction as an instruction with OpcodeFdemote. +func (i *Instruction) AsFdemote(x Value) { + i.opcode = OpcodeFdemote + i.v = x + i.typ = TypeF32 +} + +// AsFpromote initializes this instruction as an instruction with OpcodeFpromote. +func (i *Instruction) AsFpromote(x Value) { + i.opcode = OpcodeFpromote + i.v = x + i.typ = TypeF64 +} + +// AsFcvtFromInt initializes this instruction as an instruction with either OpcodeFcvtFromUint or OpcodeFcvtFromSint +func (i *Instruction) AsFcvtFromInt(x Value, signed bool, dst64bit bool) *Instruction { + if signed { + i.opcode = OpcodeFcvtFromSint + } else { + i.opcode = OpcodeFcvtFromUint + } + i.v = x + if dst64bit { + i.typ = TypeF64 + } else { + i.typ = TypeF32 + } + return i +} + +// AsFcvtToInt initializes this instruction as an instruction with either OpcodeFcvtToUint or OpcodeFcvtToSint +func (i *Instruction) AsFcvtToInt(x, ctx Value, signed bool, dst64bit bool, sat bool) *Instruction { + switch { + case signed && !sat: + i.opcode = OpcodeFcvtToSint + case !signed && !sat: + i.opcode = OpcodeFcvtToUint + case signed && sat: + i.opcode = OpcodeFcvtToSintSat + case !signed && sat: + i.opcode = OpcodeFcvtToUintSat + } + i.v = x + i.v2 = ctx + if dst64bit { + i.typ = TypeI64 + } else { + i.typ = TypeI32 + } + return i +} + +// AsVFcvtToIntSat initializes this instruction as an instruction with either OpcodeVFcvtToSintSat or OpcodeVFcvtToUintSat +func (i *Instruction) AsVFcvtToIntSat(x Value, lane VecLane, signed bool) *Instruction { + if signed { + i.opcode = OpcodeVFcvtToSintSat + } else { + i.opcode = OpcodeVFcvtToUintSat + } + i.v = x + i.u1 = uint64(lane) + return i +} + +// AsVFcvtFromInt initializes this instruction as an instruction with either OpcodeVFcvtToSintSat or OpcodeVFcvtToUintSat +func (i *Instruction) AsVFcvtFromInt(x Value, lane VecLane, signed bool) *Instruction { + if signed { + i.opcode = OpcodeVFcvtFromSint + } else { + i.opcode = OpcodeVFcvtFromUint + } + i.v = x + i.u1 = uint64(lane) + return i +} + +// AsNarrow initializes this instruction as an instruction with either OpcodeSnarrow or OpcodeUnarrow +func (i *Instruction) AsNarrow(x, y Value, lane VecLane, signed bool) *Instruction { + if signed { + i.opcode = OpcodeSnarrow + } else { + i.opcode = OpcodeUnarrow + } + i.v = x + i.v2 = y + i.u1 = uint64(lane) + return i +} + +// AsFvpromoteLow initializes this instruction as an instruction with OpcodeFvpromoteLow +func (i *Instruction) AsFvpromoteLow(x Value, lane VecLane) *Instruction { + i.opcode = OpcodeFvpromoteLow + i.v = x + i.u1 = uint64(lane) + return i +} + +// AsFvdemote initializes this instruction as an instruction with OpcodeFvdemote +func (i *Instruction) AsFvdemote(x Value, lane VecLane) *Instruction { + i.opcode = OpcodeFvdemote + i.v = x + i.u1 = uint64(lane) + return i +} + +// AsSExtend initializes this instruction as a sign extension instruction with OpcodeSExtend. +func (i *Instruction) AsSExtend(v Value, from, to byte) *Instruction { + i.opcode = OpcodeSExtend + i.v = v + i.u1 = uint64(from)<<8 | uint64(to) + if to == 64 { + i.typ = TypeI64 + } else { + i.typ = TypeI32 + } + return i +} + +// AsUExtend initializes this instruction as an unsigned extension instruction with OpcodeUExtend. +func (i *Instruction) AsUExtend(v Value, from, to byte) *Instruction { + i.opcode = OpcodeUExtend + i.v = v + i.u1 = uint64(from)<<8 | uint64(to) + if to == 64 { + i.typ = TypeI64 + } else { + i.typ = TypeI32 + } + return i +} + +func (i *Instruction) ExtendData() (from, to byte, signed bool) { + if i.opcode != OpcodeSExtend && i.opcode != OpcodeUExtend { + panic("BUG: ExtendData only available for OpcodeSExtend and OpcodeUExtend") + } + from = byte(i.u1 >> 8) + to = byte(i.u1) + signed = i.opcode == OpcodeSExtend + return +} + +// AsSelect initializes this instruction as an unsigned extension instruction with OpcodeSelect. +func (i *Instruction) AsSelect(c, x, y Value) *Instruction { + i.opcode = OpcodeSelect + i.v = c + i.v2 = x + i.v3 = y + i.typ = x.Type() + return i +} + +// SelectData returns the select data for this instruction necessary for backends. +func (i *Instruction) SelectData() (c, x, y Value) { + c = i.v + x = i.v2 + y = i.v3 + return +} + +// ExtendFromToBits returns the from and to bit size for the extension instruction. +func (i *Instruction) ExtendFromToBits() (from, to byte) { + from = byte(i.u1 >> 8) + to = byte(i.u1) + return +} + +// Format returns a string representation of this instruction with the given builder. +// For debugging purposes only. +func (i *Instruction) Format(b Builder) string { + var instSuffix string + switch i.opcode { + case OpcodeExitWithCode: + instSuffix = fmt.Sprintf(" %s, %s", i.v.Format(b), wazevoapi.ExitCode(i.u1)) + case OpcodeExitIfTrueWithCode: + instSuffix = fmt.Sprintf(" %s, %s, %s", i.v2.Format(b), i.v.Format(b), wazevoapi.ExitCode(i.u1)) + case OpcodeIadd, OpcodeIsub, OpcodeImul, OpcodeFadd, OpcodeFsub, OpcodeFmin, OpcodeFmax, OpcodeFdiv, OpcodeFmul: + instSuffix = fmt.Sprintf(" %s, %s", i.v.Format(b), i.v2.Format(b)) + case OpcodeIcmp: + instSuffix = fmt.Sprintf(" %s, %s, %s", IntegerCmpCond(i.u1), i.v.Format(b), i.v2.Format(b)) + case OpcodeFcmp: + instSuffix = fmt.Sprintf(" %s, %s, %s", FloatCmpCond(i.u1), i.v.Format(b), i.v2.Format(b)) + case OpcodeSExtend, OpcodeUExtend: + instSuffix = fmt.Sprintf(" %s, %d->%d", i.v.Format(b), i.u1>>8, i.u1&0xff) + case OpcodeCall, OpcodeCallIndirect: + view := i.vs.View() + vs := make([]string, len(view)) + for idx := range vs { + vs[idx] = view[idx].Format(b) + } + if i.opcode == OpcodeCallIndirect { + instSuffix = fmt.Sprintf(" %s:%s, %s", i.v.Format(b), SignatureID(i.u1), strings.Join(vs, ", ")) + } else { + instSuffix = fmt.Sprintf(" %s:%s, %s", FuncRef(i.u1), SignatureID(i.u2), strings.Join(vs, ", ")) + } + case OpcodeStore, OpcodeIstore8, OpcodeIstore16, OpcodeIstore32: + instSuffix = fmt.Sprintf(" %s, %s, %#x", i.v.Format(b), i.v2.Format(b), uint32(i.u1)) + case OpcodeLoad, OpcodeVZeroExtLoad: + instSuffix = fmt.Sprintf(" %s, %#x", i.v.Format(b), int32(i.u1)) + case OpcodeLoadSplat: + instSuffix = fmt.Sprintf(".%s %s, %#x", VecLane(i.u2), i.v.Format(b), int32(i.u1)) + case OpcodeUload8, OpcodeUload16, OpcodeUload32, OpcodeSload8, OpcodeSload16, OpcodeSload32: + instSuffix = fmt.Sprintf(" %s, %#x", i.v.Format(b), int32(i.u1)) + case OpcodeSelect, OpcodeVbitselect: + instSuffix = fmt.Sprintf(" %s, %s, %s", i.v.Format(b), i.v2.Format(b), i.v3.Format(b)) + case OpcodeIconst: + switch i.typ { + case TypeI32: + instSuffix = fmt.Sprintf("_32 %#x", uint32(i.u1)) + case TypeI64: + instSuffix = fmt.Sprintf("_64 %#x", i.u1) + } + case OpcodeVconst: + instSuffix = fmt.Sprintf(" %016x %016x", i.u1, i.u2) + case OpcodeF32const: + instSuffix = fmt.Sprintf(" %f", math.Float32frombits(uint32(i.u1))) + case OpcodeF64const: + instSuffix = fmt.Sprintf(" %f", math.Float64frombits(i.u1)) + case OpcodeReturn: + view := i.vs.View() + if len(view) == 0 { + break + } + vs := make([]string, len(view)) + for idx := range vs { + vs[idx] = view[idx].Format(b) + } + instSuffix = fmt.Sprintf(" %s", strings.Join(vs, ", ")) + case OpcodeJump: + view := i.vs.View() + vs := make([]string, len(view)+1) + if i.IsFallthroughJump() { + vs[0] = " fallthrough" + } else { + blockId := BasicBlockID(i.rValue) + vs[0] = " " + b.BasicBlock(blockId).Name() + } + for idx := range view { + vs[idx+1] = view[idx].Format(b) + } + + instSuffix = strings.Join(vs, ", ") + case OpcodeBrz, OpcodeBrnz: + view := i.vs.View() + vs := make([]string, len(view)+2) + vs[0] = " " + i.v.Format(b) + blockId := BasicBlockID(i.rValue) + vs[1] = b.BasicBlock(blockId).Name() + for idx := range view { + vs[idx+2] = view[idx].Format(b) + } + instSuffix = strings.Join(vs, ", ") + case OpcodeBrTable: + // `BrTable index, [label1, label2, ... labelN]` + instSuffix = fmt.Sprintf(" %s", i.v.Format(b)) + instSuffix += ", [" + for i, target := range i.rValues.View() { + blk := b.BasicBlock(BasicBlockID(target)) + if i == 0 { + instSuffix += blk.Name() + } else { + instSuffix += ", " + blk.Name() + } + } + instSuffix += "]" + case OpcodeBand, OpcodeBor, OpcodeBxor, OpcodeRotr, OpcodeRotl, OpcodeIshl, OpcodeSshr, OpcodeUshr, + OpcodeSdiv, OpcodeUdiv, OpcodeFcopysign, OpcodeSrem, OpcodeUrem, + OpcodeVbnot, OpcodeVbxor, OpcodeVbor, OpcodeVband, OpcodeVbandnot, OpcodeVIcmp, OpcodeVFcmp: + instSuffix = fmt.Sprintf(" %s, %s", i.v.Format(b), i.v2.Format(b)) + case OpcodeUndefined: + case OpcodeClz, OpcodeCtz, OpcodePopcnt, OpcodeFneg, OpcodeFcvtToSint, OpcodeFcvtToUint, OpcodeFcvtFromSint, + OpcodeFcvtFromUint, OpcodeFcvtToSintSat, OpcodeFcvtToUintSat, OpcodeFdemote, OpcodeFpromote, OpcodeIreduce, OpcodeBitcast, OpcodeSqrt, OpcodeFabs, + OpcodeCeil, OpcodeFloor, OpcodeTrunc, OpcodeNearest: + instSuffix = " " + i.v.Format(b) + case OpcodeVIadd, OpcodeExtIaddPairwise, OpcodeVSaddSat, OpcodeVUaddSat, OpcodeVIsub, OpcodeVSsubSat, OpcodeVUsubSat, + OpcodeVImin, OpcodeVUmin, OpcodeVImax, OpcodeVUmax, OpcodeVImul, OpcodeVAvgRound, + OpcodeVFadd, OpcodeVFsub, OpcodeVFmul, OpcodeVFdiv, + OpcodeVIshl, OpcodeVSshr, OpcodeVUshr, + OpcodeVFmin, OpcodeVFmax, OpcodeVMinPseudo, OpcodeVMaxPseudo, + OpcodeSnarrow, OpcodeUnarrow, OpcodeSwizzle, OpcodeSqmulRoundSat: + instSuffix = fmt.Sprintf(".%s %s, %s", VecLane(i.u1), i.v.Format(b), i.v2.Format(b)) + case OpcodeVIabs, OpcodeVIneg, OpcodeVIpopcnt, OpcodeVhighBits, OpcodeVallTrue, OpcodeVanyTrue, + OpcodeVFabs, OpcodeVFneg, OpcodeVSqrt, OpcodeVCeil, OpcodeVFloor, OpcodeVTrunc, OpcodeVNearest, + OpcodeVFcvtToUintSat, OpcodeVFcvtToSintSat, OpcodeVFcvtFromUint, OpcodeVFcvtFromSint, + OpcodeFvpromoteLow, OpcodeFvdemote, OpcodeSwidenLow, OpcodeUwidenLow, OpcodeSwidenHigh, OpcodeUwidenHigh, + OpcodeSplat: + instSuffix = fmt.Sprintf(".%s %s", VecLane(i.u1), i.v.Format(b)) + case OpcodeExtractlane: + var signedness string + if i.u1 != 0 { + signedness = "signed" + } else { + signedness = "unsigned" + } + instSuffix = fmt.Sprintf(".%s %d, %s (%s)", VecLane(i.u2), 0x0000FFFF&i.u1, i.v.Format(b), signedness) + case OpcodeInsertlane: + instSuffix = fmt.Sprintf(".%s %d, %s, %s", VecLane(i.u2), i.u1, i.v.Format(b), i.v2.Format(b)) + case OpcodeShuffle: + lanes := make([]byte, 16) + for idx := 0; idx < 8; idx++ { + lanes[idx] = byte(i.u1 >> (8 * idx)) + } + for idx := 0; idx < 8; idx++ { + lanes[idx+8] = byte(i.u2 >> (8 * idx)) + } + // Prints Shuffle.[0 1 2 3 4 5 6 7 ...] v2, v3 + instSuffix = fmt.Sprintf(".%v %s, %s", lanes, i.v.Format(b), i.v2.Format(b)) + case OpcodeAtomicRmw: + instSuffix = fmt.Sprintf(" %s_%d, %s, %s", AtomicRmwOp(i.u1), 8*i.u2, i.v.Format(b), i.v2.Format(b)) + case OpcodeAtomicLoad: + instSuffix = fmt.Sprintf("_%d, %s", 8*i.u1, i.v.Format(b)) + case OpcodeAtomicStore: + instSuffix = fmt.Sprintf("_%d, %s, %s", 8*i.u1, i.v.Format(b), i.v2.Format(b)) + case OpcodeAtomicCas: + instSuffix = fmt.Sprintf("_%d, %s, %s, %s", 8*i.u1, i.v.Format(b), i.v2.Format(b), i.v3.Format(b)) + case OpcodeFence: + instSuffix = fmt.Sprintf(" %d", i.u1) + case OpcodeTailCallReturnCall, OpcodeTailCallReturnCallIndirect: + view := i.vs.View() + vs := make([]string, len(view)) + for idx := range vs { + vs[idx] = view[idx].Format(b) + } + if i.opcode == OpcodeCallIndirect { + instSuffix = fmt.Sprintf(" %s:%s, %s", i.v.Format(b), SignatureID(i.u1), strings.Join(vs, ", ")) + } else { + instSuffix = fmt.Sprintf(" %s:%s, %s", FuncRef(i.u1), SignatureID(i.u2), strings.Join(vs, ", ")) + } + case OpcodeWideningPairwiseDotProductS: + instSuffix = fmt.Sprintf(" %s, %s", i.v.Format(b), i.v2.Format(b)) + default: + panic(fmt.Sprintf("TODO: format for %s", i.opcode)) + } + + instr := i.opcode.String() + instSuffix + + var rvs []string + r1, rs := i.Returns() + if r1.Valid() { + rvs = append(rvs, r1.formatWithType(b)) + } + + for _, v := range rs { + rvs = append(rvs, v.formatWithType(b)) + } + + if len(rvs) > 0 { + return fmt.Sprintf("%s = %s", strings.Join(rvs, ", "), instr) + } else { + return instr + } +} + +// addArgumentBranchInst adds an argument to this instruction. +func (i *Instruction) addArgumentBranchInst(b *builder, v Value) { + switch i.opcode { + case OpcodeJump, OpcodeBrz, OpcodeBrnz: + i.vs = i.vs.Append(&b.varLengthPool, v) + default: + panic("BUG: " + i.opcode.String()) + } +} + +// Constant returns true if this instruction is a constant instruction. +func (i *Instruction) Constant() bool { + switch i.opcode { + case OpcodeIconst, OpcodeF32const, OpcodeF64const: + return true + } + return false +} + +// ConstantVal returns the constant value of this instruction. +// How to interpret the return value depends on the opcode. +func (i *Instruction) ConstantVal() (ret uint64) { + switch i.opcode { + case OpcodeIconst, OpcodeF32const, OpcodeF64const: + ret = i.u1 + default: + panic("TODO") + } + return +} + +// String implements fmt.Stringer. +func (o Opcode) String() (ret string) { + switch o { + case OpcodeInvalid: + return "invalid" + case OpcodeUndefined: + return "Undefined" + case OpcodeJump: + return "Jump" + case OpcodeBrz: + return "Brz" + case OpcodeBrnz: + return "Brnz" + case OpcodeBrTable: + return "BrTable" + case OpcodeExitWithCode: + return "Exit" + case OpcodeExitIfTrueWithCode: + return "ExitIfTrue" + case OpcodeReturn: + return "Return" + case OpcodeCall: + return "Call" + case OpcodeCallIndirect: + return "CallIndirect" + case OpcodeSplat: + return "Splat" + case OpcodeSwizzle: + return "Swizzle" + case OpcodeInsertlane: + return "Insertlane" + case OpcodeExtractlane: + return "Extractlane" + case OpcodeLoad: + return "Load" + case OpcodeLoadSplat: + return "LoadSplat" + case OpcodeStore: + return "Store" + case OpcodeUload8: + return "Uload8" + case OpcodeSload8: + return "Sload8" + case OpcodeIstore8: + return "Istore8" + case OpcodeUload16: + return "Uload16" + case OpcodeSload16: + return "Sload16" + case OpcodeIstore16: + return "Istore16" + case OpcodeUload32: + return "Uload32" + case OpcodeSload32: + return "Sload32" + case OpcodeIstore32: + return "Istore32" + case OpcodeIconst: + return "Iconst" + case OpcodeF32const: + return "F32const" + case OpcodeF64const: + return "F64const" + case OpcodeVconst: + return "Vconst" + case OpcodeShuffle: + return "Shuffle" + case OpcodeSelect: + return "Select" + case OpcodeVanyTrue: + return "VanyTrue" + case OpcodeVallTrue: + return "VallTrue" + case OpcodeVhighBits: + return "VhighBits" + case OpcodeIcmp: + return "Icmp" + case OpcodeIcmpImm: + return "IcmpImm" + case OpcodeVIcmp: + return "VIcmp" + case OpcodeIadd: + return "Iadd" + case OpcodeIsub: + return "Isub" + case OpcodeImul: + return "Imul" + case OpcodeUdiv: + return "Udiv" + case OpcodeSdiv: + return "Sdiv" + case OpcodeUrem: + return "Urem" + case OpcodeSrem: + return "Srem" + case OpcodeBand: + return "Band" + case OpcodeBor: + return "Bor" + case OpcodeBxor: + return "Bxor" + case OpcodeBnot: + return "Bnot" + case OpcodeRotl: + return "Rotl" + case OpcodeRotr: + return "Rotr" + case OpcodeIshl: + return "Ishl" + case OpcodeUshr: + return "Ushr" + case OpcodeSshr: + return "Sshr" + case OpcodeClz: + return "Clz" + case OpcodeCtz: + return "Ctz" + case OpcodePopcnt: + return "Popcnt" + case OpcodeFcmp: + return "Fcmp" + case OpcodeFadd: + return "Fadd" + case OpcodeFsub: + return "Fsub" + case OpcodeFmul: + return "Fmul" + case OpcodeFdiv: + return "Fdiv" + case OpcodeSqmulRoundSat: + return "SqmulRoundSat" + case OpcodeSqrt: + return "Sqrt" + case OpcodeFneg: + return "Fneg" + case OpcodeFabs: + return "Fabs" + case OpcodeFcopysign: + return "Fcopysign" + case OpcodeFmin: + return "Fmin" + case OpcodeFmax: + return "Fmax" + case OpcodeCeil: + return "Ceil" + case OpcodeFloor: + return "Floor" + case OpcodeTrunc: + return "Trunc" + case OpcodeNearest: + return "Nearest" + case OpcodeBitcast: + return "Bitcast" + case OpcodeIreduce: + return "Ireduce" + case OpcodeSnarrow: + return "Snarrow" + case OpcodeUnarrow: + return "Unarrow" + case OpcodeSwidenLow: + return "SwidenLow" + case OpcodeSwidenHigh: + return "SwidenHigh" + case OpcodeUwidenLow: + return "UwidenLow" + case OpcodeUwidenHigh: + return "UwidenHigh" + case OpcodeExtIaddPairwise: + return "IaddPairwise" + case OpcodeWideningPairwiseDotProductS: + return "WideningPairwiseDotProductS" + case OpcodeUExtend: + return "UExtend" + case OpcodeSExtend: + return "SExtend" + case OpcodeFpromote: + return "Fpromote" + case OpcodeFdemote: + return "Fdemote" + case OpcodeFvdemote: + return "Fvdemote" + case OpcodeFcvtToUint: + return "FcvtToUint" + case OpcodeFcvtToSint: + return "FcvtToSint" + case OpcodeFcvtToUintSat: + return "FcvtToUintSat" + case OpcodeFcvtToSintSat: + return "FcvtToSintSat" + case OpcodeFcvtFromUint: + return "FcvtFromUint" + case OpcodeFcvtFromSint: + return "FcvtFromSint" + case OpcodeAtomicRmw: + return "AtomicRmw" + case OpcodeAtomicCas: + return "AtomicCas" + case OpcodeAtomicLoad: + return "AtomicLoad" + case OpcodeAtomicStore: + return "AtomicStore" + case OpcodeFence: + return "Fence" + case OpcodeTailCallReturnCall: + return "ReturnCall" + case OpcodeTailCallReturnCallIndirect: + return "ReturnCallIndirect" + case OpcodeVbor: + return "Vbor" + case OpcodeVbxor: + return "Vbxor" + case OpcodeVband: + return "Vband" + case OpcodeVbandnot: + return "Vbandnot" + case OpcodeVbnot: + return "Vbnot" + case OpcodeVbitselect: + return "Vbitselect" + case OpcodeVIadd: + return "VIadd" + case OpcodeVSaddSat: + return "VSaddSat" + case OpcodeVUaddSat: + return "VUaddSat" + case OpcodeVSsubSat: + return "VSsubSat" + case OpcodeVUsubSat: + return "VUsubSat" + case OpcodeVAvgRound: + return "OpcodeVAvgRound" + case OpcodeVIsub: + return "VIsub" + case OpcodeVImin: + return "VImin" + case OpcodeVUmin: + return "VUmin" + case OpcodeVImax: + return "VImax" + case OpcodeVUmax: + return "VUmax" + case OpcodeVImul: + return "VImul" + case OpcodeVIabs: + return "VIabs" + case OpcodeVIneg: + return "VIneg" + case OpcodeVIpopcnt: + return "VIpopcnt" + case OpcodeVIshl: + return "VIshl" + case OpcodeVUshr: + return "VUshr" + case OpcodeVSshr: + return "VSshr" + case OpcodeVFabs: + return "VFabs" + case OpcodeVFmax: + return "VFmax" + case OpcodeVFmin: + return "VFmin" + case OpcodeVFneg: + return "VFneg" + case OpcodeVFadd: + return "VFadd" + case OpcodeVFsub: + return "VFsub" + case OpcodeVFmul: + return "VFmul" + case OpcodeVFdiv: + return "VFdiv" + case OpcodeVFcmp: + return "VFcmp" + case OpcodeVCeil: + return "VCeil" + case OpcodeVFloor: + return "VFloor" + case OpcodeVTrunc: + return "VTrunc" + case OpcodeVNearest: + return "VNearest" + case OpcodeVMaxPseudo: + return "VMaxPseudo" + case OpcodeVMinPseudo: + return "VMinPseudo" + case OpcodeVSqrt: + return "VSqrt" + case OpcodeVFcvtToUintSat: + return "VFcvtToUintSat" + case OpcodeVFcvtToSintSat: + return "VFcvtToSintSat" + case OpcodeVFcvtFromUint: + return "VFcvtFromUint" + case OpcodeVFcvtFromSint: + return "VFcvtFromSint" + case OpcodeFvpromoteLow: + return "FvpromoteLow" + case OpcodeVZeroExtLoad: + return "VZeroExtLoad" + } + panic(fmt.Sprintf("unknown opcode %d", o)) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/pass.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/pass.go new file mode 100644 index 00000000000..b9763791dd5 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/pass.go @@ -0,0 +1,393 @@ +package ssa + +import ( + "fmt" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +// RunPasses implements Builder.RunPasses. +// +// The order here matters; some pass depends on the previous ones. +// +// Note that passes suffixed with "Opt" are the optimization passes, meaning that they edit the instructions and blocks +// while the other passes are not, like passEstimateBranchProbabilities does not edit them, but only calculates the additional information. +func (b *builder) RunPasses() { + b.runPreBlockLayoutPasses() + b.runBlockLayoutPass() + b.runPostBlockLayoutPasses() + b.runFinalizingPasses() +} + +func (b *builder) runPreBlockLayoutPasses() { + passSortSuccessors(b) + passDeadBlockEliminationOpt(b) + // The result of passCalculateImmediateDominators will be used by various passes below. + passCalculateImmediateDominators(b) + passRedundantPhiEliminationOpt(b) + passNopInstElimination(b) + + // TODO: implement either conversion of irreducible CFG into reducible one, or irreducible CFG detection where we panic. + // WebAssembly program shouldn't result in irreducible CFG, but we should handle it properly in just in case. + // See FixIrreducible pass in LLVM: https://llvm.org/doxygen/FixIrreducible_8cpp_source.html + + // TODO: implement more optimization passes like: + // block coalescing. + // Copy-propagation. + // Constant folding. + // Common subexpression elimination. + // Arithmetic simplifications. + // and more! + + // passDeadCodeEliminationOpt could be more accurate if we do this after other optimizations. + passDeadCodeEliminationOpt(b) + b.donePreBlockLayoutPasses = true +} + +func (b *builder) runBlockLayoutPass() { + if !b.donePreBlockLayoutPasses { + panic("runBlockLayoutPass must be called after all pre passes are done") + } + passLayoutBlocks(b) + b.doneBlockLayout = true +} + +// runPostBlockLayoutPasses runs the post block layout passes. After this point, CFG is somewhat stable, +// but still can be modified before finalizing passes. At this point, critical edges are split by passLayoutBlocks. +func (b *builder) runPostBlockLayoutPasses() { + if !b.doneBlockLayout { + panic("runPostBlockLayoutPasses must be called after block layout pass is done") + } + // TODO: Do more. e.g. tail duplication, loop unrolling, etc. + + b.donePostBlockLayoutPasses = true +} + +// runFinalizingPasses runs the finalizing passes. After this point, CFG should not be modified. +func (b *builder) runFinalizingPasses() { + if !b.donePostBlockLayoutPasses { + panic("runFinalizingPasses must be called after post block layout passes are done") + } + // Critical edges are split, so we fix the loop nesting forest. + passBuildLoopNestingForest(b) + passBuildDominatorTree(b) + // Now that we know the final placement of the blocks, we can explicitly mark the fallthrough jumps. + b.markFallthroughJumps() +} + +// passDeadBlockEliminationOpt searches the unreachable blocks, and sets the basicBlock.invalid flag true if so. +func passDeadBlockEliminationOpt(b *builder) { + entryBlk := b.entryBlk() + b.blkStack = append(b.blkStack, entryBlk) + for len(b.blkStack) > 0 { + reachableBlk := b.blkStack[len(b.blkStack)-1] + b.blkStack = b.blkStack[:len(b.blkStack)-1] + reachableBlk.visited = 1 + + if !reachableBlk.sealed && !reachableBlk.ReturnBlock() { + panic(fmt.Sprintf("%s is not sealed", reachableBlk)) + } + + if wazevoapi.SSAValidationEnabled { + reachableBlk.validate(b) + } + + for _, succ := range reachableBlk.success { + if succ.visited == 1 { + continue + } + b.blkStack = append(b.blkStack, succ) + } + } + + for blk := b.blockIteratorBegin(); blk != nil; blk = b.blockIteratorNext() { + if blk.visited != 1 { + blk.invalid = true + } + blk.visited = 0 + } +} + +// passRedundantPhiEliminationOpt eliminates the redundant PHIs (in our terminology, parameters of a block). +// This requires the reverse post-order traversal to be calculated before calling this function, +// hence passCalculateImmediateDominators must be called before this. +func passRedundantPhiEliminationOpt(b *builder) { + redundantParams := b.redundantParams[:0] // reuse the slice from previous iterations. + + // TODO: this might be costly for large programs, but at least, as far as I did the experiment, it's almost the + // same as the single iteration version in terms of the overall compilation time. That *might be* mostly thanks to the fact + // that removing many PHIs results in the reduction of the total instructions, not because of this indefinite iteration is + // relatively small. For example, sqlite speedtest binary results in the large number of redundant PHIs, + // the maximum number of iteration was 22, which seems to be acceptable but not that small either since the + // complexity here is O(BlockNum * Iterations) at the worst case where BlockNum might be the order of thousands. + // -- Note -- + // Currently, each iteration can run in any order of blocks, but it empirically converges quickly in practice when + // running on the reverse post-order. It might be possible to optimize this further by using the dominator tree. + for { + changed := false + _ = b.blockIteratorReversePostOrderBegin() // skip entry block! + // Below, we intentionally use the named iteration variable name, as this comes with inevitable nested for loops! + for blk := b.blockIteratorReversePostOrderNext(); blk != nil; blk = b.blockIteratorReversePostOrderNext() { + params := blk.params.View() + paramNum := len(params) + + for paramIndex := 0; paramIndex < paramNum; paramIndex++ { + phiValue := params[paramIndex] + redundant := true + + nonSelfReferencingValue := ValueInvalid + for predIndex := range blk.preds { + br := blk.preds[predIndex].branch + // Resolve the alias in the arguments so that we could use the previous iteration's result. + b.resolveArgumentAlias(br) + pred := br.vs.View()[paramIndex] + if pred == phiValue { + // This is self-referencing: PHI from the same PHI. + continue + } + + if !nonSelfReferencingValue.Valid() { + nonSelfReferencingValue = pred + continue + } + + if nonSelfReferencingValue != pred { + redundant = false + break + } + } + + if !nonSelfReferencingValue.Valid() { + // This shouldn't happen, and must be a bug in builder.go. + panic("BUG: params added but only self-referencing") + } + + if redundant { + redundantParams = append(redundantParams, redundantParam{ + index: paramIndex, uniqueValue: nonSelfReferencingValue, + }) + } + } + + if len(redundantParams) == 0 { + continue + } + changed = true + + // Remove the redundant PHIs from the argument list of branching instructions. + for predIndex := range blk.preds { + redundantParamsCur, predParamCur := 0, 0 + predBlk := blk.preds[predIndex] + branchInst := predBlk.branch + view := branchInst.vs.View() + for argIndex, value := range view { + if len(redundantParams) == redundantParamsCur || + redundantParams[redundantParamsCur].index != argIndex { + view[predParamCur] = value + predParamCur++ + } else { + redundantParamsCur++ + } + } + branchInst.vs.Cut(predParamCur) + } + + // Still need to have the definition of the value of the PHI (previously as the parameter). + for i := range redundantParams { + redundantValue := &redundantParams[i] + phiValue := params[redundantValue.index] + // Create an alias in this block from the only phi argument to the phi value. + b.alias(phiValue, redundantValue.uniqueValue) + } + + // Finally, Remove the param from the blk. + paramsCur, redundantParamsCur := 0, 0 + for paramIndex := 0; paramIndex < paramNum; paramIndex++ { + param := params[paramIndex] + if len(redundantParams) == redundantParamsCur || redundantParams[redundantParamsCur].index != paramIndex { + params[paramsCur] = param + paramsCur++ + } else { + redundantParamsCur++ + } + } + blk.params.Cut(paramsCur) + + // Clears the map for the next iteration. + redundantParams = redundantParams[:0] + } + + if !changed { + break + } + } + + // Reuse the slice for the future passes. + b.redundantParams = redundantParams +} + +// passDeadCodeEliminationOpt traverses all the instructions, and calculates the reference count of each Value, and +// eliminates all the unnecessary instructions whose ref count is zero. +// The results are stored at builder.valueRefCounts. This also assigns a InstructionGroupID to each Instruction +// during the process. This is the last SSA-level optimization pass and after this, +// the SSA function is ready to be used by backends. +// +// TODO: the algorithm here might not be efficient. Get back to this later. +func passDeadCodeEliminationOpt(b *builder) { + nvid := int(b.nextValueID) + if nvid >= len(b.valuesInfo) { + l := nvid - len(b.valuesInfo) + 1 + b.valuesInfo = append(b.valuesInfo, make([]ValueInfo, l)...) + view := b.valuesInfo[len(b.valuesInfo)-l:] + for i := range view { + view[i].alias = ValueInvalid + } + } + + // First, we gather all the instructions with side effects. + liveInstructions := b.instStack[:0] + // During the process, we will assign InstructionGroupID to each instruction, which is not + // relevant to dead code elimination, but we need in the backend. + var gid InstructionGroupID + for blk := b.blockIteratorBegin(); blk != nil; blk = b.blockIteratorNext() { + for cur := blk.rootInstr; cur != nil; cur = cur.next { + cur.gid = gid + switch cur.sideEffect() { + case sideEffectTraps: + // The trappable should always be alive. + liveInstructions = append(liveInstructions, cur) + case sideEffectStrict: + liveInstructions = append(liveInstructions, cur) + // The strict side effect should create different instruction groups. + gid++ + } + } + } + + // Find all the instructions referenced by live instructions transitively. + for len(liveInstructions) > 0 { + tail := len(liveInstructions) - 1 + live := liveInstructions[tail] + liveInstructions = liveInstructions[:tail] + if live.live { + // If it's already marked alive, this is referenced multiple times, + // so we can skip it. + continue + } + live.live = true + + // Before we walk, we need to resolve the alias first. + b.resolveArgumentAlias(live) + + v1, v2, v3, vs := live.Args() + if v1.Valid() { + producingInst := b.InstructionOfValue(v1) + if producingInst != nil { + liveInstructions = append(liveInstructions, producingInst) + } + } + + if v2.Valid() { + producingInst := b.InstructionOfValue(v2) + if producingInst != nil { + liveInstructions = append(liveInstructions, producingInst) + } + } + + if v3.Valid() { + producingInst := b.InstructionOfValue(v3) + if producingInst != nil { + liveInstructions = append(liveInstructions, producingInst) + } + } + + for _, v := range vs { + producingInst := b.InstructionOfValue(v) + if producingInst != nil { + liveInstructions = append(liveInstructions, producingInst) + } + } + } + + // Now that all the live instructions are flagged as live=true, we eliminate all dead instructions. + for blk := b.blockIteratorBegin(); blk != nil; blk = b.blockIteratorNext() { + for cur := blk.rootInstr; cur != nil; cur = cur.next { + if !cur.live { + // Remove the instruction from the list. + if prev := cur.prev; prev != nil { + prev.next = cur.next + } else { + blk.rootInstr = cur.next + } + if next := cur.next; next != nil { + next.prev = cur.prev + } + continue + } + + // If the value alive, we can be sure that arguments are used definitely. + // Hence, we can increment the value reference counts. + v1, v2, v3, vs := cur.Args() + if v1.Valid() { + b.incRefCount(v1.ID(), cur) + } + if v2.Valid() { + b.incRefCount(v2.ID(), cur) + } + if v3.Valid() { + b.incRefCount(v3.ID(), cur) + } + for _, v := range vs { + b.incRefCount(v.ID(), cur) + } + } + } + + b.instStack = liveInstructions // we reuse the stack for the next iteration. +} + +func (b *builder) incRefCount(id ValueID, from *Instruction) { + if wazevoapi.SSALoggingEnabled { + fmt.Printf("v%d referenced from %v\n", id, from.Format(b)) + } + info := &b.valuesInfo[id] + info.RefCount++ +} + +// passNopInstElimination eliminates the instructions which is essentially a no-op. +func passNopInstElimination(b *builder) { + for blk := b.blockIteratorBegin(); blk != nil; blk = b.blockIteratorNext() { + for cur := blk.rootInstr; cur != nil; cur = cur.next { + switch cur.Opcode() { + // TODO: add more logics here. + case OpcodeIshl, OpcodeSshr, OpcodeUshr: + x, amount := cur.Arg2() + definingInst := b.InstructionOfValue(amount) + if definingInst == nil { + // If there's no defining instruction, that means the amount is coming from the parameter. + continue + } + if definingInst.Constant() { + v := definingInst.ConstantVal() + + if x.Type().Bits() == 64 { + v = v % 64 + } else { + v = v % 32 + } + if v == 0 { + b.alias(cur.Return(), x) + } + } + } + } + } +} + +// passSortSuccessors sorts the successors of each block in the natural program order. +func passSortSuccessors(b *builder) { + for i := 0; i < b.basicBlocksPool.Allocated(); i++ { + blk := b.basicBlocksPool.View(i) + sortBlocks(blk.success) + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/pass_blk_layouts.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/pass_blk_layouts.go new file mode 100644 index 00000000000..0118e8b2e5f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/pass_blk_layouts.go @@ -0,0 +1,334 @@ +package ssa + +import ( + "fmt" + "strings" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +// passLayoutBlocks implements Builder.LayoutBlocks. This re-organizes builder.reversePostOrderedBasicBlocks. +// +// TODO: there are tons of room for improvement here. e.g. LLVM has BlockPlacementPass using BlockFrequencyInfo, +// BranchProbabilityInfo, and LoopInfo to do a much better job. Also, if we have the profiling instrumentation +// like ball-larus algorithm, then we could do profile-guided optimization. Basically all of them are trying +// to maximize the fall-through opportunities which is most efficient. +// +// Here, fallthrough happens when a block ends with jump instruction whose target is the right next block in the +// builder.reversePostOrderedBasicBlocks. +// +// Currently, we just place blocks using the DFS reverse post-order of the dominator tree with the heuristics: +// 1. a split edge trampoline towards a loop header will be placed as a fallthrough. +// 2. we invert the brz and brnz if it makes the fallthrough more likely. +// +// This heuristic is done in maybeInvertBranches function. +func passLayoutBlocks(b *builder) { + // We might end up splitting critical edges which adds more basic blocks, + // so we store the currently existing basic blocks in nonSplitBlocks temporarily. + // That way we can iterate over the original basic blocks while appending new ones into reversePostOrderedBasicBlocks. + nonSplitBlocks := b.blkStack[:0] + for i, blk := range b.reversePostOrderedBasicBlocks { + if !blk.Valid() { + continue + } + nonSplitBlocks = append(nonSplitBlocks, blk) + if i != len(b.reversePostOrderedBasicBlocks)-1 { + _ = maybeInvertBranches(b, blk, b.reversePostOrderedBasicBlocks[i+1]) + } + } + + var trampolines []*basicBlock + + // Reset the order slice since we update on the fly by splitting critical edges. + b.reversePostOrderedBasicBlocks = b.reversePostOrderedBasicBlocks[:0] + uninsertedTrampolines := b.blkStack2[:0] + for _, blk := range nonSplitBlocks { + for i := range blk.preds { + pred := blk.preds[i].blk + if pred.visited == 1 || !pred.Valid() { + continue + } else if pred.reversePostOrder < blk.reversePostOrder { + // This means the edge is critical, and this pred is the trampoline and yet to be inserted. + // Split edge trampolines must come before the destination in reverse post-order. + b.reversePostOrderedBasicBlocks = append(b.reversePostOrderedBasicBlocks, pred) + pred.visited = 1 // mark as inserted. + } + } + + // Now that we've already added all the potential trampoline blocks incoming to this block, + // we can add this block itself. + b.reversePostOrderedBasicBlocks = append(b.reversePostOrderedBasicBlocks, blk) + blk.visited = 1 // mark as inserted. + + if len(blk.success) < 2 { + // There won't be critical edge originating from this block. + continue + } else if blk.currentInstr.opcode == OpcodeBrTable { + // We don't split critical edges here, because at the construction site of BrTable, we already split the edges. + continue + } + + for sidx, succ := range blk.success { + if !succ.ReturnBlock() && // If the successor is a return block, we need to split the edge any way because we need "epilogue" to be inserted. + // Plus if there's no multiple incoming edges to this successor, (pred, succ) is not critical. + len(succ.preds) < 2 { + continue + } + + // Otherwise, we are sure this is a critical edge. To modify the CFG, we need to find the predecessor info + // from the successor. + var predInfo *basicBlockPredecessorInfo + for i := range succ.preds { // This linear search should not be a problem since the number of predecessors should almost always small. + pred := &succ.preds[i] + if pred.blk == blk { + predInfo = pred + break + } + } + + if predInfo == nil { + // This must be a bug in somewhere around branch manipulation. + panic("BUG: predecessor info not found while the successor exists in successors list") + } + + if wazevoapi.SSALoggingEnabled { + fmt.Printf("trying to split edge from %d->%d at %s\n", + blk.ID(), succ.ID(), predInfo.branch.Format(b)) + } + + trampoline := b.splitCriticalEdge(blk, succ, predInfo) + // Update the successors slice because the target is no longer the original `succ`. + blk.success[sidx] = trampoline + + if wazevoapi.SSAValidationEnabled { + trampolines = append(trampolines, trampoline) + } + + if wazevoapi.SSALoggingEnabled { + fmt.Printf("edge split from %d->%d at %s as %d->%d->%d \n", + blk.ID(), succ.ID(), predInfo.branch.Format(b), + blk.ID(), trampoline.ID(), succ.ID()) + } + + fallthroughBranch := blk.currentInstr + if fallthroughBranch.opcode == OpcodeJump && BasicBlockID(fallthroughBranch.rValue) == trampoline.id { + // This can be lowered as fallthrough at the end of the block. + b.reversePostOrderedBasicBlocks = append(b.reversePostOrderedBasicBlocks, trampoline) + trampoline.visited = 1 // mark as inserted. + } else { + uninsertedTrampolines = append(uninsertedTrampolines, trampoline) + } + } + + for _, trampoline := range uninsertedTrampolines { + if trampoline.success[0].reversePostOrder <= trampoline.reversePostOrder { // "<=", not "<" because the target might be itself. + // This means the critical edge was backward, so we insert after the current block immediately. + b.reversePostOrderedBasicBlocks = append(b.reversePostOrderedBasicBlocks, trampoline) + trampoline.visited = 1 // mark as inserted. + } // If the target is forward, we can wait to insert until the target is inserted. + } + uninsertedTrampolines = uninsertedTrampolines[:0] // Reuse the stack for the next block. + } + + if wazevoapi.SSALoggingEnabled { + var bs []string + for _, blk := range b.reversePostOrderedBasicBlocks { + bs = append(bs, blk.Name()) + } + fmt.Println("ordered blocks: ", strings.Join(bs, ", ")) + } + + if wazevoapi.SSAValidationEnabled { + for _, trampoline := range trampolines { + if trampoline.visited != 1 { + panic("BUG: trampoline block not inserted: " + trampoline.formatHeader(b)) + } + trampoline.validate(b) + } + } + + // Reuse the stack for the next iteration. + b.blkStack2 = uninsertedTrampolines[:0] +} + +// markFallthroughJumps finds the fallthrough jumps and marks them as such. +func (b *builder) markFallthroughJumps() { + l := len(b.reversePostOrderedBasicBlocks) - 1 + for i, blk := range b.reversePostOrderedBasicBlocks { + if i < l { + cur := blk.currentInstr + if cur.opcode == OpcodeJump && BasicBlockID(cur.rValue) == b.reversePostOrderedBasicBlocks[i+1].id { + cur.AsFallthroughJump() + } + } + } +} + +// maybeInvertBranches inverts the branch instructions if it is likely possible to the fallthrough more likely with simple heuristics. +// nextInRPO is the next block in the reverse post-order. +// +// Returns true if the branch is inverted for testing purpose. +func maybeInvertBranches(b *builder, now *basicBlock, nextInRPO *basicBlock) bool { + fallthroughBranch := now.currentInstr + if fallthroughBranch.opcode == OpcodeBrTable { + return false + } + + condBranch := fallthroughBranch.prev + if condBranch == nil || (condBranch.opcode != OpcodeBrnz && condBranch.opcode != OpcodeBrz) { + return false + } + + if len(fallthroughBranch.vs.View()) != 0 || len(condBranch.vs.View()) != 0 { + // If either one of them has arguments, we don't invert the branches. + return false + } + + // So this block has two branches (a conditional branch followed by an unconditional branch) at the end. + // We can invert the condition of the branch if it makes the fallthrough more likely. + + fallthroughTarget := b.basicBlock(BasicBlockID(fallthroughBranch.rValue)) + condTarget := b.basicBlock(BasicBlockID(condBranch.rValue)) + + if fallthroughTarget.loopHeader { + // First, if the tail's target is loopHeader, we don't need to do anything here, + // because the edge is likely to be critical edge for complex loops (e.g. loop with branches inside it). + // That means, we will split the edge in the end of LayoutBlocks function, and insert the trampoline block + // right after this block, which will be fallthrough in any way. + return false + } else if condTarget.loopHeader { + // On the other hand, if the condBranch's target is loopHeader, we invert the condition of the branch + // so that we could get the fallthrough to the trampoline block. + goto invert + } + + if fallthroughTarget == nextInRPO { + // Also, if the tail's target is the next block in the reverse post-order, we don't need to do anything here, + // because if this is not critical edge, we would end up placing these two blocks adjacent to each other. + // Even if it is the critical edge, we place the trampoline block right after this block, which will be fallthrough in any way. + return false + } else if condTarget == nextInRPO { + // If the condBranch's target is the next block in the reverse post-order, we invert the condition of the branch + // so that we could get the fallthrough to the block. + goto invert + } else { + return false + } + +invert: + for i := range fallthroughTarget.preds { + pred := &fallthroughTarget.preds[i] + if pred.branch == fallthroughBranch { + pred.branch = condBranch + break + } + } + for i := range condTarget.preds { + pred := &condTarget.preds[i] + if pred.branch == condBranch { + pred.branch = fallthroughBranch + break + } + } + + condBranch.InvertBrx() + condBranch.rValue = Value(fallthroughTarget.ID()) + fallthroughBranch.rValue = Value(condTarget.ID()) + if wazevoapi.SSALoggingEnabled { + fmt.Printf("inverting branches at %d->%d and %d->%d\n", + now.ID(), fallthroughTarget.ID(), now.ID(), condTarget.ID()) + } + + return true +} + +// splitCriticalEdge splits the critical edge between the given predecessor (`pred`) and successor (owning `predInfo`). +// +// - `pred` is the source of the critical edge, +// - `succ` is the destination of the critical edge, +// - `predInfo` is the predecessor info in the succ.preds slice which represents the critical edge. +// +// Why splitting critical edges is important? See following links: +// +// - https://en.wikipedia.org/wiki/Control-flow_graph +// - https://nickdesaulniers.github.io/blog/2023/01/27/critical-edge-splitting/ +// +// The returned basic block is the trampoline block which is inserted to split the critical edge. +func (b *builder) splitCriticalEdge(pred, succ *basicBlock, predInfo *basicBlockPredecessorInfo) *basicBlock { + // In the following, we convert the following CFG: + // + // pred --(originalBranch)--> succ + // + // to the following CFG: + // + // pred --(newBranch)--> trampoline --(originalBranch)-> succ + // + // where trampoline is a new basic block which is created to split the critical edge. + + trampoline := b.allocateBasicBlock() + if int(trampoline.id) >= len(b.dominators) { + b.dominators = append(b.dominators, make([]*basicBlock, trampoline.id+1)...) + } + b.dominators[trampoline.id] = pred + + originalBranch := predInfo.branch + + // Replace originalBranch with the newBranch. + newBranch := b.AllocateInstruction() + newBranch.opcode = originalBranch.opcode + newBranch.rValue = Value(trampoline.ID()) + switch originalBranch.opcode { + case OpcodeJump: + case OpcodeBrz, OpcodeBrnz: + originalBranch.opcode = OpcodeJump // Trampoline consists of one unconditional branch. + newBranch.v = originalBranch.v + originalBranch.v = ValueInvalid + default: + panic("BUG: critical edge shouldn't be originated from br_table") + } + swapInstruction(pred, originalBranch, newBranch) + + // Replace the original branch with the new branch. + trampoline.rootInstr = originalBranch + trampoline.currentInstr = originalBranch + trampoline.success = append(trampoline.success, succ) // Do not use []*basicBlock{pred} because we might have already allocated the slice. + trampoline.preds = append(trampoline.preds, // same as ^. + basicBlockPredecessorInfo{blk: pred, branch: newBranch}) + b.Seal(trampoline) + + // Update the original branch to point to the trampoline. + predInfo.blk = trampoline + predInfo.branch = originalBranch + + if wazevoapi.SSAValidationEnabled { + trampoline.validate(b) + } + + if len(trampoline.params.View()) > 0 { + panic("trampoline should not have params") + } + + // Assign the same order as the original block so that this will be placed before the actual destination. + trampoline.reversePostOrder = pred.reversePostOrder + return trampoline +} + +// swapInstruction replaces `old` in the block `blk` with `New`. +func swapInstruction(blk *basicBlock, old, New *Instruction) { + if blk.rootInstr == old { + blk.rootInstr = New + next := old.next + New.next = next + next.prev = New + } else { + if blk.currentInstr == old { + blk.currentInstr = New + } + prev := old.prev + prev.next, New.prev = New, prev + if next := old.next; next != nil { + New.next, next.prev = next, New + } + } + old.prev, old.next = nil, nil +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/pass_cfg.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/pass_cfg.go new file mode 100644 index 00000000000..e8288c4bd34 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/pass_cfg.go @@ -0,0 +1,313 @@ +package ssa + +import ( + "fmt" + "math" + "strings" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +// passCalculateImmediateDominators calculates immediate dominators for each basic block. +// The result is stored in b.dominators. This make it possible for the following passes to +// use builder.isDominatedBy to check if a block is dominated by another block. +// +// At the last of pass, this function also does the loop detection and sets the basicBlock.loop flag. +func passCalculateImmediateDominators(b *builder) { + reversePostOrder := b.reversePostOrderedBasicBlocks[:0] + + // Store the reverse postorder from the entrypoint into reversePostOrder slice. + // This calculation of reverse postorder is not described in the paper, + // so we use heuristic to calculate it so that we could potentially handle arbitrary + // complex CFGs under the assumption that success is sorted in program's natural order. + // That means blk.success[i] always appears before blk.success[i+1] in the source program, + // which is a reasonable assumption as long as SSA Builder is properly used. + // + // First we push blocks in postorder iteratively visit successors of the entry block. + entryBlk := b.entryBlk() + exploreStack := append(b.blkStack[:0], entryBlk) + // These flags are used to track the state of the block in the DFS traversal. + // We temporarily use the reversePostOrder field to store the state. + const visitStateUnseen, visitStateSeen, visitStateDone = 0, 1, 2 + entryBlk.visited = visitStateSeen + for len(exploreStack) > 0 { + tail := len(exploreStack) - 1 + blk := exploreStack[tail] + exploreStack = exploreStack[:tail] + switch blk.visited { + case visitStateUnseen: + // This is likely a bug in the frontend. + panic("BUG: unsupported CFG") + case visitStateSeen: + // This is the first time to pop this block, and we have to see the successors first. + // So push this block again to the stack. + exploreStack = append(exploreStack, blk) + // And push the successors to the stack if necessary. + for _, succ := range blk.success { + if succ.ReturnBlock() || succ.invalid { + continue + } + if succ.visited == visitStateUnseen { + succ.visited = visitStateSeen + exploreStack = append(exploreStack, succ) + } + } + // Finally, we could pop this block once we pop all of its successors. + blk.visited = visitStateDone + case visitStateDone: + // Note: at this point we push blk in postorder despite its name. + reversePostOrder = append(reversePostOrder, blk) + default: + panic("BUG") + } + } + // At this point, reversePostOrder has postorder actually, so we reverse it. + for i := len(reversePostOrder)/2 - 1; i >= 0; i-- { + j := len(reversePostOrder) - 1 - i + reversePostOrder[i], reversePostOrder[j] = reversePostOrder[j], reversePostOrder[i] + } + + for i, blk := range reversePostOrder { + blk.reversePostOrder = int32(i) + } + + // Reuse the dominators slice if possible from the previous computation of function. + b.dominators = b.dominators[:cap(b.dominators)] + if len(b.dominators) < b.basicBlocksPool.Allocated() { + // Generously reserve space in the slice because the slice will be reused future allocation. + b.dominators = append(b.dominators, make([]*basicBlock, b.basicBlocksPool.Allocated())...) + } + calculateDominators(reversePostOrder, b.dominators) + + // Reuse the slices for the future use. + b.blkStack = exploreStack + + // For the following passes. + b.reversePostOrderedBasicBlocks = reversePostOrder + + // Ready to detect loops! + subPassLoopDetection(b) +} + +// calculateDominators calculates the immediate dominator of each node in the CFG, and store the result in `doms`. +// The algorithm is based on the one described in the paper "A Simple, Fast Dominance Algorithm" +// https://www.cs.rice.edu/~keith/EMBED/dom.pdf which is a faster/simple alternative to the well known Lengauer-Tarjan algorithm. +// +// The following code almost matches the pseudocode in the paper with one exception (see the code comment below). +// +// The result slice `doms` must be pre-allocated with the size larger than the size of dfsBlocks. +func calculateDominators(reversePostOrderedBlks []*basicBlock, doms []*basicBlock) { + entry, reversePostOrderedBlks := reversePostOrderedBlks[0], reversePostOrderedBlks[1: /* skips entry point */] + for _, blk := range reversePostOrderedBlks { + doms[blk.id] = nil + } + doms[entry.id] = entry + + changed := true + for changed { + changed = false + for _, blk := range reversePostOrderedBlks { + var u *basicBlock + for i := range blk.preds { + pred := blk.preds[i].blk + // Skip if this pred is not reachable yet. Note that this is not described in the paper, + // but it is necessary to handle nested loops etc. + if doms[pred.id] == nil { + continue + } + + if u == nil { + u = pred + continue + } else { + u = intersect(doms, u, pred) + } + } + if doms[blk.id] != u { + doms[blk.id] = u + changed = true + } + } + } +} + +// intersect returns the common dominator of blk1 and blk2. +// +// This is the `intersect` function in the paper. +func intersect(doms []*basicBlock, blk1 *basicBlock, blk2 *basicBlock) *basicBlock { + finger1, finger2 := blk1, blk2 + for finger1 != finger2 { + // Move the 'finger1' upwards to its immediate dominator. + for finger1.reversePostOrder > finger2.reversePostOrder { + finger1 = doms[finger1.id] + } + // Move the 'finger2' upwards to its immediate dominator. + for finger2.reversePostOrder > finger1.reversePostOrder { + finger2 = doms[finger2.id] + } + } + return finger1 +} + +// subPassLoopDetection detects loops in the function using the immediate dominators. +// +// This is run at the last of passCalculateImmediateDominators. +func subPassLoopDetection(b *builder) { + for blk := b.blockIteratorBegin(); blk != nil; blk = b.blockIteratorNext() { + for i := range blk.preds { + pred := blk.preds[i].blk + if pred.invalid { + continue + } + if b.isDominatedBy(pred, blk) { + blk.loopHeader = true + } + } + } +} + +// buildLoopNestingForest builds the loop nesting forest for the function. +// This must be called after branch splitting since it relies on the CFG. +func passBuildLoopNestingForest(b *builder) { + ent := b.entryBlk() + doms := b.dominators + for _, blk := range b.reversePostOrderedBasicBlocks { + n := doms[blk.id] + for !n.loopHeader && n != ent { + n = doms[n.id] + } + + if n == ent && blk.loopHeader { + b.loopNestingForestRoots = append(b.loopNestingForestRoots, blk) + } else if n == ent { + } else if n.loopHeader { + n.loopNestingForestChildren = n.loopNestingForestChildren.Append(&b.varLengthBasicBlockPool, blk) + } + } + + if wazevoapi.SSALoggingEnabled { + for _, root := range b.loopNestingForestRoots { + printLoopNestingForest(root.(*basicBlock), 0) + } + } +} + +func printLoopNestingForest(root *basicBlock, depth int) { + fmt.Println(strings.Repeat("\t", depth), "loop nesting forest root:", root.ID()) + for _, child := range root.loopNestingForestChildren.View() { + fmt.Println(strings.Repeat("\t", depth+1), "child:", child.ID()) + if child.LoopHeader() { + printLoopNestingForest(child.(*basicBlock), depth+2) + } + } +} + +type dominatorSparseTree struct { + time int32 + euler []*basicBlock + first, depth []int32 + table [][]int32 +} + +// passBuildDominatorTree builds the dominator tree for the function, and constructs builder.sparseTree. +func passBuildDominatorTree(b *builder) { + // First we materialize the children of each node in the dominator tree. + idoms := b.dominators + for _, blk := range b.reversePostOrderedBasicBlocks { + parent := idoms[blk.id] + if parent == nil { + panic("BUG") + } else if parent == blk { + // This is the entry block. + continue + } + if prev := parent.child; prev == nil { + parent.child = blk + } else { + parent.child = blk + blk.sibling = prev + } + } + + // Reset the state from the previous computation. + n := b.basicBlocksPool.Allocated() + st := &b.sparseTree + st.euler = append(st.euler[:0], make([]*basicBlock, 2*n-1)...) + st.first = append(st.first[:0], make([]int32, n)...) + for i := range st.first { + st.first[i] = -1 + } + st.depth = append(st.depth[:0], make([]int32, 2*n-1)...) + st.time = 0 + + // Start building the sparse tree. + st.eulerTour(b.entryBlk(), 0) + st.buildSparseTable() +} + +func (dt *dominatorSparseTree) eulerTour(node *basicBlock, height int32) { + if wazevoapi.SSALoggingEnabled { + fmt.Println(strings.Repeat("\t", int(height)), "euler tour:", node.ID()) + } + dt.euler[dt.time] = node + dt.depth[dt.time] = height + if dt.first[node.id] == -1 { + dt.first[node.id] = dt.time + } + dt.time++ + + for child := node.child; child != nil; child = child.sibling { + dt.eulerTour(child, height+1) + dt.euler[dt.time] = node // add the current node again after visiting a child + dt.depth[dt.time] = height + dt.time++ + } +} + +// buildSparseTable builds a sparse table for RMQ queries. +func (dt *dominatorSparseTree) buildSparseTable() { + n := len(dt.depth) + k := int(math.Log2(float64(n))) + 1 + table := dt.table + + if n >= len(table) { + table = append(table, make([][]int32, n-len(table)+1)...) + } + for i := range table { + if len(table[i]) < k { + table[i] = append(table[i], make([]int32, k-len(table[i]))...) + } + table[i][0] = int32(i) + } + + for j := 1; 1< first[v] { + u, v = v, u + } + return dt.euler[dt.rmq(first[u], first[v])] +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/signature.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/signature.go new file mode 100644 index 00000000000..43483395a24 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/signature.go @@ -0,0 +1,49 @@ +package ssa + +import ( + "fmt" + "strings" +) + +// Signature is a function prototype. +type Signature struct { + // ID is a unique identifier for this signature used to lookup. + ID SignatureID + // Params and Results are the types of the parameters and results of the function. + Params, Results []Type + + // used is true if this is used by the currently-compiled function. + // Debugging only. + used bool +} + +// String implements fmt.Stringer. +func (s *Signature) String() string { + str := strings.Builder{} + str.WriteString(s.ID.String()) + str.WriteString(": ") + if len(s.Params) > 0 { + for _, typ := range s.Params { + str.WriteString(typ.String()) + } + } else { + str.WriteByte('v') + } + str.WriteByte('_') + if len(s.Results) > 0 { + for _, typ := range s.Results { + str.WriteString(typ.String()) + } + } else { + str.WriteByte('v') + } + return str.String() +} + +// SignatureID is an unique identifier used to lookup. +type SignatureID int + +// String implements fmt.Stringer. +func (s SignatureID) String() string { + return fmt.Sprintf("sig%d", s) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/ssa.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/ssa.go new file mode 100644 index 00000000000..b477e58bdbd --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/ssa.go @@ -0,0 +1,14 @@ +// Package ssa is used to construct SSA function. By nature this is free of Wasm specific thing +// and ISA. +// +// We use the "block argument" variant of SSA: https://en.wikipedia.org/wiki/Static_single-assignment_form#Block_arguments +// which is equivalent to the traditional PHI function based one, but more convenient during optimizations. +// However, in this package's source code comment, we might use PHI whenever it seems necessary in order to be aligned with +// existing literatures, e.g. SSA level optimization algorithms are often described using PHI nodes. +// +// The rationale doc for the choice of "block argument" by MLIR of LLVM is worth a read: +// https://mlir.llvm.org/docs/Rationale/Rationale/#block-arguments-vs-phi-nodes +// +// The algorithm to resolve variable definitions used here is based on the paper +// "Simple and Efficient Construction of Static Single Assignment Form": https://link.springer.com/content/pdf/10.1007/978-3-642-37051-9_6.pdf. +package ssa diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/type.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/type.go new file mode 100644 index 00000000000..73daf4269c6 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/type.go @@ -0,0 +1,115 @@ +package ssa + +type Type byte + +const ( + typeInvalid Type = iota + + // TODO: add 8, 16 bit types when it's needed for optimizations. + + // TypeI32 represents an integer type with 32 bits. + TypeI32 + + // TypeI64 represents an integer type with 64 bits. + TypeI64 + + // TypeF32 represents 32-bit floats in the IEEE 754. + TypeF32 + + // TypeF64 represents 64-bit floats in the IEEE 754. + TypeF64 + + // TypeV128 represents 128-bit SIMD vectors. + TypeV128 + + // -- Do not add new types after this line. ---- + typeEnd +) + +// String implements fmt.Stringer. +func (t Type) String() (ret string) { + switch t { + case typeInvalid: + return "invalid" + case TypeI32: + return "i32" + case TypeI64: + return "i64" + case TypeF32: + return "f32" + case TypeF64: + return "f64" + case TypeV128: + return "v128" + default: + panic(int(t)) + } +} + +// IsInt returns true if the type is an integer type. +func (t Type) IsInt() bool { + return t == TypeI32 || t == TypeI64 +} + +// IsFloat returns true if the type is a floating point type. +func (t Type) IsFloat() bool { + return t == TypeF32 || t == TypeF64 +} + +// Bits returns the number of bits required to represent the type. +func (t Type) Bits() byte { + switch t { + case TypeI32, TypeF32: + return 32 + case TypeI64, TypeF64: + return 64 + case TypeV128: + return 128 + default: + panic(int(t)) + } +} + +// Size returns the number of bytes required to represent the type. +func (t Type) Size() byte { + return t.Bits() / 8 +} + +func (t Type) invalid() bool { + return t == typeInvalid +} + +// VecLane represents a lane in a SIMD vector. +type VecLane byte + +const ( + VecLaneInvalid VecLane = 1 + iota + VecLaneI8x16 + VecLaneI16x8 + VecLaneI32x4 + VecLaneI64x2 + VecLaneF32x4 + VecLaneF64x2 +) + +// String implements fmt.Stringer. +func (vl VecLane) String() (ret string) { + switch vl { + case VecLaneInvalid: + return "invalid" + case VecLaneI8x16: + return "i8x16" + case VecLaneI16x8: + return "i16x8" + case VecLaneI32x4: + return "i32x4" + case VecLaneI64x2: + return "i64x2" + case VecLaneF32x4: + return "f32x4" + case VecLaneF64x2: + return "f64x2" + default: + panic(int(vl)) + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/vs.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/vs.go new file mode 100644 index 00000000000..d906e7e3546 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/vs.go @@ -0,0 +1,114 @@ +package ssa + +import ( + "fmt" + "math" + + "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" +) + +// Variable is a unique identifier for a source program's variable and will correspond to +// multiple ssa Value(s). +// +// For example, `Local 1` is a Variable in WebAssembly, and Value(s) will be created for it +// whenever it executes `local.set 1`. +// +// Variable is useful to track the SSA Values of a variable in the source program, and +// can be used to find the corresponding latest SSA Value via Builder.FindValue. +// +// Higher 4-bit is used to store Type for this variable. +type Variable uint32 + +// String implements fmt.Stringer. +func (v Variable) String() string { + return fmt.Sprintf("var%d", v&0x0fffffff) +} + +func (v Variable) setType(typ Type) Variable { + if v >= 1<<28 { + panic(fmt.Sprintf("Too large variable: %d", v)) + } + return Variable(typ)<<28 | v +} + +func (v Variable) getType() Type { + return Type(v >> 28) +} + +// Value represents an SSA value with a type information. The relationship with Variable is 1: N (including 0), +// that means there might be multiple Variable(s) for a Value. +// +// 32 to 59-bit is used to store the unique identifier of the Instruction that generates this value if any. +// 60 to 63-bit is used to store Type for this value. +type Value uint64 + +// ValueID is the lower 32bit of Value, which is the pure identifier of Value without type info. +type ValueID uint32 + +const ( + valueIDInvalid ValueID = math.MaxUint32 + ValueInvalid = Value(valueIDInvalid) +) + +// Format creates a debug string for this Value using the data stored in Builder. +func (v Value) Format(b Builder) string { + if annotation, ok := b.(*builder).valueAnnotations[v.ID()]; ok { + return annotation + } + return fmt.Sprintf("v%d", v.ID()) +} + +func (v Value) formatWithType(b Builder) (ret string) { + if annotation, ok := b.(*builder).valueAnnotations[v.ID()]; ok { + ret = annotation + ":" + v.Type().String() + } else { + ret = fmt.Sprintf("v%d:%s", v.ID(), v.Type()) + } + + if wazevoapi.SSALoggingEnabled { // This is useful to check live value analysis bugs. + if bd := b.(*builder); bd.donePostBlockLayoutPasses { + id := v.ID() + ret += fmt.Sprintf("(ref=%d)", bd.valuesInfo[id].RefCount) + } + } + return ret +} + +// Valid returns true if this value is valid. +func (v Value) Valid() bool { + return v.ID() != valueIDInvalid +} + +// Type returns the Type of this value. +func (v Value) Type() Type { + return Type(v >> 60) +} + +// ID returns the valueID of this value. +func (v Value) ID() ValueID { + return ValueID(v) +} + +// setType sets a type to this Value and returns the updated Value. +func (v Value) setType(typ Type) Value { + return v | Value(typ)<<60 +} + +// setInstructionID sets an Instruction.id to this Value and returns the updated Value. +func (v Value) setInstructionID(id int) Value { + if id < 0 || uint(id) >= 1<<28 { + panic(fmt.Sprintf("Too large instruction ID: %d", id)) + } + return v | Value(id)<<32 +} + +// instructionID() returns the Instruction.id of this Value. +func (v Value) instructionID() int { + return int(v>>32) & 0x0fffffff +} + +// Values is a slice of Value. Use this instead of []Value to reuse the underlying memory. +type Values = wazevoapi.VarLength[Value] + +// ValuesNil is a nil Values. +var ValuesNil = wazevoapi.NewNilVarLength[Value]() diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/debug_options.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/debug_options.go new file mode 100644 index 00000000000..783ab122a65 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/debug_options.go @@ -0,0 +1,189 @@ +package wazevoapi + +import ( + "context" + "encoding/hex" + "fmt" + "math/rand" + "os" + "sync" + "time" +) + +// These consts are used various places in the wazevo implementations. +// Instead of defining them in each file, we define them here so that we can quickly iterate on +// debugging without spending "where do we have debug logging?" time. + +// ----- Debug logging ----- +// These consts must be disabled by default. Enable them only when debugging. + +const ( + FrontEndLoggingEnabled = false + SSALoggingEnabled = false + RegAllocLoggingEnabled = false +) + +// ----- Output prints ----- +// These consts must be disabled by default. Enable them only when debugging. + +const ( + PrintSSA = false + PrintOptimizedSSA = false + PrintSSAToBackendIRLowering = false + PrintRegisterAllocated = false + PrintFinalizedMachineCode = false + PrintMachineCodeHexPerFunction = printMachineCodeHexPerFunctionUnmodified || PrintMachineCodeHexPerFunctionDisassemblable //nolint + printMachineCodeHexPerFunctionUnmodified = false + // PrintMachineCodeHexPerFunctionDisassemblable prints the machine code while modifying the actual result + // to make it disassemblable. This is useful when debugging the final machine code. See the places where this is used for detail. + // When this is enabled, functions must not be called. + PrintMachineCodeHexPerFunctionDisassemblable = false +) + +// printTarget is the function index to print the machine code. This is used for debugging to print the machine code +// of a specific function. +const printTarget = -1 + +// PrintEnabledIndex returns true if the current function index is the print target. +func PrintEnabledIndex(ctx context.Context) bool { + if printTarget == -1 { + return true + } + return GetCurrentFunctionIndex(ctx) == printTarget +} + +// ----- Validations ----- +const ( + // SSAValidationEnabled enables the SSA validation. This is disabled by default since the operation is expensive. + SSAValidationEnabled = false +) + +// ----- Stack Guard Check ----- +const ( + // StackGuardCheckEnabled enables the stack guard check to ensure that our stack bounds check works correctly. + StackGuardCheckEnabled = false + StackGuardCheckGuardPageSize = 8096 +) + +// CheckStackGuardPage checks the given stack guard page is not corrupted. +func CheckStackGuardPage(s []byte) { + for i := 0; i < StackGuardCheckGuardPageSize; i++ { + if s[i] != 0 { + panic( + fmt.Sprintf("BUG: stack guard page is corrupted:\n\tguard_page=%s\n\tstack=%s", + hex.EncodeToString(s[:StackGuardCheckGuardPageSize]), + hex.EncodeToString(s[StackGuardCheckGuardPageSize:]), + )) + } + } +} + +// ----- Deterministic compilation verifier ----- + +const ( + // DeterministicCompilationVerifierEnabled enables the deterministic compilation verifier. This is disabled by default + // since the operation is expensive. But when in doubt, enable this to make sure the compilation is deterministic. + DeterministicCompilationVerifierEnabled = false + DeterministicCompilationVerifyingIter = 5 +) + +type ( + verifierState struct { + initialCompilationDone bool + maybeRandomizedIndexes []int + r *rand.Rand + values sync.Map + } + verifierStateContextKey struct{} + currentFunctionNameKey struct{} + currentFunctionIndexKey struct{} +) + +// NewDeterministicCompilationVerifierContext creates a new context with the deterministic compilation verifier used per wasm.Module. +func NewDeterministicCompilationVerifierContext(ctx context.Context, localFunctions int) context.Context { + maybeRandomizedIndexes := make([]int, localFunctions) + for i := range maybeRandomizedIndexes { + maybeRandomizedIndexes[i] = i + } + r := rand.New(rand.NewSource(time.Now().UnixNano())) + return context.WithValue(ctx, verifierStateContextKey{}, &verifierState{ + r: r, maybeRandomizedIndexes: maybeRandomizedIndexes, values: sync.Map{}, + }) +} + +// DeterministicCompilationVerifierRandomizeIndexes randomizes the indexes for the deterministic compilation verifier. +// Returns a slice that maps an index to the randomized index. +func DeterministicCompilationVerifierRandomizeIndexes(ctx context.Context) []int { + state := ctx.Value(verifierStateContextKey{}).(*verifierState) + if !state.initialCompilationDone { + // If this is the first attempt, we use the index as-is order. + state.initialCompilationDone = true + return state.maybeRandomizedIndexes + } + r := state.r + r.Shuffle(len(state.maybeRandomizedIndexes), func(i, j int) { + state.maybeRandomizedIndexes[i], state.maybeRandomizedIndexes[j] = state.maybeRandomizedIndexes[j], state.maybeRandomizedIndexes[i] + }) + return state.maybeRandomizedIndexes +} + +// VerifyOrSetDeterministicCompilationContextValue verifies that the `newValue` is the same as the previous value for the given `scope` +// and the current function name. If the previous value doesn't exist, it sets the value to the given `newValue`. +// +// If the verification fails, this prints the diff and exits the process. +func VerifyOrSetDeterministicCompilationContextValue(ctx context.Context, scope string, newValue string) { + fn := ctx.Value(currentFunctionNameKey{}).(string) + key := fn + ": " + scope + verifierCtx := ctx.Value(verifierStateContextKey{}).(*verifierState) + oldValue, loaded := verifierCtx.values.LoadOrStore(key, newValue) + if !loaded { + return + } + if oldValue != newValue { + fmt.Printf( + `BUG: Deterministic compilation failed for function%s at scope="%s". + +This is mostly due to (but might not be limited to): + * Resetting ssa.Builder, backend.Compiler or frontend.Compiler, etc doens't work as expected, and the compilation has been affected by the previous iterations. + * Using a map with non-deterministic iteration order. + +---------- [old] ---------- +%s + +---------- [new] ---------- +%s +`, + fn, scope, oldValue, newValue, + ) + os.Exit(1) + } +} + +// nolint +const NeedFunctionNameInContext = PrintSSA || + PrintOptimizedSSA || + PrintSSAToBackendIRLowering || + PrintRegisterAllocated || + PrintFinalizedMachineCode || + PrintMachineCodeHexPerFunction || + DeterministicCompilationVerifierEnabled || + PerfMapEnabled + +// SetCurrentFunctionName sets the current function name to the given `functionName`. +func SetCurrentFunctionName(ctx context.Context, index int, functionName string) context.Context { + ctx = context.WithValue(ctx, currentFunctionNameKey{}, functionName) + ctx = context.WithValue(ctx, currentFunctionIndexKey{}, index) + return ctx +} + +// GetCurrentFunctionName returns the current function name. +func GetCurrentFunctionName(ctx context.Context) string { + ret, _ := ctx.Value(currentFunctionNameKey{}).(string) + return ret +} + +// GetCurrentFunctionIndex returns the current function index. +func GetCurrentFunctionIndex(ctx context.Context) int { + ret, _ := ctx.Value(currentFunctionIndexKey{}).(int) + return ret +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/exitcode.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/exitcode.go new file mode 100644 index 00000000000..5ad59498255 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/exitcode.go @@ -0,0 +1,109 @@ +package wazevoapi + +// ExitCode is an exit code of an execution of a function. +type ExitCode uint32 + +const ( + ExitCodeOK ExitCode = iota + ExitCodeGrowStack + ExitCodeGrowMemory + ExitCodeUnreachable + ExitCodeMemoryOutOfBounds + // ExitCodeCallGoModuleFunction is an exit code for a call to an api.GoModuleFunction. + ExitCodeCallGoModuleFunction + // ExitCodeCallGoFunction is an exit code for a call to an api.GoFunction. + ExitCodeCallGoFunction + ExitCodeTableOutOfBounds + ExitCodeIndirectCallNullPointer + ExitCodeIndirectCallTypeMismatch + ExitCodeIntegerDivisionByZero + ExitCodeIntegerOverflow + ExitCodeInvalidConversionToInteger + ExitCodeCheckModuleExitCode + ExitCodeCallListenerBefore + ExitCodeCallListenerAfter + ExitCodeCallGoModuleFunctionWithListener + ExitCodeCallGoFunctionWithListener + ExitCodeTableGrow + ExitCodeRefFunc + ExitCodeMemoryWait32 + ExitCodeMemoryWait64 + ExitCodeMemoryNotify + ExitCodeUnalignedAtomic + exitCodeMax +) + +const ExitCodeMask = 0xff + +// String implements fmt.Stringer. +func (e ExitCode) String() string { + switch e { + case ExitCodeOK: + return "ok" + case ExitCodeGrowStack: + return "grow_stack" + case ExitCodeCallGoModuleFunction: + return "call_go_module_function" + case ExitCodeCallGoFunction: + return "call_go_function" + case ExitCodeUnreachable: + return "unreachable" + case ExitCodeMemoryOutOfBounds: + return "memory_out_of_bounds" + case ExitCodeUnalignedAtomic: + return "unaligned_atomic" + case ExitCodeTableOutOfBounds: + return "table_out_of_bounds" + case ExitCodeIndirectCallNullPointer: + return "indirect_call_null_pointer" + case ExitCodeIndirectCallTypeMismatch: + return "indirect_call_type_mismatch" + case ExitCodeIntegerDivisionByZero: + return "integer_division_by_zero" + case ExitCodeIntegerOverflow: + return "integer_overflow" + case ExitCodeInvalidConversionToInteger: + return "invalid_conversion_to_integer" + case ExitCodeCheckModuleExitCode: + return "check_module_exit_code" + case ExitCodeCallListenerBefore: + return "call_listener_before" + case ExitCodeCallListenerAfter: + return "call_listener_after" + case ExitCodeCallGoModuleFunctionWithListener: + return "call_go_module_function_with_listener" + case ExitCodeCallGoFunctionWithListener: + return "call_go_function_with_listener" + case ExitCodeGrowMemory: + return "grow_memory" + case ExitCodeTableGrow: + return "table_grow" + case ExitCodeRefFunc: + return "ref_func" + case ExitCodeMemoryWait32: + return "memory_wait32" + case ExitCodeMemoryWait64: + return "memory_wait64" + case ExitCodeMemoryNotify: + return "memory_notify" + } + panic("TODO") +} + +func ExitCodeCallGoModuleFunctionWithIndex(index int, withListener bool) ExitCode { + if withListener { + return ExitCodeCallGoModuleFunctionWithListener | ExitCode(index<<8) + } + return ExitCodeCallGoModuleFunction | ExitCode(index<<8) +} + +func ExitCodeCallGoFunctionWithIndex(index int, withListener bool) ExitCode { + if withListener { + return ExitCodeCallGoFunctionWithListener | ExitCode(index<<8) + } + return ExitCodeCallGoFunction | ExitCode(index<<8) +} + +func GoFunctionIndexFromExitCode(exitCode ExitCode) int { + return int(exitCode >> 8) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/offsetdata.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/offsetdata.go new file mode 100644 index 00000000000..fe6161b049a --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/offsetdata.go @@ -0,0 +1,216 @@ +package wazevoapi + +import ( + "github.com/tetratelabs/wazero/internal/wasm" +) + +const ( + // FunctionInstanceSize is the size of wazevo.functionInstance. + FunctionInstanceSize = 24 + // FunctionInstanceExecutableOffset is an offset of `executable` field in wazevo.functionInstance + FunctionInstanceExecutableOffset = 0 + // FunctionInstanceModuleContextOpaquePtrOffset is an offset of `moduleContextOpaquePtr` field in wazevo.functionInstance + FunctionInstanceModuleContextOpaquePtrOffset = 8 + // FunctionInstanceTypeIDOffset is an offset of `typeID` field in wazevo.functionInstance + FunctionInstanceTypeIDOffset = 16 +) + +const ( + // ExecutionContextOffsetExitCodeOffset is an offset of `exitCode` field in wazevo.executionContext + ExecutionContextOffsetExitCodeOffset Offset = 0 + // ExecutionContextOffsetCallerModuleContextPtr is an offset of `callerModuleContextPtr` field in wazevo.executionContext + ExecutionContextOffsetCallerModuleContextPtr Offset = 8 + // ExecutionContextOffsetOriginalFramePointer is an offset of `originalFramePointer` field in wazevo.executionContext + ExecutionContextOffsetOriginalFramePointer Offset = 16 + // ExecutionContextOffsetOriginalStackPointer is an offset of `originalStackPointer` field in wazevo.executionContext + ExecutionContextOffsetOriginalStackPointer Offset = 24 + // ExecutionContextOffsetGoReturnAddress is an offset of `goReturnAddress` field in wazevo.executionContext + ExecutionContextOffsetGoReturnAddress Offset = 32 + // ExecutionContextOffsetStackBottomPtr is an offset of `stackBottomPtr` field in wazevo.executionContext + ExecutionContextOffsetStackBottomPtr Offset = 40 + // ExecutionContextOffsetGoCallReturnAddress is an offset of `goCallReturnAddress` field in wazevo.executionContext + ExecutionContextOffsetGoCallReturnAddress Offset = 48 + // ExecutionContextOffsetStackPointerBeforeGoCall is an offset of `StackPointerBeforeGoCall` field in wazevo.executionContext + ExecutionContextOffsetStackPointerBeforeGoCall Offset = 56 + // ExecutionContextOffsetStackGrowRequiredSize is an offset of `stackGrowRequiredSize` field in wazevo.executionContext + ExecutionContextOffsetStackGrowRequiredSize Offset = 64 + // ExecutionContextOffsetMemoryGrowTrampolineAddress is an offset of `memoryGrowTrampolineAddress` field in wazevo.executionContext + ExecutionContextOffsetMemoryGrowTrampolineAddress Offset = 72 + // ExecutionContextOffsetStackGrowCallTrampolineAddress is an offset of `stackGrowCallTrampolineAddress` field in wazevo.executionContext. + ExecutionContextOffsetStackGrowCallTrampolineAddress Offset = 80 + // ExecutionContextOffsetCheckModuleExitCodeTrampolineAddress is an offset of `checkModuleExitCodeTrampolineAddress` field in wazevo.executionContext. + ExecutionContextOffsetCheckModuleExitCodeTrampolineAddress Offset = 88 + // ExecutionContextOffsetSavedRegistersBegin is an offset of the first element of `savedRegisters` field in wazevo.executionContext + ExecutionContextOffsetSavedRegistersBegin Offset = 96 + // ExecutionContextOffsetGoFunctionCallCalleeModuleContextOpaque is an offset of `goFunctionCallCalleeModuleContextOpaque` field in wazevo.executionContext + ExecutionContextOffsetGoFunctionCallCalleeModuleContextOpaque Offset = 1120 + // ExecutionContextOffsetTableGrowTrampolineAddress is an offset of `tableGrowTrampolineAddress` field in wazevo.executionContext + ExecutionContextOffsetTableGrowTrampolineAddress Offset = 1128 + // ExecutionContextOffsetRefFuncTrampolineAddress is an offset of `refFuncTrampolineAddress` field in wazevo.executionContext + ExecutionContextOffsetRefFuncTrampolineAddress Offset = 1136 + ExecutionContextOffsetMemmoveAddress Offset = 1144 + ExecutionContextOffsetFramePointerBeforeGoCall Offset = 1152 + ExecutionContextOffsetMemoryWait32TrampolineAddress Offset = 1160 + ExecutionContextOffsetMemoryWait64TrampolineAddress Offset = 1168 + ExecutionContextOffsetMemoryNotifyTrampolineAddress Offset = 1176 +) + +// ModuleContextOffsetData allows the compilers to get the information about offsets to the fields of wazevo.moduleContextOpaque, +// This is unique per module. +type ModuleContextOffsetData struct { + TotalSize int + ModuleInstanceOffset, + LocalMemoryBegin, + ImportedMemoryBegin, + ImportedFunctionsBegin, + GlobalsBegin, + TypeIDs1stElement, + TablesBegin, + BeforeListenerTrampolines1stElement, + AfterListenerTrampolines1stElement, + DataInstances1stElement, + ElementInstances1stElement Offset +} + +// ImportedFunctionOffset returns an offset of the i-th imported function. +// Each item is stored as wazevo.functionInstance whose size matches FunctionInstanceSize. +func (m *ModuleContextOffsetData) ImportedFunctionOffset(i wasm.Index) ( + executableOffset, moduleCtxOffset, typeIDOffset Offset, +) { + base := m.ImportedFunctionsBegin + Offset(i)*FunctionInstanceSize + return base, base + 8, base + 16 +} + +// GlobalInstanceOffset returns an offset of the i-th global instance. +func (m *ModuleContextOffsetData) GlobalInstanceOffset(i wasm.Index) Offset { + return m.GlobalsBegin + Offset(i)*16 +} + +// Offset represents an offset of a field of a struct. +type Offset int32 + +// U32 encodes an Offset as uint32 for convenience. +func (o Offset) U32() uint32 { + return uint32(o) +} + +// I64 encodes an Offset as int64 for convenience. +func (o Offset) I64() int64 { + return int64(o) +} + +// U64 encodes an Offset as int64 for convenience. +func (o Offset) U64() uint64 { + return uint64(o) +} + +// LocalMemoryBase returns an offset of the first byte of the local memory. +func (m *ModuleContextOffsetData) LocalMemoryBase() Offset { + return m.LocalMemoryBegin +} + +// LocalMemoryLen returns an offset of the length of the local memory buffer. +func (m *ModuleContextOffsetData) LocalMemoryLen() Offset { + if l := m.LocalMemoryBegin; l >= 0 { + return l + 8 + } + return -1 +} + +// TableOffset returns an offset of the i-th table instance. +func (m *ModuleContextOffsetData) TableOffset(tableIndex int) Offset { + return m.TablesBegin + Offset(tableIndex)*8 +} + +// NewModuleContextOffsetData creates a ModuleContextOffsetData determining the structure of moduleContextOpaque for the given Module. +// The structure is described in the comment of wazevo.moduleContextOpaque. +func NewModuleContextOffsetData(m *wasm.Module, withListener bool) ModuleContextOffsetData { + ret := ModuleContextOffsetData{} + var offset Offset + + ret.ModuleInstanceOffset = 0 + offset += 8 + + if m.MemorySection != nil { + ret.LocalMemoryBegin = offset + // buffer base + memory size. + const localMemorySizeInOpaqueModuleContext = 16 + offset += localMemorySizeInOpaqueModuleContext + } else { + // Indicates that there's no local memory + ret.LocalMemoryBegin = -1 + } + + if m.ImportMemoryCount > 0 { + offset = align8(offset) + // *wasm.MemoryInstance + imported memory's owner (moduleContextOpaque) + const importedMemorySizeInOpaqueModuleContext = 16 + ret.ImportedMemoryBegin = offset + offset += importedMemorySizeInOpaqueModuleContext + } else { + // Indicates that there's no imported memory + ret.ImportedMemoryBegin = -1 + } + + if m.ImportFunctionCount > 0 { + offset = align8(offset) + ret.ImportedFunctionsBegin = offset + // Each function is stored wazevo.functionInstance. + size := int(m.ImportFunctionCount) * FunctionInstanceSize + offset += Offset(size) + } else { + ret.ImportedFunctionsBegin = -1 + } + + if globals := int(m.ImportGlobalCount) + len(m.GlobalSection); globals > 0 { + // Align to 16 bytes for globals, as f32/f64/v128 might be loaded via SIMD instructions. + offset = align16(offset) + ret.GlobalsBegin = offset + // Pointers to *wasm.GlobalInstance. + offset += Offset(globals) * 16 + } else { + ret.GlobalsBegin = -1 + } + + if tables := len(m.TableSection) + int(m.ImportTableCount); tables > 0 { + offset = align8(offset) + ret.TypeIDs1stElement = offset + offset += 8 // First element of TypeIDs. + + ret.TablesBegin = offset + // Pointers to *wasm.TableInstance. + offset += Offset(tables) * 8 + } else { + ret.TypeIDs1stElement = -1 + ret.TablesBegin = -1 + } + + if withListener { + offset = align8(offset) + ret.BeforeListenerTrampolines1stElement = offset + offset += 8 // First element of BeforeListenerTrampolines. + + ret.AfterListenerTrampolines1stElement = offset + offset += 8 // First element of AfterListenerTrampolines. + } else { + ret.BeforeListenerTrampolines1stElement = -1 + ret.AfterListenerTrampolines1stElement = -1 + } + + ret.DataInstances1stElement = offset + offset += 8 // First element of DataInstances. + + ret.ElementInstances1stElement = offset + offset += 8 // First element of ElementInstances. + + ret.TotalSize = int(align16(offset)) + return ret +} + +func align16(o Offset) Offset { + return (o + 15) &^ 15 +} + +func align8(o Offset) Offset { + return (o + 7) &^ 7 +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/perfmap.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/perfmap.go new file mode 100644 index 00000000000..642c7f75dfd --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/perfmap.go @@ -0,0 +1,96 @@ +package wazevoapi + +import ( + "fmt" + "os" + "strconv" + "sync" +) + +var PerfMap *Perfmap + +func init() { + if PerfMapEnabled { + pid := os.Getpid() + filename := "/tmp/perf-" + strconv.Itoa(pid) + ".map" + + fh, err := os.OpenFile(filename, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0o644) + if err != nil { + panic(err) + } + + PerfMap = &Perfmap{fh: fh} + } +} + +// Perfmap holds perfmap entries to be flushed into a perfmap file. +type Perfmap struct { + entries []entry + mux sync.Mutex + fh *os.File +} + +type entry struct { + index int + offset int64 + size uint64 + name string +} + +func (f *Perfmap) Lock() { + f.mux.Lock() +} + +func (f *Perfmap) Unlock() { + f.mux.Unlock() +} + +// AddModuleEntry adds a perfmap entry into the perfmap file. +// index is the index of the function in the module, offset is the offset of the function in the module, +// size is the size of the function, and name is the name of the function. +// +// Note that the entries are not flushed into the perfmap file until Flush is called, +// and the entries are module-scoped; Perfmap must be locked until Flush is called. +func (f *Perfmap) AddModuleEntry(index int, offset int64, size uint64, name string) { + e := entry{index: index, offset: offset, size: size, name: name} + if f.entries == nil { + f.entries = []entry{e} + return + } + f.entries = append(f.entries, e) +} + +// Flush writes the perfmap entries into the perfmap file where the entries are adjusted by the given `addr` and `functionOffsets`. +func (f *Perfmap) Flush(addr uintptr, functionOffsets []int) { + defer func() { + _ = f.fh.Sync() + }() + + for _, e := range f.entries { + if _, err := f.fh.WriteString(fmt.Sprintf("%x %s %s\n", + uintptr(e.offset)+addr+uintptr(functionOffsets[e.index]), + strconv.FormatUint(e.size, 16), + e.name, + )); err != nil { + panic(err) + } + } + f.entries = f.entries[:0] +} + +// Clear clears the perfmap entries not yet flushed. +func (f *Perfmap) Clear() { + f.entries = f.entries[:0] +} + +// AddEntry writes a perfmap entry directly into the perfmap file, not using the entries. +func (f *Perfmap) AddEntry(addr uintptr, size uint64, name string) { + _, err := f.fh.WriteString(fmt.Sprintf("%x %s %s\n", + addr, + strconv.FormatUint(size, 16), + name, + )) + if err != nil { + panic(err) + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/perfmap_disabled.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/perfmap_disabled.go new file mode 100644 index 00000000000..bcc4e545c2d --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/perfmap_disabled.go @@ -0,0 +1,5 @@ +//go:build !perfmap + +package wazevoapi + +const PerfMapEnabled = false diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/perfmap_enabled.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/perfmap_enabled.go new file mode 100644 index 00000000000..2a39879ecc3 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/perfmap_enabled.go @@ -0,0 +1,5 @@ +//go:build perfmap + +package wazevoapi + +const PerfMapEnabled = true diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/pool.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/pool.go new file mode 100644 index 00000000000..d67a3262d03 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/pool.go @@ -0,0 +1,215 @@ +package wazevoapi + +const poolPageSize = 128 + +// Pool is a pool of T that can be allocated and reset. +// This is useful to avoid unnecessary allocations. +type Pool[T any] struct { + pages []*[poolPageSize]T + resetFn func(*T) + allocated, index int +} + +// NewPool returns a new Pool. +// resetFn is called when a new T is allocated in Pool.Allocate. +func NewPool[T any](resetFn func(*T)) Pool[T] { + var ret Pool[T] + ret.resetFn = resetFn + ret.Reset() + return ret +} + +// Allocated returns the number of allocated T currently in the pool. +func (p *Pool[T]) Allocated() int { + return p.allocated +} + +// Allocate allocates a new T from the pool. +func (p *Pool[T]) Allocate() *T { + if p.index == poolPageSize { + if len(p.pages) == cap(p.pages) { + p.pages = append(p.pages, new([poolPageSize]T)) + } else { + i := len(p.pages) + p.pages = p.pages[:i+1] + if p.pages[i] == nil { + p.pages[i] = new([poolPageSize]T) + } + } + p.index = 0 + } + ret := &p.pages[len(p.pages)-1][p.index] + if p.resetFn != nil { + p.resetFn(ret) + } + p.index++ + p.allocated++ + return ret +} + +// View returns the pointer to i-th item from the pool. +func (p *Pool[T]) View(i int) *T { + page, index := i/poolPageSize, i%poolPageSize + return &p.pages[page][index] +} + +// Reset resets the pool. +func (p *Pool[T]) Reset() { + p.pages = p.pages[:0] + p.index = poolPageSize + p.allocated = 0 +} + +// IDedPool is a pool of T that can be allocated and reset, with a way to get T by an ID. +type IDedPool[T any] struct { + pool Pool[T] + idToItems []*T + maxIDEncountered int +} + +// NewIDedPool returns a new IDedPool. +func NewIDedPool[T any](resetFn func(*T)) IDedPool[T] { + return IDedPool[T]{pool: NewPool(resetFn), maxIDEncountered: -1} +} + +// GetOrAllocate returns the T with the given id. +func (p *IDedPool[T]) GetOrAllocate(id int) *T { + if p.maxIDEncountered < id { + p.maxIDEncountered = id + } + if id >= len(p.idToItems) { + p.idToItems = append(p.idToItems, make([]*T, id-len(p.idToItems)+1)...) + } + if p.idToItems[id] == nil { + p.idToItems[id] = p.pool.Allocate() + } + return p.idToItems[id] +} + +// Get returns the T with the given id, or nil if it's not allocated. +func (p *IDedPool[T]) Get(id int) *T { + if id >= len(p.idToItems) { + return nil + } + return p.idToItems[id] +} + +// Reset resets the pool. +func (p *IDedPool[T]) Reset() { + p.pool.Reset() + for i := 0; i <= p.maxIDEncountered; i++ { + p.idToItems[i] = nil + } + p.maxIDEncountered = -1 +} + +// MaxIDEncountered returns the maximum id encountered so far. +func (p *IDedPool[T]) MaxIDEncountered() int { + return p.maxIDEncountered +} + +// arraySize is the size of the array used in VarLengthPool's arrayPool. +// This is chosen to be 8, which is empirically a good number among 8, 12, 16 and 20. +const arraySize = 8 + +// VarLengthPool is a pool of VarLength[T] that can be allocated and reset. +type ( + VarLengthPool[T any] struct { + arrayPool Pool[varLengthPoolArray[T]] + slicePool Pool[[]T] + } + // varLengthPoolArray wraps an array and keeps track of the next index to be used to avoid the heap allocation. + varLengthPoolArray[T any] struct { + arr [arraySize]T + next int + } +) + +// VarLength is a variable length array that can be reused via a pool. +type VarLength[T any] struct { + arr *varLengthPoolArray[T] + slc *[]T +} + +// NewVarLengthPool returns a new VarLengthPool. +func NewVarLengthPool[T any]() VarLengthPool[T] { + return VarLengthPool[T]{ + arrayPool: NewPool(func(v *varLengthPoolArray[T]) { + v.next = 0 + }), + slicePool: NewPool(func(i *[]T) { + *i = (*i)[:0] + }), + } +} + +// NewNilVarLength returns a new VarLength[T] with a nil backing. +func NewNilVarLength[T any]() VarLength[T] { + return VarLength[T]{} +} + +// Allocate allocates a new VarLength[T] from the pool. +func (p *VarLengthPool[T]) Allocate(knownMin int) VarLength[T] { + if knownMin <= arraySize { + arr := p.arrayPool.Allocate() + return VarLength[T]{arr: arr} + } + slc := p.slicePool.Allocate() + if cap(*slc) < knownMin { + *slc = make([]T, 0, knownMin) + } + return VarLength[T]{slc: slc} +} + +// Reset resets the pool. +func (p *VarLengthPool[T]) Reset() { + p.arrayPool.Reset() + p.slicePool.Reset() +} + +// Append appends items to the backing slice just like the `append` builtin function in Go. +func (i VarLength[T]) Append(p *VarLengthPool[T], items ...T) VarLength[T] { + slc := i.slc + if slc != nil { + *slc = append(*slc, items...) + return i + } + + arr := i.arr + if arr == nil { + arr = p.arrayPool.Allocate() + i.arr = arr + } + + if arr.next+len(items) <= arraySize { + arr.next += copy(arr.arr[arr.next:], items) + } else { + slc = p.slicePool.Allocate() + // Copy the array to the slice. + *slc = append(*slc, arr.arr[:arr.next]...) + *slc = append(*slc, items...) + i.slc = slc + } + return i +} + +// View returns the backing slice. +func (i VarLength[T]) View() []T { + if slc := i.slc; slc != nil { + return *i.slc + } + if arr := i.arr; arr != nil { + return arr.arr[:arr.next] + } + return nil +} + +// Cut cuts the backing slice to the given length. +// Precondition: n <= len(i.backing). +func (i VarLength[T]) Cut(n int) { + if slc := i.slc; slc != nil { + *slc = (*slc)[:n] + } else if arr := i.arr; arr != nil { + arr.next = n + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/ptr.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/ptr.go new file mode 100644 index 00000000000..c17d665b6fd --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/ptr.go @@ -0,0 +1,15 @@ +package wazevoapi + +import "unsafe" + +// PtrFromUintptr resurrects the original *T from the given uintptr. +// The caller of this function MUST be sure that ptr is valid. +func PtrFromUintptr[T any](ptr uintptr) *T { + // Wraps ptrs as the double pointer in order to avoid the unsafe access as detected by race detector. + // + // For example, if we have (*function)(unsafe.Pointer(ptr)) instead, then the race detector's "checkptr" + // subroutine warns as "checkptr: pointer arithmetic result points to invalid allocation" + // https://github.com/golang/go/blob/go1.24.0/src/runtime/checkptr.go#L69 + var wrapped *uintptr = &ptr + return *(**T)(unsafe.Pointer(wrapped)) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/queue.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/queue.go new file mode 100644 index 00000000000..e3118fa69e9 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/queue.go @@ -0,0 +1,26 @@ +package wazevoapi + +// Queue is the resettable queue where the underlying slice is reused. +type Queue[T any] struct { + index int + Data []T +} + +func (q *Queue[T]) Enqueue(v T) { + q.Data = append(q.Data, v) +} + +func (q *Queue[T]) Dequeue() (ret T) { + ret = q.Data[q.index] + q.index++ + return +} + +func (q *Queue[T]) Empty() bool { + return q.index >= len(q.Data) +} + +func (q *Queue[T]) Reset() { + q.index = 0 + q.Data = q.Data[:0] +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/resetmap.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/resetmap.go new file mode 100644 index 00000000000..3fc7aa143db --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/resetmap.go @@ -0,0 +1,11 @@ +package wazevoapi + +// ResetMap resets the map to an empty state, or creates a new map if it is nil. +func ResetMap[K comparable, V any](m map[K]V) map[K]V { + if m == nil { + m = make(map[K]V) + } else { + clear(m) + } + return m +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/checkpoint.go b/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/checkpoint.go new file mode 100644 index 00000000000..fc62e83f3e0 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/checkpoint.go @@ -0,0 +1,10 @@ +package expctxkeys + +// EnableSnapshotterKey is a context key to indicate that snapshotting should be enabled. +// The context.Context passed to a exported function invocation should have this key set +// to a non-nil value, and host functions will be able to retrieve it using SnapshotterKey. +type EnableSnapshotterKey struct{} + +// SnapshotterKey is a context key to access a Snapshotter from a host function. +// It is only present if EnableSnapshotter was set in the function invocation context. +type SnapshotterKey struct{} diff --git a/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/close.go b/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/close.go new file mode 100644 index 00000000000..75e5134e5c4 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/close.go @@ -0,0 +1,5 @@ +package expctxkeys + +// CloseNotifierKey is a context.Context Value key. Its associated value should be a +// Notifier. +type CloseNotifierKey struct{} diff --git a/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/compilationworkers.go b/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/compilationworkers.go new file mode 100644 index 00000000000..a79c4889497 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/compilationworkers.go @@ -0,0 +1,6 @@ +package expctxkeys + +// CompilationWorkers is a context.Context Value key. +// Its associated value should be an int representing the number of workers +// we want to spawn to compile a given wasm input. +type CompilationWorkers struct{} diff --git a/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/expctxkeys.go b/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/expctxkeys.go new file mode 100644 index 00000000000..6800005b91a --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/expctxkeys.go @@ -0,0 +1,2 @@ +// Package expctxkeys provides keys for the context used to store the experimental APIs. +package expctxkeys diff --git a/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/importresolver.go b/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/importresolver.go new file mode 100644 index 00000000000..af52cc80eb5 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/importresolver.go @@ -0,0 +1,6 @@ +package expctxkeys + +// ImportResolverKey is a context.Context Value key. +// Its associated value should be an ImportResolver. +// See issue 2294. +type ImportResolverKey struct{} diff --git a/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/listener.go b/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/listener.go new file mode 100644 index 00000000000..9565db8e93f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/listener.go @@ -0,0 +1,7 @@ +package expctxkeys + +// FunctionListenerFactoryKey is a context.Context Value key. +// Its associated value should be a FunctionListenerFactory. +// +// See https://github.com/tetratelabs/wazero/issues/451 +type FunctionListenerFactoryKey struct{} diff --git a/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/memory.go b/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/memory.go new file mode 100644 index 00000000000..d41c019147f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/expctxkeys/memory.go @@ -0,0 +1,4 @@ +package expctxkeys + +// MemoryAllocatorKey is a context.Context key for the experimental memory allocator. +type MemoryAllocatorKey struct{} diff --git a/vendor/github.com/tetratelabs/wazero/internal/filecache/compilationcache.go b/vendor/github.com/tetratelabs/wazero/internal/filecache/compilationcache.go new file mode 100644 index 00000000000..b2dbd46507d --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/filecache/compilationcache.go @@ -0,0 +1,42 @@ +package filecache + +import ( + "crypto/sha256" + "io" +) + +// Cache allows the compiler engine to skip compilation of wasm to machine code +// where doing so is redundant for the same wasm binary and version of wazero. +// +// This augments the default in-memory cache of compiled functions, by +// decoupling it from a wazero.Runtime instance. Concretely, a runtime loses +// its cache once closed. This cache allows the runtime to rebuild its +// in-memory cache quicker, significantly reducing first-hit penalty on a hit. +// +// See New for the example implementation. +type Cache interface { + // Get is called when the runtime is trying to get the cached compiled functions. + // Implementations are supposed to return compiled function in io.Reader with ok=true + // if the key exists on the cache. In the case of not-found, this should return + // ok=false with err=nil. content.Close() is automatically called by + // the caller of this Get. + // + // Note: the returned content won't go through the validation pass of Wasm binary + // which is applied when the binary is compiled from scratch without cache hit. + Get(key Key) (content io.ReadCloser, ok bool, err error) + // + // Add is called when the runtime is trying to add the new cache entry. + // The given `content` must be un-modified, and returned as-is in Get method. + // + // Note: the `content` is ensured to be safe through the validation phase applied on the Wasm binary. + Add(key Key, content io.Reader) (err error) + // + // Delete is called when the cache on the `key` returned by Get is no longer usable, and + // must be purged. Specifically, this is called happens when the wazero's version has been changed. + // For example, that is when there's a difference between the version of compiling wazero and the + // version of the currently used wazero. + Delete(key Key) (err error) +} + +// Key represents the 256-bit unique identifier assigned to each cache entry. +type Key = [sha256.Size]byte diff --git a/vendor/github.com/tetratelabs/wazero/internal/filecache/file_cache.go b/vendor/github.com/tetratelabs/wazero/internal/filecache/file_cache.go new file mode 100644 index 00000000000..940a79a8d50 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/filecache/file_cache.go @@ -0,0 +1,76 @@ +package filecache + +import ( + "encoding/hex" + "errors" + "io" + "os" + "path" + "path/filepath" +) + +// New returns a new Cache implemented by fileCache. +func New(dir string) Cache { + return newFileCache(dir) +} + +func newFileCache(dir string) *fileCache { + return &fileCache{dirPath: dir} +} + +// fileCache persists compiled functions into dirPath. +// +// Note: this can be expanded to do binary signing/verification, set TTL on each entry, etc. +type fileCache struct { + dirPath string +} + +func (fc *fileCache) path(key Key) string { + return path.Join(fc.dirPath, hex.EncodeToString(key[:])) +} + +func (fc *fileCache) Get(key Key) (content io.ReadCloser, ok bool, err error) { + f, err := os.Open(fc.path(key)) + if errors.Is(err, os.ErrNotExist) { + return nil, false, nil + } else if err != nil { + return nil, false, err + } else { + return f, true, nil + } +} + +func (fc *fileCache) Add(key Key, content io.Reader) (err error) { + path := fc.path(key) + dirPath, fileName := filepath.Split(path) + + file, err := os.CreateTemp(dirPath, fileName+".*.tmp") + if err != nil { + return + } + defer func() { + file.Close() + if err != nil { + _ = os.Remove(file.Name()) + } + }() + if _, err = io.Copy(file, content); err != nil { + return + } + if err = file.Sync(); err != nil { + return + } + if err = file.Close(); err != nil { + return + } + err = os.Rename(file.Name(), path) + return +} + +func (fc *fileCache) Delete(key Key) (err error) { + err = os.Remove(fc.path(key)) + if errors.Is(err, os.ErrNotExist) { + err = nil + } + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/fsapi/file.go b/vendor/github.com/tetratelabs/wazero/internal/fsapi/file.go new file mode 100644 index 00000000000..0640b22712a --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/fsapi/file.go @@ -0,0 +1,69 @@ +package fsapi + +import experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + +// File includes methods not yet ready to document for end users, notably +// non-blocking functionality. +// +// Particularly, Poll is subject to debate. For example, whether a user should +// be able to choose how to implement timeout or not. Currently, this interface +// allows the user to choose to sleep or use native polling, and which choice +// they make impacts thread behavior as summarized here: +// https://github.com/tetratelabs/wazero/pull/1606#issuecomment-1665475516 +type File interface { + experimentalsys.File + + // IsNonblock returns true if the file was opened with O_NONBLOCK, or + // SetNonblock was successfully enabled on this file. + // + // # Notes + // + // - This might not match the underlying state of the file descriptor if + // the file was not opened via OpenFile. + IsNonblock() bool + + // SetNonblock toggles the non-blocking mode (O_NONBLOCK) of this file. + // + // # Errors + // + // A zero Errno is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - EBADF: the file or directory was closed. + // + // # Notes + // + // - This is like syscall.SetNonblock and `fcntl` with O_NONBLOCK in + // POSIX. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html + SetNonblock(enable bool) experimentalsys.Errno + + // Poll returns if the file has data ready to be read or written. + // + // # Parameters + // + // The `flag` parameter determines which event to await, such as POLLIN, + // POLLOUT, or a combination like `POLLIN|POLLOUT`. + // + // The `timeoutMillis` parameter is how long to block for an event, or + // interrupted, in milliseconds. There are two special values: + // - zero returns immediately + // - any negative value blocks any amount of time + // + // # Results + // + // `ready` means there was data ready to read or written. False can mean no + // event was ready or `errno` is not zero. + // + // A zero `errno` is success. The below are expected otherwise: + // - ENOSYS: the implementation does not support this function. + // - ENOTSUP: the implementation does not the flag combination. + // - EINTR: the call was interrupted prior to an event. + // + // # Notes + // + // - This is like `poll` in POSIX, for a single file. + // See https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html + // - No-op files, such as those which read from /dev/null, should return + // immediately true, as data will never become available. + // - See /RATIONALE.md for detailed notes including impact of blocking. + Poll(flag Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/fsapi/poll.go b/vendor/github.com/tetratelabs/wazero/internal/fsapi/poll.go new file mode 100644 index 00000000000..25f7c5711b1 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/fsapi/poll.go @@ -0,0 +1,20 @@ +package fsapi + +// Pflag are bit flags used for File.Poll. Values, including zero, should not +// be interpreted numerically. Instead, use by constants prefixed with 'POLL'. +// +// # Notes +// +// - This is like `pollfd.events` flags for `poll` in POSIX. See +// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/poll.h.html +type Pflag uint32 + +// Only define bitflags we support and are needed by `poll_oneoff` in wasip1 +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#eventrwflags +const ( + // POLLIN is a read event. + POLLIN Pflag = 1 << iota + + // POLLOUT is a write event. + POLLOUT +) diff --git a/vendor/github.com/tetratelabs/wazero/internal/fsapi/unimplemented.go b/vendor/github.com/tetratelabs/wazero/internal/fsapi/unimplemented.go new file mode 100644 index 00000000000..99d9c2db343 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/fsapi/unimplemented.go @@ -0,0 +1,27 @@ +package fsapi + +import experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + +func Adapt(f experimentalsys.File) File { + if f, ok := f.(File); ok { + return f + } + return unimplementedFile{f} +} + +type unimplementedFile struct{ experimentalsys.File } + +// IsNonblock implements File.IsNonblock +func (unimplementedFile) IsNonblock() bool { + return false +} + +// SetNonblock implements File.SetNonblock +func (unimplementedFile) SetNonblock(bool) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Poll implements File.Poll +func (unimplementedFile) Poll(Pflag, int32) (ready bool, errno experimentalsys.Errno) { + return false, experimentalsys.ENOSYS +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/ieee754/ieee754.go b/vendor/github.com/tetratelabs/wazero/internal/ieee754/ieee754.go new file mode 100644 index 00000000000..0c9298957f8 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/ieee754/ieee754.go @@ -0,0 +1,29 @@ +package ieee754 + +import ( + "encoding/binary" + "io" + "math" +) + +// DecodeFloat32 decodes a float32 in IEEE 754 binary representation. +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#floating-point%E2%91%A2 +func DecodeFloat32(buf []byte) (float32, error) { + if len(buf) < 4 { + return 0, io.ErrUnexpectedEOF + } + + raw := binary.LittleEndian.Uint32(buf[:4]) + return math.Float32frombits(raw), nil +} + +// DecodeFloat64 decodes a float64 in IEEE 754 binary representation. +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#floating-point%E2%91%A2 +func DecodeFloat64(buf []byte) (float64, error) { + if len(buf) < 8 { + return 0, io.ErrUnexpectedEOF + } + + raw := binary.LittleEndian.Uint64(buf) + return math.Float64frombits(raw), nil +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/internalapi/internal.go b/vendor/github.com/tetratelabs/wazero/internal/internalapi/internal.go new file mode 100644 index 00000000000..a4f354355fa --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/internalapi/internal.go @@ -0,0 +1,9 @@ +package internalapi + +type WazeroOnly interface { + wazeroOnly() +} + +type WazeroOnlyType struct{} + +func (WazeroOnlyType) wazeroOnly() {} diff --git a/vendor/github.com/tetratelabs/wazero/internal/leb128/leb128.go b/vendor/github.com/tetratelabs/wazero/internal/leb128/leb128.go new file mode 100644 index 00000000000..7fc0eb9c77b --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/leb128/leb128.go @@ -0,0 +1,285 @@ +package leb128 + +import ( + "errors" + "fmt" + "io" +) + +const ( + maxVarintLen32 = 5 + maxVarintLen33 = maxVarintLen32 + maxVarintLen64 = 10 + + int33Mask int64 = 1 << 7 + int33Mask2 = ^int33Mask + int33Mask3 = 1 << 6 + int33Mask4 = 8589934591 // 2^33-1 + int33Mask5 = 1 << 32 + int33Mask6 = int33Mask4 + 1 // 2^33 + + int64Mask3 = 1 << 6 + int64Mask4 = ^0 +) + +var ( + errOverflow32 = errors.New("overflows a 32-bit integer") + errOverflow33 = errors.New("overflows a 33-bit integer") + errOverflow64 = errors.New("overflows a 64-bit integer") +) + +// EncodeInt32 encodes the signed value into a buffer in LEB128 format +// +// See https://en.wikipedia.org/wiki/LEB128#Encode_signed_integer +func EncodeInt32(value int32) []byte { + return EncodeInt64(int64(value)) +} + +// EncodeInt64 encodes the signed value into a buffer in LEB128 format +// +// See https://en.wikipedia.org/wiki/LEB128#Encode_signed_integer +func EncodeInt64(value int64) (buf []byte) { + for { + // Take 7 remaining low-order bits from the value into b. + b := uint8(value & 0x7f) + // Extract the sign bit. + s := uint8(value & 0x40) + value >>= 7 + + // The encoding unsigned numbers is simpler as it only needs to check if the value is non-zero to tell if there + // are more bits to encode. Signed is a little more complicated as you have to double-check the sign bit. + // If either case, set the high-order bit to tell the reader there are more bytes in this int. + if (value != -1 || s == 0) && (value != 0 || s != 0) { + b |= 0x80 + } + + // Append b into the buffer + buf = append(buf, b) + if b&0x80 == 0 { + break + } + } + return buf +} + +// EncodeUint32 encodes the value into a buffer in LEB128 format +// +// See https://en.wikipedia.org/wiki/LEB128#Encode_unsigned_integer +func EncodeUint32(value uint32) []byte { + return EncodeUint64(uint64(value)) +} + +// EncodeUint64 encodes the value into a buffer in LEB128 format +// +// See https://en.wikipedia.org/wiki/LEB128#Encode_unsigned_integer +func EncodeUint64(value uint64) (buf []byte) { + // This is effectively a do/while loop where we take 7 bits of the value and encode them until it is zero. + for { + // Take 7 remaining low-order bits from the value into b. + b := uint8(value & 0x7f) + value = value >> 7 + + // If there are remaining bits, the value won't be zero: Set the high- + // order bit to tell the reader there are more bytes in this uint. + if value != 0 { + b |= 0x80 + } + + // Append b into the buffer + buf = append(buf, b) + if b&0x80 == 0 { + return buf + } + } +} + +type nextByte func(i int) (byte, error) + +func DecodeUint32(r io.ByteReader) (ret uint32, bytesRead uint64, err error) { + return decodeUint32(func(_ int) (byte, error) { return r.ReadByte() }) +} + +func LoadUint32(buf []byte) (ret uint32, bytesRead uint64, err error) { + return decodeUint32(func(i int) (byte, error) { + if i >= len(buf) { + return 0, io.EOF + } + return buf[i], nil + }) +} + +func decodeUint32(next nextByte) (ret uint32, bytesRead uint64, err error) { + // Derived from https://github.com/golang/go/blob/go1.24.0/src/encoding/binary/varint.go + // with the modification on the overflow handling tailored for 32-bits. + var s uint32 + for i := 0; i < maxVarintLen32; i++ { + b, err := next(i) + if err != nil { + return 0, 0, err + } + if b < 0x80 { + // Unused bits must be all zero. + if i == maxVarintLen32-1 && (b&0xf0) > 0 { + return 0, 0, errOverflow32 + } + return ret | uint32(b)<= bufLen { + return 0, 0, io.EOF + } + b := buf[i] + if b < 0x80 { + // Unused bits (non first bit) must all be zero. + if i == maxVarintLen64-1 && b > 1 { + return 0, 0, errOverflow64 + } + return ret | uint64(b)<= len(buf) { + return 0, io.EOF + } + return buf[i], nil + }) +} + +func decodeInt32(next nextByte) (ret int32, bytesRead uint64, err error) { + var shift int + var b byte + for { + b, err = next(int(bytesRead)) + if err != nil { + return 0, 0, fmt.Errorf("readByte failed: %w", err) + } + ret |= (int32(b) & 0x7f) << shift + shift += 7 + bytesRead++ + if b&0x80 == 0 { + if shift < 32 && (b&0x40) != 0 { + ret |= ^0 << shift + } + // Over flow checks. + // fixme: can be optimized. + if bytesRead > maxVarintLen32 { + return 0, 0, errOverflow32 + } else if unused := b & 0b00110000; bytesRead == maxVarintLen32 && ret < 0 && unused != 0b00110000 { + return 0, 0, errOverflow32 + } else if bytesRead == maxVarintLen32 && ret >= 0 && unused != 0x00 { + return 0, 0, errOverflow32 + } + return + } + } +} + +// DecodeInt33AsInt64 is a special cased decoder for wasm.BlockType which is encoded as a positive signed integer, yet +// still needs to fit the 32-bit range of allowed indices. Hence, this is 33, not 32-bit! +// +// See https://webassembly.github.io/spec/core/binary/instructions.html#control-instructions +func DecodeInt33AsInt64(r io.ByteReader) (ret int64, bytesRead uint64, err error) { + var shift int + var b int64 + var rb byte + for shift < 35 { + rb, err = r.ReadByte() + if err != nil { + return 0, 0, fmt.Errorf("readByte failed: %w", err) + } + b = int64(rb) + ret |= (b & int33Mask2) << shift + shift += 7 + bytesRead++ + if b&int33Mask == 0 { + break + } + } + + // fixme: can be optimized + if shift < 33 && (b&int33Mask3) == int33Mask3 { + ret |= int33Mask4 << shift + } + ret = ret & int33Mask4 + + // if 33rd bit == 1, we translate it as a corresponding signed-33bit minus value + if ret&int33Mask5 > 0 { + ret = ret - int33Mask6 + } + // Over flow checks. + // fixme: can be optimized. + if bytesRead > maxVarintLen33 { + return 0, 0, errOverflow33 + } else if unused := b & 0b00100000; bytesRead == maxVarintLen33 && ret < 0 && unused != 0b00100000 { + return 0, 0, errOverflow33 + } else if bytesRead == maxVarintLen33 && ret >= 0 && unused != 0x00 { + return 0, 0, errOverflow33 + } + return ret, bytesRead, nil +} + +func DecodeInt64(r io.ByteReader) (ret int64, bytesRead uint64, err error) { + return decodeInt64(func(_ int) (byte, error) { return r.ReadByte() }) +} + +func LoadInt64(buf []byte) (ret int64, bytesRead uint64, err error) { + return decodeInt64(func(i int) (byte, error) { + if i >= len(buf) { + return 0, io.EOF + } + return buf[i], nil + }) +} + +func decodeInt64(next nextByte) (ret int64, bytesRead uint64, err error) { + var shift int + var b byte + for { + b, err = next(int(bytesRead)) + if err != nil { + return 0, 0, fmt.Errorf("readByte failed: %w", err) + } + ret |= (int64(b) & 0x7f) << shift + shift += 7 + bytesRead++ + if b&0x80 == 0 { + if shift < 64 && (b&int64Mask3) == int64Mask3 { + ret |= int64Mask4 << shift + } + // Over flow checks. + // fixme: can be optimized. + if bytesRead > maxVarintLen64 { + return 0, 0, errOverflow64 + } else if unused := b & 0b00111110; bytesRead == maxVarintLen64 && ret < 0 && unused != 0b00111110 { + return 0, 0, errOverflow64 + } else if bytesRead == maxVarintLen64 && ret >= 0 && unused != 0x00 { + return 0, 0, errOverflow64 + } + return + } + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/moremath/moremath.go b/vendor/github.com/tetratelabs/wazero/internal/moremath/moremath.go new file mode 100644 index 00000000000..4741f07bb90 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/moremath/moremath.go @@ -0,0 +1,271 @@ +package moremath + +import ( + "math" +) + +// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/syntax/values.html#floating-point +const ( + // F32CanonicalNaNBits is the 32-bit float where payload's MSB equals 1 and others are all zero. + F32CanonicalNaNBits = uint32(0x7fc0_0000) + // F32CanonicalNaNBitsMask can be used to judge the value `v` is canonical nan as "v&F32CanonicalNaNBitsMask == F32CanonicalNaNBits" + F32CanonicalNaNBitsMask = uint32(0x7fff_ffff) + // F64CanonicalNaNBits is the 64-bit float where payload's MSB equals 1 and others are all zero. + F64CanonicalNaNBits = uint64(0x7ff8_0000_0000_0000) + // F64CanonicalNaNBitsMask can be used to judge the value `v` is canonical nan as "v&F64CanonicalNaNBitsMask == F64CanonicalNaNBits" + F64CanonicalNaNBitsMask = uint64(0x7fff_ffff_ffff_ffff) + // F32ArithmeticNaNPayloadMSB is used to extract the most significant bit of payload of 32-bit arithmetic NaN values + F32ArithmeticNaNPayloadMSB = uint32(0x0040_0000) + // F32ExponentMask is used to extract the exponent of 32-bit floating point. + F32ExponentMask = uint32(0x7f80_0000) + // F32ArithmeticNaNBits is an example 32-bit arithmetic NaN. + F32ArithmeticNaNBits = F32CanonicalNaNBits | 0b1 // Set first bit to make this different from the canonical NaN. + // F64ArithmeticNaNPayloadMSB is used to extract the most significant bit of payload of 64-bit arithmetic NaN values + F64ArithmeticNaNPayloadMSB = uint64(0x0008_0000_0000_0000) + // F64ExponentMask is used to extract the exponent of 64-bit floating point. + F64ExponentMask = uint64(0x7ff0_0000_0000_0000) + // F64ArithmeticNaNBits is an example 64-bit arithmetic NaN. + F64ArithmeticNaNBits = F64CanonicalNaNBits | 0b1 // Set first bit to make this different from the canonical NaN. +) + +// WasmCompatMin64 is the Wasm spec compatible variant of math.Min for 64-bit floating points. +func WasmCompatMin64(x, y float64) float64 { + switch { + case math.IsNaN(x) || math.IsNaN(y): + return returnF64NaNBinOp(x, y) + case math.IsInf(x, -1) || math.IsInf(y, -1): + return math.Inf(-1) + case x == 0 && x == y: + if math.Signbit(x) { + return x + } + return y + } + if x < y { + return x + } + return y +} + +// WasmCompatMin32 is the Wasm spec compatible variant of math.Min for 32-bit floating points. +func WasmCompatMin32(x, y float32) float32 { + x64, y64 := float64(x), float64(y) + switch { + case math.IsNaN(x64) || math.IsNaN(y64): + return returnF32NaNBinOp(x, y) + case math.IsInf(x64, -1) || math.IsInf(y64, -1): + return float32(math.Inf(-1)) + case x == 0 && x == y: + if math.Signbit(x64) { + return x + } + return y + } + if x < y { + return x + } + return y +} + +// WasmCompatMax64 is the Wasm spec compatible variant of math.Max for 64-bit floating points. +func WasmCompatMax64(x, y float64) float64 { + switch { + case math.IsNaN(x) || math.IsNaN(y): + return returnF64NaNBinOp(x, y) + case math.IsInf(x, 1) || math.IsInf(y, 1): + return math.Inf(1) + case x == 0 && x == y: + if math.Signbit(x) { + return y + } + return x + } + if x > y { + return x + } + return y +} + +// WasmCompatMax32 is the Wasm spec compatible variant of math.Max for 32-bit floating points. +func WasmCompatMax32(x, y float32) float32 { + x64, y64 := float64(x), float64(y) + switch { + case math.IsNaN(x64) || math.IsNaN(y64): + return returnF32NaNBinOp(x, y) + case math.IsInf(x64, 1) || math.IsInf(y64, 1): + return float32(math.Inf(1)) + case x == 0 && x == y: + if math.Signbit(x64) { + return y + } + return x + } + if x > y { + return x + } + return y +} + +// WasmCompatNearestF32 is the Wasm spec compatible variant of math.Round, used for Nearest instruction. +// For example, this converts 1.9 to 2.0, and this has the semantics of LLVM's rint intrinsic. +// +// e.g. math.Round(-4.5) results in -5 while this results in -4. +// +// See https://llvm.org/docs/LangRef.html#llvm-rint-intrinsic. +func WasmCompatNearestF32(f float32) float32 { + var res float32 + // TODO: look at https://github.com/bytecodealliance/wasmtime/pull/2171 and reconsider this algorithm + if f != 0 { + ceil := float32(math.Ceil(float64(f))) + floor := float32(math.Floor(float64(f))) + distToCeil := math.Abs(float64(f - ceil)) + distToFloor := math.Abs(float64(f - floor)) + h := ceil / 2.0 + if distToCeil < distToFloor { + res = ceil + } else if distToCeil == distToFloor && float32(math.Floor(float64(h))) == h { + res = ceil + } else { + res = floor + } + } else { + res = f + } + return returnF32UniOp(f, res) +} + +// WasmCompatNearestF64 is the Wasm spec compatible variant of math.Round, used for Nearest instruction. +// For example, this converts 1.9 to 2.0, and this has the semantics of LLVM's rint intrinsic. +// +// e.g. math.Round(-4.5) results in -5 while this results in -4. +// +// See https://llvm.org/docs/LangRef.html#llvm-rint-intrinsic. +func WasmCompatNearestF64(f float64) float64 { + // TODO: look at https://github.com/bytecodealliance/wasmtime/pull/2171 and reconsider this algorithm + var res float64 + if f != 0 { + ceil := math.Ceil(f) + floor := math.Floor(f) + distToCeil := math.Abs(f - ceil) + distToFloor := math.Abs(f - floor) + h := ceil / 2.0 + if distToCeil < distToFloor { + res = ceil + } else if distToCeil == distToFloor && math.Floor(h) == h { + res = ceil + } else { + res = floor + } + } else { + res = f + } + return returnF64UniOp(f, res) +} + +// WasmCompatCeilF32 is the same as math.Ceil on 32-bit except that +// the returned NaN value follows the Wasm specification on NaN +// propagation. +// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation +func WasmCompatCeilF32(f float32) float32 { + return returnF32UniOp(f, float32(math.Ceil(float64(f)))) +} + +// WasmCompatCeilF64 is the same as math.Ceil on 64-bit except that +// the returned NaN value follows the Wasm specification on NaN +// propagation. +// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation +func WasmCompatCeilF64(f float64) float64 { + return returnF64UniOp(f, math.Ceil(f)) +} + +// WasmCompatFloorF32 is the same as math.Floor on 32-bit except that +// the returned NaN value follows the Wasm specification on NaN +// propagation. +// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation +func WasmCompatFloorF32(f float32) float32 { + return returnF32UniOp(f, float32(math.Floor(float64(f)))) +} + +// WasmCompatFloorF64 is the same as math.Floor on 64-bit except that +// the returned NaN value follows the Wasm specification on NaN +// propagation. +// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation +func WasmCompatFloorF64(f float64) float64 { + return returnF64UniOp(f, math.Floor(f)) +} + +// WasmCompatTruncF32 is the same as math.Trunc on 32-bit except that +// the returned NaN value follows the Wasm specification on NaN +// propagation. +// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation +func WasmCompatTruncF32(f float32) float32 { + return returnF32UniOp(f, float32(math.Trunc(float64(f)))) +} + +// WasmCompatTruncF64 is the same as math.Trunc on 64-bit except that +// the returned NaN value follows the Wasm specification on NaN +// propagation. +// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation +func WasmCompatTruncF64(f float64) float64 { + return returnF64UniOp(f, math.Trunc(f)) +} + +func f32IsNaN(v float32) bool { + return v != v // this is how NaN is defined. +} + +func f64IsNaN(v float64) bool { + return v != v // this is how NaN is defined. +} + +// returnF32UniOp returns the result of 32-bit unary operation. This accepts `original` which is the operand, +// and `result` which is its result. This returns the `result` as-is if the result is not NaN. Otherwise, this follows +// the same logic as in the reference interpreter as well as the amd64 and arm64 floating point handling. +func returnF32UniOp(original, result float32) float32 { + // Following the same logic as in the reference interpreter: + // https://github.com/WebAssembly/spec/blob/d48af683f5e6d00c13f775ab07d29a15daf92203/interpreter/exec/fxx.ml#L115-L122 + if !f32IsNaN(result) { + return result + } + if !f32IsNaN(original) { + return math.Float32frombits(F32CanonicalNaNBits) + } + return math.Float32frombits(math.Float32bits(original) | F32CanonicalNaNBits) +} + +// returnF32UniOp returns the result of 64-bit unary operation. This accepts `original` which is the operand, +// and `result` which is its result. This returns the `result` as-is if the result is not NaN. Otherwise, this follows +// the same logic as in the reference interpreter as well as the amd64 and arm64 floating point handling. +func returnF64UniOp(original, result float64) float64 { + // Following the same logic as in the reference interpreter (== amd64 and arm64's behavior): + // https://github.com/WebAssembly/spec/blob/d48af683f5e6d00c13f775ab07d29a15daf92203/interpreter/exec/fxx.ml#L115-L122 + if !f64IsNaN(result) { + return result + } + if !f64IsNaN(original) { + return math.Float64frombits(F64CanonicalNaNBits) + } + return math.Float64frombits(math.Float64bits(original) | F64CanonicalNaNBits) +} + +// returnF64NaNBinOp returns a NaN for 64-bit binary operations. `x` and `y` are original floats +// and at least one of them is NaN. The returned NaN is guaranteed to comply with the NaN propagation +// procedure: https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation +func returnF64NaNBinOp(x, y float64) float64 { + if f64IsNaN(x) { + return math.Float64frombits(math.Float64bits(x) | F64CanonicalNaNBits) + } else { + return math.Float64frombits(math.Float64bits(y) | F64CanonicalNaNBits) + } +} + +// returnF64NaNBinOp returns a NaN for 32-bit binary operations. `x` and `y` are original floats +// and at least one of them is NaN. The returned NaN is guaranteed to comply with the NaN propagation +// procedure: https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation +func returnF32NaNBinOp(x, y float32) float32 { + if f32IsNaN(x) { + return math.Float32frombits(math.Float32bits(x) | F32CanonicalNaNBits) + } else { + return math.Float32frombits(math.Float32bits(y) | F32CanonicalNaNBits) + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid.go b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid.go new file mode 100644 index 00000000000..fb9a34bc479 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid.go @@ -0,0 +1,26 @@ +package platform + +// CpuFeatureFlags exposes methods for querying CPU capabilities +type CpuFeatureFlags uint64 + +const ( + // CpuFeatureAmd64SSE4_1 is the flag to query CpuFeatureFlags.Has for SSEv4.1 capabilities on amd64 + CpuFeatureAmd64SSE4_1 = 1 << iota + // CpuFeatureAmd64BMI1 is the flag to query CpuFeatureFlags.Has for Bit Manipulation Instruction Set 1 (e.g. TZCNT) on amd64 + CpuFeatureAmd64BMI1 + // CpuExtraFeatureABM is the flag to query CpuFeatureFlags.Has for Advanced Bit Manipulation capabilities (e.g. LZCNT) on amd64 + CpuFeatureAmd64ABM +) + +const ( + // CpuFeatureArm64Atomic is the flag to query CpuFeatureFlags.Has for Large System Extensions capabilities on arm64 + CpuFeatureArm64Atomic CpuFeatureFlags = 1 << iota +) + +func (c CpuFeatureFlags) Has(f CpuFeatureFlags) bool { + return c&f != 0 +} + +func (c CpuFeatureFlags) Raw() uint64 { + return uint64(c) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_amd64.go b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_amd64.go new file mode 100644 index 00000000000..9e7cf4f05bc --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_amd64.go @@ -0,0 +1,22 @@ +package platform + +import "golang.org/x/sys/cpu" + +// CpuFeatures exposes the capabilities for this CPU, queried via the Has method. +var CpuFeatures = loadCpuFeatureFlags() + +func loadCpuFeatureFlags() (flags CpuFeatureFlags) { + if cpu.X86.HasSSE41 { + flags |= CpuFeatureAmd64SSE4_1 + } + if cpu.X86.HasBMI1 { + flags |= CpuFeatureAmd64BMI1 + } + // x/sys/cpu does not track the ABM explicitly. + // LZCNT combined with BMI1 and BMI2 completes the expanded ABM instruction set. + // Intel includes LZCNT in BMI1, and all AMD CPUs with POPCNT also have LZCNT. + if cpu.X86.HasBMI1 && cpu.X86.HasBMI2 && cpu.X86.HasPOPCNT { + flags |= CpuFeatureAmd64ABM + } + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_arm64.go b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_arm64.go new file mode 100644 index 00000000000..af06dab9e8b --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_arm64.go @@ -0,0 +1,28 @@ +package platform + +import ( + "runtime" + + "golang.org/x/sys/cpu" +) + +// CpuFeatures exposes the capabilities for this CPU, queried via the Has method. +var CpuFeatures = loadCpuFeatureFlags() + +func loadCpuFeatureFlags() (flags CpuFeatureFlags) { + switch runtime.GOOS { + case "darwin", "windows": + // These OSes do not allow userland to read the instruction set attribute registers, + // but basically require atomic instructions: + // - "darwin" is the desktop version (mobile version is "ios"), + // and the M1 is a ARMv8.4. + // - "windows" requires them from Windows 11, see page 12 + // https://download.microsoft.com/download/7/8/8/788bf5ab-0751-4928-a22c-dffdc23c27f2/Minimum%20Hardware%20Requirements%20for%20Windows%2011.pdf + flags |= CpuFeatureArm64Atomic + default: + if cpu.ARM64.HasATOMICS { + flags |= CpuFeatureArm64Atomic + } + } + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_unsupported.go b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_unsupported.go new file mode 100644 index 00000000000..8f748ff8d16 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_unsupported.go @@ -0,0 +1,5 @@ +//go:build !(amd64 || arm64) + +package platform + +const CpuFeatures CpuFeatureFlags = 0 diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/crypto.go b/vendor/github.com/tetratelabs/wazero/internal/platform/crypto.go new file mode 100644 index 00000000000..c141f00f0c6 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/crypto.go @@ -0,0 +1,17 @@ +package platform + +import ( + "io" + "math/rand" +) + +// seed is a fixed seed value for NewFakeRandSource. +// +// Trivia: While arbitrary, 42 was chosen as it is the "Ultimate Answer" in +// the Douglas Adams novel "The Hitchhiker's Guide to the Galaxy." +const seed = int64(42) + +// NewFakeRandSource returns a deterministic source of random values. +func NewFakeRandSource() io.Reader { + return rand.New(rand.NewSource(seed)) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_linux.go b/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_linux.go new file mode 100644 index 00000000000..f3eabde21eb --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_linux.go @@ -0,0 +1,72 @@ +package platform + +import ( + "math/bits" + "os" + "sort" + "strconv" + "strings" + + "golang.org/x/sys/unix" +) + +var hugePagesConfigs []hugePagesConfig + +type hugePagesConfig struct { + size int + flag int +} + +func (hpc *hugePagesConfig) match(size int) bool { + return (size & (hpc.size - 1)) == 0 +} + +func init() { + dirents, err := os.ReadDir("/sys/kernel/mm/hugepages/") + if err != nil { + return + } + + for _, dirent := range dirents { + name := dirent.Name() + if !strings.HasPrefix(name, "hugepages-") { + continue + } + if !strings.HasSuffix(name, "kB") { + continue + } + n, err := strconv.ParseUint(name[10:len(name)-2], 10, 64) + if err != nil { + continue + } + if bits.OnesCount64(n) != 1 { + continue + } + n *= 1024 + hugePagesConfigs = append(hugePagesConfigs, hugePagesConfig{ + size: int(n), + flag: int(bits.TrailingZeros64(n)< hugePagesConfigs[j].size + }) +} + +func mmapCodeSegment(size int) ([]byte, error) { + flag := unix.MAP_ANON | unix.MAP_PRIVATE + prot := unix.PROT_READ | unix.PROT_WRITE + + for _, hugePagesConfig := range hugePagesConfigs { + if hugePagesConfig.match(size) { + b, err := unix.Mmap(-1, 0, size, prot, flag|hugePagesConfig.flag) + if err != nil { + continue + } + return b, nil + } + } + + return unix.Mmap(-1, 0, size, prot, flag) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_other.go b/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_other.go new file mode 100644 index 00000000000..9502e131339 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_other.go @@ -0,0 +1,19 @@ +// Separated from linux which has support for huge pages. + +//go:build unix && !linux + +package platform + +import "golang.org/x/sys/unix" + +func mmapCodeSegment(size int) ([]byte, error) { + return unix.Mmap( + -1, + 0, + size, + unix.PROT_READ|unix.PROT_WRITE, + // Anonymous as this is not an actual file, but a memory, + // Private as this is in-process memory region. + unix.MAP_ANON|unix.MAP_PRIVATE, + ) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_unix.go b/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_unix.go new file mode 100644 index 00000000000..c4be92c39e9 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_unix.go @@ -0,0 +1,14 @@ +//go:build unix + +package platform + +import "golang.org/x/sys/unix" + +func munmapCodeSegment(code []byte) error { + return unix.Munmap(code) +} + +// MprotectCodeSegment is like unix.Mprotect with RX permission. +func MprotectCodeSegment(b []byte) (err error) { + return unix.Mprotect(b, unix.PROT_READ|unix.PROT_EXEC) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_unsupported.go b/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_unsupported.go new file mode 100644 index 00000000000..6223053ac68 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_unsupported.go @@ -0,0 +1,22 @@ +//go:build !(unix || windows) + +package platform + +import ( + "fmt" + "runtime" +) + +var errUnsupported = fmt.Errorf("mmap unsupported on GOOS=%s. Use interpreter instead.", runtime.GOOS) + +func munmapCodeSegment(code []byte) error { + panic(errUnsupported) +} + +func mmapCodeSegment(size int) ([]byte, error) { + panic(errUnsupported) +} + +func MprotectCodeSegment(b []byte) (err error) { + panic(errUnsupported) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_windows.go b/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_windows.go new file mode 100644 index 00000000000..80f01802bb0 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_windows.go @@ -0,0 +1,31 @@ +package platform + +import ( + "unsafe" + + "golang.org/x/sys/windows" +) + +func munmapCodeSegment(code []byte) error { + address := unsafe.Pointer(&code[0]) + size := uintptr(0) // size must be 0 because we're using MEM_RELEASE. + return windows.VirtualFree(uintptr(address), size, windows.MEM_RELEASE) +} + +func mmapCodeSegment(size int) ([]byte, error) { + address := uintptr(0) // system determines where to allocate the region. + p, err := windows.VirtualAlloc(address, uintptr(size), + windows.MEM_COMMIT, windows.PAGE_READWRITE) + if err != nil { + return nil, err + } + + return unsafe.Slice((*byte)(unsafe.Pointer(p)), size), nil +} + +var old = uint32(windows.PAGE_READWRITE) + +func MprotectCodeSegment(b []byte) (err error) { + address := unsafe.Pointer(&b[0]) + return windows.VirtualProtect(uintptr(address), uintptr(len(b)), windows.PAGE_EXECUTE_READ, &old) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/path.go b/vendor/github.com/tetratelabs/wazero/internal/platform/path.go new file mode 100644 index 00000000000..361049ae250 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/path.go @@ -0,0 +1,6 @@ +//go:build !windows + +package platform + +// ToPosixPath returns the input, as only windows might return backslashes. +func ToPosixPath(in string) string { return in } diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/path_windows.go b/vendor/github.com/tetratelabs/wazero/internal/platform/path_windows.go new file mode 100644 index 00000000000..77c4187d95f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/path_windows.go @@ -0,0 +1,17 @@ +package platform + +import "strings" + +// ToPosixPath returns the input, converting any backslashes to forward ones. +func ToPosixPath(in string) string { + // strings.Map only allocates on change, which is good enough especially as + // path.Join uses forward slash even on windows. + return strings.Map(windowsToPosixSeparator, in) +} + +func windowsToPosixSeparator(r rune) rune { + if r == '\\' { + return '/' + } + return r +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/platform.go b/vendor/github.com/tetratelabs/wazero/internal/platform/platform.go new file mode 100644 index 00000000000..722ac1243d0 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/platform.go @@ -0,0 +1,49 @@ +// Package platform includes runtime-specific code needed for the compiler or otherwise. +package platform + +import ( + "runtime" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental" +) + +// CompilerSupported includes constraints here and also the assembler. +func CompilerSupported() bool { + return CompilerSupports(api.CoreFeaturesV2) +} + +func CompilerSupports(features api.CoreFeatures) bool { + switch runtime.GOOS { + case "linux", "darwin", "freebsd", "netbsd", "windows": + if runtime.GOARCH == "arm64" { + if features.IsEnabled(experimental.CoreFeaturesThreads) { + return CpuFeatures.Has(CpuFeatureArm64Atomic) + } + return true + } + fallthrough + case "dragonfly", "solaris", "illumos": + return runtime.GOARCH == "amd64" && CpuFeatures.Has(CpuFeatureAmd64SSE4_1) + default: + return false + } +} + +// MmapCodeSegment copies the code into the executable region and returns the byte slice of the region. +// +// See https://man7.org/linux/man-pages/man2/mmap.2.html for mmap API and flags. +func MmapCodeSegment(size int) ([]byte, error) { + if size == 0 { + panic("BUG: MmapCodeSegment with zero length") + } + return mmapCodeSegment(size) +} + +// MunmapCodeSegment unmaps the given memory region. +func MunmapCodeSegment(code []byte) error { + if len(code) == 0 { + panic("BUG: MunmapCodeSegment with zero length") + } + return munmapCodeSegment(code) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/time.go b/vendor/github.com/tetratelabs/wazero/internal/platform/time.go new file mode 100644 index 00000000000..fa9da1acb20 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/time.go @@ -0,0 +1,76 @@ +package platform + +import ( + "sync/atomic" + "time" + + "github.com/tetratelabs/wazero/sys" +) + +const ( + ms = int64(time.Millisecond) + // FakeEpochNanos is midnight UTC 2022-01-01 and exposed for testing + FakeEpochNanos = 1640995200000 * ms +) + +// NewFakeWalltime implements sys.Walltime with FakeEpochNanos that increases by 1ms each reading. +// See /RATIONALE.md +func NewFakeWalltime() sys.Walltime { + // AddInt64 returns the new value. Adjust so the first reading will be FakeEpochNanos + t := FakeEpochNanos - ms + return func() (sec int64, nsec int32) { + wt := atomic.AddInt64(&t, ms) + return wt / 1e9, int32(wt % 1e9) + } +} + +// NewFakeNanotime implements sys.Nanotime that increases by 1ms each reading. +// See /RATIONALE.md +func NewFakeNanotime() sys.Nanotime { + // AddInt64 returns the new value. Adjust so the first reading will be zero. + t := int64(0) - ms + return func() int64 { + return atomic.AddInt64(&t, ms) + } +} + +// FakeNanosleep implements sys.Nanosleep by returning without sleeping. +var FakeNanosleep = sys.Nanosleep(func(int64) {}) + +// FakeOsyield implements sys.Osyield by returning without yielding. +var FakeOsyield = sys.Osyield(func() {}) + +// Walltime implements sys.Walltime with time.Now. +// +// Note: This is only notably less efficient than it could be is reading +// runtime.walltime(). time.Now defensively reads nanotime also, just in case +// time.Since is used. This doubles the performance impact. However, wall time +// is likely to be read less frequently than Nanotime. Also, doubling the cost +// matters less on fast platforms that can return both in <=100ns. +func Walltime() (sec int64, nsec int32) { + t := time.Now() + return t.Unix(), int32(t.Nanosecond()) +} + +// nanoBase uses time.Now to ensure a monotonic clock reading on all platforms +// via time.Since. +var nanoBase = time.Now() + +// nanotimePortable implements sys.Nanotime with time.Since. +// +// Note: This is less efficient than it could be is reading runtime.nanotime(), +// Just to do that requires CGO. +func nanotimePortable() int64 { + return time.Since(nanoBase).Nanoseconds() +} + +// Nanotime implements sys.Nanotime with runtime.nanotime() if CGO is available +// and time.Since if not. +func Nanotime() int64 { + return nanotime() +} + +// Nanosleep implements sys.Nanosleep with time.Sleep. +func Nanosleep(ns int64) { + time.Sleep(time.Duration(ns)) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/time_notwindows.go b/vendor/github.com/tetratelabs/wazero/internal/platform/time_notwindows.go new file mode 100644 index 00000000000..a1bdc8d5d45 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/time_notwindows.go @@ -0,0 +1,11 @@ +//go:build !windows + +package platform + +import _ "unsafe" // for go:linkname + +// nanotime uses runtime.nanotime as it is available on all platforms and +// benchmarks faster than using time.Since. +// +//go:linkname nanotime runtime.nanotime +func nanotime() int64 diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/time_windows.go b/vendor/github.com/tetratelabs/wazero/internal/platform/time_windows.go new file mode 100644 index 00000000000..53bf421a604 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/time_windows.go @@ -0,0 +1,40 @@ +//go:build windows + +package platform + +import ( + "math/bits" + "syscall" + "time" + "unsafe" +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + _QueryPerformanceCounter = kernel32.NewProc("QueryPerformanceCounter") + _QueryPerformanceFrequency = kernel32.NewProc("QueryPerformanceFrequency") +) + +var qpcfreq uint64 + +func init() { + _, _, _ = _QueryPerformanceFrequency.Call(uintptr(unsafe.Pointer(&qpcfreq))) +} + +// On Windows, time.Time handled in time package cannot have the nanosecond precision. +// The reason is that by default, it doesn't use QueryPerformanceCounter[1], but instead, use "interrupt time" +// which doesn't support nanoseconds precision (though it is a monotonic) [2, 3]. +// +// [1] https://learn.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter +// [2] https://github.com/golang/go/blob/go1.24.0/src/runtime/sys_windows_amd64.s#L279-L284 +// [3] https://github.com/golang/go/blob/go1.24.0/src/runtime/time_windows.h#L7-L13 +// +// Therefore, on Windows, we directly invoke the syscall for QPC instead of time.Now or runtime.nanotime. +// See https://github.com/golang/go/issues/31160 for example. +func nanotime() int64 { + var counter uint64 + _, _, _ = _QueryPerformanceCounter.Call(uintptr(unsafe.Pointer(&counter))) + hi, lo := bits.Mul64(counter, uint64(time.Second)) + nanos, _ := bits.Div64(hi, lo, qpcfreq) + return int64(nanos) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sock/sock.go b/vendor/github.com/tetratelabs/wazero/internal/sock/sock.go new file mode 100644 index 00000000000..ca17aa39ee7 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sock/sock.go @@ -0,0 +1,89 @@ +package sock + +import ( + "fmt" + "net" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +// TCPSock is a pseudo-file representing a TCP socket. +type TCPSock interface { + sys.File + + Accept() (TCPConn, sys.Errno) +} + +// TCPConn is a pseudo-file representing a TCP connection. +type TCPConn interface { + sys.File + + // Recvfrom only supports the flag sysfs.MSG_PEEK + // TODO: document this like sys.File with known sys.Errno + Recvfrom(p []byte, flags int) (n int, errno sys.Errno) + + // TODO: document this like sys.File with known sys.Errno + Shutdown(how int) sys.Errno +} + +// ConfigKey is a context.Context Value key. Its associated value should be a Config. +type ConfigKey struct{} + +// Config is an internal struct meant to implement +// the interface in experimental/sock/Config. +type Config struct { + // TCPAddresses is a slice of the configured host:port pairs. + TCPAddresses []TCPAddress +} + +// TCPAddress is a host:port pair to pre-open. +type TCPAddress struct { + // Host is the host name for this listener. + Host string + // Port is the port number for this listener. + Port int +} + +// WithTCPListener implements the method of the same name in experimental/sock/Config. +// +// However, to avoid cyclic dependencies, this is returning the *Config in this scope. +// The interface is implemented in experimental/sock/Config via delegation. +func (c *Config) WithTCPListener(host string, port int) *Config { + ret := c.clone() + ret.TCPAddresses = append(ret.TCPAddresses, TCPAddress{host, port}) + return &ret +} + +// Makes a deep copy of this sockConfig. +func (c *Config) clone() Config { + ret := *c + ret.TCPAddresses = make([]TCPAddress, 0, len(c.TCPAddresses)) + ret.TCPAddresses = append(ret.TCPAddresses, c.TCPAddresses...) + return ret +} + +// BuildTCPListeners build listeners from the current configuration. +func (c *Config) BuildTCPListeners() (tcpListeners []*net.TCPListener, err error) { + for _, tcpAddr := range c.TCPAddresses { + var ln net.Listener + ln, err = net.Listen("tcp", tcpAddr.String()) + if err != nil { + break + } + if tcpln, ok := ln.(*net.TCPListener); ok { + tcpListeners = append(tcpListeners, tcpln) + } + } + if err != nil { + // An error occurred, cleanup. + for _, l := range tcpListeners { + _ = l.Close() // Ignore errors, we are already cleaning. + } + tcpListeners = nil + } + return +} + +func (t TCPAddress) String() string { + return fmt.Sprintf("%s:%d", t.Host, t.Port) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sock/sock_supported.go b/vendor/github.com/tetratelabs/wazero/internal/sock/sock_supported.go new file mode 100644 index 00000000000..32f9bc4b6d5 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sock/sock_supported.go @@ -0,0 +1,11 @@ +//go:build !(plan9 || js) + +package sock + +import "syscall" + +const ( + SHUT_RD = syscall.SHUT_RD + SHUT_RDWR = syscall.SHUT_RDWR + SHUT_WR = syscall.SHUT_WR +) diff --git a/vendor/github.com/tetratelabs/wazero/internal/sock/sock_unsupported.go b/vendor/github.com/tetratelabs/wazero/internal/sock/sock_unsupported.go new file mode 100644 index 00000000000..76ec031efa2 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sock/sock_unsupported.go @@ -0,0 +1,10 @@ +//go:build plan9 || js + +package sock + +// plan9/js doesn't declare these constants +const ( + SHUT_RD = 1 << iota + SHUT_WR + SHUT_RDWR = SHUT_RD | SHUT_WR +) diff --git a/vendor/github.com/tetratelabs/wazero/internal/sys/fs.go b/vendor/github.com/tetratelabs/wazero/internal/sys/fs.go new file mode 100644 index 00000000000..157de788f3f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sys/fs.go @@ -0,0 +1,457 @@ +package sys + +import ( + "io" + "io/fs" + "net" + + "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/descriptor" + "github.com/tetratelabs/wazero/internal/fsapi" + socketapi "github.com/tetratelabs/wazero/internal/sock" + "github.com/tetratelabs/wazero/internal/sysfs" +) + +const ( + FdStdin int32 = iota + FdStdout + FdStderr + // FdPreopen is the file descriptor of the first pre-opened directory. + // + // # Why file descriptor 3? + // + // While not specified, the most common WASI implementation, wasi-libc, + // expects POSIX style file descriptor allocation, where the lowest + // available number is used to open the next file. Since 1 and 2 are taken + // by stdout and stderr, the next is 3. + // - https://github.com/WebAssembly/WASI/issues/122 + // - https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_14 + // - https://github.com/WebAssembly/wasi-libc/blob/wasi-sdk-16/libc-bottom-half/sources/preopens.c#L215 + FdPreopen +) + +const modeDevice = fs.ModeDevice | 0o640 + +// FileEntry maps a path to an open file in a file system. +type FileEntry struct { + // Name is the name of the directory up to its pre-open, or the pre-open + // name itself when IsPreopen. + // + // # Notes + // + // - This can drift on rename. + // - This relates to the guest path, which is not the real file path + // except if the entire host filesystem was made available. + Name string + + // IsPreopen is a directory that is lazily opened. + IsPreopen bool + + // FS is the filesystem associated with the pre-open. + FS sys.FS + + // File is always non-nil. + File fsapi.File + + // direntCache is nil until DirentCache was called. + direntCache *DirentCache +} + +// DirentCache gets or creates a DirentCache for this file or returns an error. +// +// # Errors +// +// A zero sys.Errno is success. The below are expected otherwise: +// - sys.ENOSYS: the implementation does not support this function. +// - sys.EBADF: the dir was closed or not readable. +// - sys.ENOTDIR: the file was not a directory. +// +// # Notes +// +// - See /RATIONALE.md for design notes. +func (f *FileEntry) DirentCache() (*DirentCache, sys.Errno) { + if dir := f.direntCache; dir != nil { + return dir, 0 + } + + // Require the file to be a directory vs a late error on the same. + if isDir, errno := f.File.IsDir(); errno != 0 { + return nil, errno + } else if !isDir { + return nil, sys.ENOTDIR + } + + // Generate the dotEntries only once. + if dotEntries, errno := synthesizeDotEntries(f); errno != 0 { + return nil, errno + } else { + f.direntCache = &DirentCache{f: f.File, dotEntries: dotEntries} + } + + return f.direntCache, 0 +} + +// DirentCache is a caching abstraction of sys.File Readdir. +// +// This is special-cased for "wasi_snapshot_preview1.fd_readdir", and may be +// unneeded, or require changes, to support preview1 or preview2. +// - The position of the dirents are serialized as `d_next`. For reasons +// described below, any may need to be re-read. This accepts any positions +// in the cache, rather than track the position of the last dirent. +// - dot entries ("." and "..") must be returned. See /RATIONALE.md for why. +// - An sys.Dirent Name is variable length, it could exceed memory size and +// need to be re-read. +// - Multiple dirents may be returned. It is more efficient to read from the +// underlying file in bulk vs one-at-a-time. +// +// The last results returned by Read are cached, but entries before that +// position are not. This support re-reading entries that couldn't fit into +// memory without accidentally caching all entries in a large directory. This +// approach is sometimes called a sliding window. +type DirentCache struct { + // f is the underlying file + f sys.File + + // dotEntries are the "." and ".." entries added when the directory is + // initialized. + dotEntries []sys.Dirent + + // dirents are the potentially unread directory entries. + // + // Internal detail: nil is different from zero length. Zero length is an + // exhausted directory (eof). nil means the re-read. + dirents []sys.Dirent + + // countRead is the total count of dirents read since last rewind. + countRead uint64 + + // eof is true when the underlying file is at EOF. This avoids re-reading + // the directory when it is exhausted. Entires in an exhausted directory + // are not visible until it is rewound via calling Read with `pos==0`. + eof bool +} + +// synthesizeDotEntries generates a slice of the two elements "." and "..". +func synthesizeDotEntries(f *FileEntry) ([]sys.Dirent, sys.Errno) { + dotIno, errno := f.File.Ino() + if errno != 0 { + return nil, errno + } + result := [2]sys.Dirent{} + result[0] = sys.Dirent{Name: ".", Ino: dotIno, Type: fs.ModeDir} + // See /RATIONALE.md for why we don't attempt to get an inode for ".." and + // why in wasi-libc this won't fan-out either. + result[1] = sys.Dirent{Name: "..", Ino: 0, Type: fs.ModeDir} + return result[:], 0 +} + +// exhaustedDirents avoids allocating empty slices. +var exhaustedDirents = [0]sys.Dirent{} + +// Read is similar to and returns the same errors as `Readdir` on sys.File. +// The main difference is this caches entries returned, resulting in multiple +// valid positions to read from. +// +// When zero, `pos` means rewind to the beginning of this directory. This +// implies a rewind (Seek to zero on the underlying sys.File), unless the +// initial entries are still cached. +// +// When non-zero, `pos` is the zero based index of all dirents returned since +// last rewind. Only entries beginning at `pos` are cached for subsequent +// calls. A non-zero `pos` before the cache returns sys.ENOENT for reasons +// described on DirentCache documentation. +// +// Up to `n` entries are cached and returned. When `n` exceeds the cache, the +// difference are read from the underlying sys.File via `Readdir`. EOF is +// when `len(dirents)` returned are less than `n`. +func (d *DirentCache) Read(pos uint64, n uint32) (dirents []sys.Dirent, errno sys.Errno) { + switch { + case pos > d.countRead: // farther than read or negative coerced to uint64. + return nil, sys.ENOENT + case pos == 0 && d.dirents != nil: + // Rewind if we have already read entries. This allows us to see new + // entries added after the directory was opened. + if _, errno = d.f.Seek(0, io.SeekStart); errno != 0 { + return + } + d.dirents = nil // dump cache + d.countRead = 0 + } + + if n == 0 { + return // special case no entries. + } + + if d.dirents == nil { + // Always populate dot entries, which makes min len(dirents) == 2. + d.dirents = d.dotEntries + d.countRead = 2 + d.eof = false + + if countToRead := int(n - 2); countToRead <= 0 { + return + } else if dirents, errno = d.f.Readdir(countToRead); errno != 0 { + return + } else if countRead := len(dirents); countRead > 0 { + d.eof = countRead < countToRead + d.dirents = append(d.dotEntries, dirents...) + d.countRead += uint64(countRead) + } + + return d.cachedDirents(n), 0 + } + + // Reset our cache to the first entry being read. + cacheStart := d.countRead - uint64(len(d.dirents)) + if pos < cacheStart { + // We don't currently allow reads before our cache because Seek(0) is + // the only portable way. Doing otherwise requires skipping, which we + // won't do unless wasi-testsuite starts requiring it. Implementing + // this would allow re-reading a large directory, so care would be + // needed to not buffer the entire directory in memory while skipping. + errno = sys.ENOENT + return + } else if posInCache := pos - cacheStart; posInCache != 0 { + if uint64(len(d.dirents)) == posInCache { + // Avoid allocation re-slicing to zero length. + d.dirents = exhaustedDirents[:] + } else { + d.dirents = d.dirents[posInCache:] + } + } + + // See if we need more entries. + if countToRead := int(n) - len(d.dirents); countToRead > 0 && !d.eof { + // Try to read more, which could fail. + if dirents, errno = d.f.Readdir(countToRead); errno != 0 { + return + } + + // Append the next read entries if we weren't at EOF. + if countRead := len(dirents); countRead > 0 { + d.eof = countRead < countToRead + d.dirents = append(d.dirents, dirents...) + d.countRead += uint64(countRead) + } + } + + return d.cachedDirents(n), 0 +} + +// cachedDirents returns up to `n` dirents from the cache. +func (d *DirentCache) cachedDirents(n uint32) []sys.Dirent { + direntCount := uint32(len(d.dirents)) + switch { + case direntCount == 0: + return nil + case direntCount > n: + return d.dirents[:n] + } + return d.dirents +} + +type FSContext struct { + // openedFiles is a map of file descriptor numbers (>=FdPreopen) to open files + // (or directories) and defaults to empty. + // TODO: This is unguarded, so not goroutine-safe! + openedFiles FileTable +} + +// FileTable is a specialization of the descriptor.Table type used to map file +// descriptors to file entries. +type FileTable = descriptor.Table[int32, *FileEntry] + +// LookupFile returns a file if it is in the table. +func (c *FSContext) LookupFile(fd int32) (*FileEntry, bool) { + return c.openedFiles.Lookup(fd) +} + +// OpenFile opens the file into the table and returns its file descriptor. +// The result must be closed by CloseFile or Close. +func (c *FSContext) OpenFile(fs sys.FS, path string, flag sys.Oflag, perm fs.FileMode) (int32, sys.Errno) { + if f, errno := fs.OpenFile(path, flag, perm); errno != 0 { + return 0, errno + } else { + fe := &FileEntry{FS: fs, File: fsapi.Adapt(f)} + if path == "/" || path == "." { + fe.Name = "" + } else { + fe.Name = path + } + if newFD, ok := c.openedFiles.Insert(fe); !ok { + return 0, sys.EBADF + } else { + return newFD, 0 + } + } +} + +// Renumber assigns the file pointed by the descriptor `from` to `to`. +func (c *FSContext) Renumber(from, to int32) sys.Errno { + fromFile, ok := c.openedFiles.Lookup(from) + if !ok || to < 0 { + return sys.EBADF + } else if fromFile.IsPreopen { + return sys.ENOTSUP + } + + // If toFile is already open, we close it to prevent windows lock issues. + // + // The doc is unclear and other implementations do nothing for already-opened To FDs. + // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_renumberfd-fd-to-fd---errno + // https://github.com/bytecodealliance/wasmtime/blob/main/crates/wasi-common/src/snapshots/preview_1.rs#L531-L546 + if toFile, ok := c.openedFiles.Lookup(to); ok { + if toFile.IsPreopen { + return sys.ENOTSUP + } + _ = toFile.File.Close() + } + + c.openedFiles.Delete(from) + if !c.openedFiles.InsertAt(fromFile, to) { + return sys.EBADF + } + return 0 +} + +// SockAccept accepts a sock.TCPConn into the file table and returns its file +// descriptor. +func (c *FSContext) SockAccept(sockFD int32, nonblock bool) (int32, sys.Errno) { + var sock socketapi.TCPSock + if e, ok := c.LookupFile(sockFD); !ok || !e.IsPreopen { + return 0, sys.EBADF // Not a preopen + } else if sock, ok = e.File.(socketapi.TCPSock); !ok { + return 0, sys.EBADF // Not a sock + } + + conn, errno := sock.Accept() + if errno != 0 { + return 0, errno + } + + fe := &FileEntry{File: fsapi.Adapt(conn)} + + if nonblock { + if errno = fe.File.SetNonblock(true); errno != 0 { + _ = conn.Close() + return 0, errno + } + } + + if newFD, ok := c.openedFiles.Insert(fe); !ok { + return 0, sys.EBADF + } else { + return newFD, 0 + } +} + +// CloseFile returns any error closing the existing file. +func (c *FSContext) CloseFile(fd int32) (errno sys.Errno) { + f, ok := c.openedFiles.Lookup(fd) + if !ok { + return sys.EBADF + } + if errno = f.File.Close(); errno != 0 { + return errno + } + c.openedFiles.Delete(fd) + return errno +} + +// Close implements io.Closer +func (c *FSContext) Close() (err error) { + // Close any files opened in this context + c.openedFiles.Range(func(fd int32, entry *FileEntry) bool { + if errno := entry.File.Close(); errno != 0 { + err = errno // This means err returned == the last non-nil error. + } + return true + }) + // A closed FSContext cannot be reused so clear the state. + c.openedFiles = FileTable{} + return +} + +// InitFSContext initializes a FSContext with stdio streams and optional +// pre-opened filesystems and TCP listeners. +func (c *Context) InitFSContext( + stdin io.Reader, + stdout, stderr io.Writer, + fs []sys.FS, guestPaths []string, + tcpListeners []*net.TCPListener, +) (err error) { + inFile, err := stdinFileEntry(stdin) + if err != nil { + return err + } + c.fsc.openedFiles.Insert(inFile) + outWriter, err := stdioWriterFileEntry("stdout", stdout) + if err != nil { + return err + } + c.fsc.openedFiles.Insert(outWriter) + errWriter, err := stdioWriterFileEntry("stderr", stderr) + if err != nil { + return err + } + c.fsc.openedFiles.Insert(errWriter) + + for i, f := range fs { + guestPath := guestPaths[i] + + if StripPrefixesAndTrailingSlash(guestPath) == "" { + // Default to bind to '/' when guestPath is effectively empty. + guestPath = "/" + } + c.fsc.openedFiles.Insert(&FileEntry{ + FS: f, + Name: guestPath, + IsPreopen: true, + File: &lazyDir{fs: f}, + }) + } + + for _, tl := range tcpListeners { + c.fsc.openedFiles.Insert(&FileEntry{IsPreopen: true, File: fsapi.Adapt(sysfs.NewTCPListenerFile(tl))}) + } + return nil +} + +// StripPrefixesAndTrailingSlash skips any leading "./" or "/" such that the +// result index begins with another string. A result of "." coerces to the +// empty string "" because the current directory is handled by the guest. +// +// Results are the offset/len pair which is an optimization to avoid re-slicing +// overhead, as this function is called for every path operation. +// +// Note: Relative paths should be handled by the guest, as that's what knows +// what the current directory is. However, paths that escape the current +// directory e.g. "../.." have been found in `tinygo test` and this +// implementation takes care to avoid it. +func StripPrefixesAndTrailingSlash(path string) string { + // strip trailing slashes + pathLen := len(path) + for ; pathLen > 0 && path[pathLen-1] == '/'; pathLen-- { + } + + pathI := 0 +loop: + for pathI < pathLen { + switch path[pathI] { + case '/': + pathI++ + case '.': + nextI := pathI + 1 + if nextI < pathLen && path[nextI] == '/' { + pathI = nextI + 1 + } else if nextI == pathLen { + pathI = nextI + } else { + break loop + } + default: + break loop + } + } + return path[pathI:pathLen] +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sys/lazy.go b/vendor/github.com/tetratelabs/wazero/internal/sys/lazy.go new file mode 100644 index 00000000000..fe233d29ea0 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sys/lazy.go @@ -0,0 +1,151 @@ +package sys + +import ( + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" + "github.com/tetratelabs/wazero/sys" +) + +// compile-time check to ensure lazyDir implements sys.File. +var _ experimentalsys.File = (*lazyDir)(nil) + +type lazyDir struct { + experimentalsys.DirFile + + fs experimentalsys.FS + f experimentalsys.File +} + +// Dev implements the same method as documented on sys.File +func (d *lazyDir) Dev() (uint64, experimentalsys.Errno) { + if f, ok := d.file(); !ok { + return 0, experimentalsys.EBADF + } else { + return f.Dev() + } +} + +// Ino implements the same method as documented on sys.File +func (d *lazyDir) Ino() (sys.Inode, experimentalsys.Errno) { + if f, ok := d.file(); !ok { + return 0, experimentalsys.EBADF + } else { + return f.Ino() + } +} + +// IsDir implements the same method as documented on sys.File +func (d *lazyDir) IsDir() (bool, experimentalsys.Errno) { + // Note: we don't return a constant because we don't know if this is really + // backed by a dir, until the first call. + if f, ok := d.file(); !ok { + return false, experimentalsys.EBADF + } else { + return f.IsDir() + } +} + +// IsAppend implements the same method as documented on sys.File +func (d *lazyDir) IsAppend() bool { + return false +} + +// SetAppend implements the same method as documented on sys.File +func (d *lazyDir) SetAppend(bool) experimentalsys.Errno { + return experimentalsys.EISDIR +} + +// Seek implements the same method as documented on sys.File +func (d *lazyDir) Seek(offset int64, whence int) (newOffset int64, errno experimentalsys.Errno) { + if f, ok := d.file(); !ok { + return 0, experimentalsys.EBADF + } else { + return f.Seek(offset, whence) + } +} + +// Stat implements the same method as documented on sys.File +func (d *lazyDir) Stat() (sys.Stat_t, experimentalsys.Errno) { + if f, ok := d.file(); !ok { + return sys.Stat_t{}, experimentalsys.EBADF + } else { + return f.Stat() + } +} + +// Readdir implements the same method as documented on sys.File +func (d *lazyDir) Readdir(n int) (dirents []experimentalsys.Dirent, errno experimentalsys.Errno) { + if f, ok := d.file(); !ok { + return nil, experimentalsys.EBADF + } else { + return f.Readdir(n) + } +} + +// Sync implements the same method as documented on sys.File +func (d *lazyDir) Sync() experimentalsys.Errno { + if f, ok := d.file(); !ok { + return experimentalsys.EBADF + } else { + return f.Sync() + } +} + +// Datasync implements the same method as documented on sys.File +func (d *lazyDir) Datasync() experimentalsys.Errno { + if f, ok := d.file(); !ok { + return experimentalsys.EBADF + } else { + return f.Datasync() + } +} + +// Utimens implements the same method as documented on sys.File +func (d *lazyDir) Utimens(atim, mtim int64) experimentalsys.Errno { + if f, ok := d.file(); !ok { + return experimentalsys.EBADF + } else { + return f.Utimens(atim, mtim) + } +} + +// file returns the underlying file or false if it doesn't exist. +func (d *lazyDir) file() (experimentalsys.File, bool) { + if f := d.f; d.f != nil { + return f, true + } + var errno experimentalsys.Errno + d.f, errno = d.fs.OpenFile(".", experimentalsys.O_RDONLY, 0) + switch errno { + case 0: + return d.f, true + case experimentalsys.ENOENT: + return nil, false + default: + panic(errno) // unexpected + } +} + +// Close implements fs.File +func (d *lazyDir) Close() experimentalsys.Errno { + f := d.f + if f == nil { + return 0 // never opened + } + return f.Close() +} + +// IsNonblock implements the same method as documented on fsapi.File +func (d *lazyDir) IsNonblock() bool { + return false +} + +// SetNonblock implements the same method as documented on fsapi.File +func (d *lazyDir) SetNonblock(bool) experimentalsys.Errno { + return experimentalsys.EISDIR +} + +// Poll implements the same method as documented on fsapi.File +func (d *lazyDir) Poll(fsapi.Pflag, int32) (ready bool, errno experimentalsys.Errno) { + return false, experimentalsys.ENOSYS +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sys/stdio.go b/vendor/github.com/tetratelabs/wazero/internal/sys/stdio.go new file mode 100644 index 00000000000..32c33661eb1 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sys/stdio.go @@ -0,0 +1,128 @@ +package sys + +import ( + "io" + "os" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" + "github.com/tetratelabs/wazero/internal/sysfs" + "github.com/tetratelabs/wazero/sys" +) + +// StdinFile is a fs.ModeDevice file for use implementing FdStdin. +// This is safer than reading from os.DevNull as it can never overrun +// operating system file descriptors. +type StdinFile struct { + noopStdinFile + io.Reader +} + +// Read implements the same method as documented on sys.File +func (f *StdinFile) Read(buf []byte) (int, experimentalsys.Errno) { + n, err := f.Reader.Read(buf) + return n, experimentalsys.UnwrapOSError(err) +} + +type writerFile struct { + noopStdoutFile + + w io.Writer +} + +// Write implements the same method as documented on sys.File +func (f *writerFile) Write(buf []byte) (int, experimentalsys.Errno) { + n, err := f.w.Write(buf) + return n, experimentalsys.UnwrapOSError(err) +} + +// noopStdinFile is a fs.ModeDevice file for use implementing FdStdin. This is +// safer than reading from os.DevNull as it can never overrun operating system +// file descriptors. +type noopStdinFile struct { + noopStdioFile +} + +// Read implements the same method as documented on sys.File +func (noopStdinFile) Read([]byte) (int, experimentalsys.Errno) { + return 0, 0 // Always EOF +} + +// Poll implements the same method as documented on fsapi.File +func (noopStdinFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { + if flag != fsapi.POLLIN { + return false, experimentalsys.ENOTSUP + } + return true, 0 // always ready to read nothing +} + +// noopStdoutFile is a fs.ModeDevice file for use implementing FdStdout and +// FdStderr. +type noopStdoutFile struct { + noopStdioFile +} + +// Write implements the same method as documented on sys.File +func (noopStdoutFile) Write(buf []byte) (int, experimentalsys.Errno) { + return len(buf), 0 // same as io.Discard +} + +type noopStdioFile struct { + experimentalsys.UnimplementedFile +} + +// Stat implements the same method as documented on sys.File +func (noopStdioFile) Stat() (sys.Stat_t, experimentalsys.Errno) { + return sys.Stat_t{Mode: modeDevice, Nlink: 1}, 0 +} + +// IsDir implements the same method as documented on sys.File +func (noopStdioFile) IsDir() (bool, experimentalsys.Errno) { + return false, 0 +} + +// Close implements the same method as documented on sys.File +func (noopStdioFile) Close() (errno experimentalsys.Errno) { return } + +// IsNonblock implements the same method as documented on fsapi.File +func (noopStdioFile) IsNonblock() bool { + return false +} + +// SetNonblock implements the same method as documented on fsapi.File +func (noopStdioFile) SetNonblock(bool) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Poll implements the same method as documented on fsapi.File +func (noopStdioFile) Poll(fsapi.Pflag, int32) (ready bool, errno experimentalsys.Errno) { + return false, experimentalsys.ENOSYS +} + +func stdinFileEntry(r io.Reader) (*FileEntry, error) { + if r == nil { + return &FileEntry{Name: "stdin", IsPreopen: true, File: &noopStdinFile{}}, nil + } else if f, ok := r.(*os.File); ok { + if f, err := sysfs.NewStdioFile(true, f); err != nil { + return nil, err + } else { + return &FileEntry{Name: "stdin", IsPreopen: true, File: f}, nil + } + } else { + return &FileEntry{Name: "stdin", IsPreopen: true, File: &StdinFile{Reader: r}}, nil + } +} + +func stdioWriterFileEntry(name string, w io.Writer) (*FileEntry, error) { + if w == nil { + return &FileEntry{Name: name, IsPreopen: true, File: &noopStdoutFile{}}, nil + } else if f, ok := w.(*os.File); ok { + if f, err := sysfs.NewStdioFile(false, f); err != nil { + return nil, err + } else { + return &FileEntry{Name: name, IsPreopen: true, File: f}, nil + } + } else { + return &FileEntry{Name: name, IsPreopen: true, File: &writerFile{w: w}}, nil + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sys/sys.go b/vendor/github.com/tetratelabs/wazero/internal/sys/sys.go new file mode 100644 index 00000000000..12279ee495e --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sys/sys.go @@ -0,0 +1,228 @@ +package sys + +import ( + "errors" + "fmt" + "io" + "net" + "time" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/platform" + "github.com/tetratelabs/wazero/sys" +) + +// Context holds module-scoped system resources currently only supported by +// built-in host functions. +type Context struct { + args, environ [][]byte + argsSize, environSize uint32 + + walltime sys.Walltime + walltimeResolution sys.ClockResolution + nanotime sys.Nanotime + nanotimeResolution sys.ClockResolution + nanosleep sys.Nanosleep + osyield sys.Osyield + randSource io.Reader + fsc FSContext +} + +// Args is like os.Args and defaults to nil. +// +// Note: The count will never be more than math.MaxUint32. +// See wazero.ModuleConfig WithArgs +func (c *Context) Args() [][]byte { + return c.args +} + +// ArgsSize is the size to encode Args as Null-terminated strings. +// +// Note: To get the size without null-terminators, subtract the length of Args from this value. +// See wazero.ModuleConfig WithArgs +// See https://en.wikipedia.org/wiki/Null-terminated_string +func (c *Context) ArgsSize() uint32 { + return c.argsSize +} + +// Environ are "key=value" entries like os.Environ and default to nil. +// +// Note: The count will never be more than math.MaxUint32. +// See wazero.ModuleConfig WithEnv +func (c *Context) Environ() [][]byte { + return c.environ +} + +// EnvironSize is the size to encode Environ as Null-terminated strings. +// +// Note: To get the size without null-terminators, subtract the length of Environ from this value. +// See wazero.ModuleConfig WithEnv +// See https://en.wikipedia.org/wiki/Null-terminated_string +func (c *Context) EnvironSize() uint32 { + return c.environSize +} + +// Walltime implements platform.Walltime. +func (c *Context) Walltime() (sec int64, nsec int32) { + return c.walltime() +} + +// WalltimeNanos returns platform.Walltime as epoch nanoseconds. +func (c *Context) WalltimeNanos() int64 { + sec, nsec := c.Walltime() + return (sec * time.Second.Nanoseconds()) + int64(nsec) +} + +// WalltimeResolution returns resolution of Walltime. +func (c *Context) WalltimeResolution() sys.ClockResolution { + return c.walltimeResolution +} + +// Nanotime implements sys.Nanotime. +func (c *Context) Nanotime() int64 { + return c.nanotime() +} + +// NanotimeResolution returns resolution of Nanotime. +func (c *Context) NanotimeResolution() sys.ClockResolution { + return c.nanotimeResolution +} + +// Nanosleep implements sys.Nanosleep. +func (c *Context) Nanosleep(ns int64) { + c.nanosleep(ns) +} + +// Osyield implements sys.Osyield. +func (c *Context) Osyield() { + c.osyield() +} + +// FS returns the possibly empty (UnimplementedFS) file system context. +func (c *Context) FS() *FSContext { + return &c.fsc +} + +// RandSource is a source of random bytes and defaults to a deterministic source. +// see wazero.ModuleConfig WithRandSource +func (c *Context) RandSource() io.Reader { + return c.randSource +} + +// DefaultContext returns Context with no values set except a possible nil +// sys.FS. +// +// Note: This is only used for testing. +func DefaultContext(fs experimentalsys.FS) *Context { + if sysCtx, err := NewContext(0, nil, nil, nil, nil, nil, nil, nil, 0, nil, 0, nil, nil, []experimentalsys.FS{fs}, []string{""}, nil); err != nil { + panic(fmt.Errorf("BUG: DefaultContext should never error: %w", err)) + } else { + return sysCtx + } +} + +// NewContext is a factory function which helps avoid needing to know defaults or exporting all fields. +// Note: max is exposed for testing. max is only used for env/args validation. +func NewContext( + max uint32, + args, environ [][]byte, + stdin io.Reader, + stdout, stderr io.Writer, + randSource io.Reader, + walltime sys.Walltime, + walltimeResolution sys.ClockResolution, + nanotime sys.Nanotime, + nanotimeResolution sys.ClockResolution, + nanosleep sys.Nanosleep, + osyield sys.Osyield, + fs []experimentalsys.FS, guestPaths []string, + tcpListeners []*net.TCPListener, +) (sysCtx *Context, err error) { + sysCtx = &Context{args: args, environ: environ} + + if sysCtx.argsSize, err = nullTerminatedByteCount(max, args); err != nil { + return nil, fmt.Errorf("args invalid: %w", err) + } + + if sysCtx.environSize, err = nullTerminatedByteCount(max, environ); err != nil { + return nil, fmt.Errorf("environ invalid: %w", err) + } + + if randSource == nil { + sysCtx.randSource = platform.NewFakeRandSource() + } else { + sysCtx.randSource = randSource + } + + if walltime != nil { + if clockResolutionInvalid(walltimeResolution) { + return nil, fmt.Errorf("invalid Walltime resolution: %d", walltimeResolution) + } + sysCtx.walltime = walltime + sysCtx.walltimeResolution = walltimeResolution + } else { + sysCtx.walltime = platform.NewFakeWalltime() + sysCtx.walltimeResolution = sys.ClockResolution(time.Microsecond.Nanoseconds()) + } + + if nanotime != nil { + if clockResolutionInvalid(nanotimeResolution) { + return nil, fmt.Errorf("invalid Nanotime resolution: %d", nanotimeResolution) + } + sysCtx.nanotime = nanotime + sysCtx.nanotimeResolution = nanotimeResolution + } else { + sysCtx.nanotime = platform.NewFakeNanotime() + sysCtx.nanotimeResolution = sys.ClockResolution(time.Nanosecond) + } + + if nanosleep != nil { + sysCtx.nanosleep = nanosleep + } else { + sysCtx.nanosleep = platform.FakeNanosleep + } + + if osyield != nil { + sysCtx.osyield = osyield + } else { + sysCtx.osyield = platform.FakeOsyield + } + + err = sysCtx.InitFSContext(stdin, stdout, stderr, fs, guestPaths, tcpListeners) + + return +} + +// clockResolutionInvalid returns true if the value stored isn't reasonable. +func clockResolutionInvalid(resolution sys.ClockResolution) bool { + return resolution < 1 || resolution > sys.ClockResolution(time.Hour.Nanoseconds()) +} + +// nullTerminatedByteCount ensures the count or Nul-terminated length of the elements doesn't exceed max, and that no +// element includes the nul character. +func nullTerminatedByteCount(max uint32, elements [][]byte) (uint32, error) { + count := uint32(len(elements)) + if count > max { + return 0, errors.New("exceeds maximum count") + } + + // The buffer size is the total size including null terminators. The null terminator count == value count, sum + // count with each value length. This works because in Go, the length of a string is the same as its byte count. + bufSize, maxSize := uint64(count), uint64(max) // uint64 to allow summing without overflow + for _, e := range elements { + // As this is null-terminated, We have to validate there are no null characters in the string. + for _, c := range e { + if c == 0 { + return 0, errors.New("contains NUL character") + } + } + + nextSize := bufSize + uint64(len(e)) + if nextSize > maxSize { + return 0, errors.New("exceeds maximum size") + } + bufSize = nextSize + + } + return uint32(bufSize), nil +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/adapter.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/adapter.go new file mode 100644 index 00000000000..51a9a548046 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/adapter.go @@ -0,0 +1,105 @@ +package sysfs + +import ( + "fmt" + "io/fs" + "path" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/sys" +) + +type AdaptFS struct { + FS fs.FS +} + +// String implements fmt.Stringer +func (a *AdaptFS) String() string { + return fmt.Sprintf("%v", a.FS) +} + +// OpenFile implements the same method as documented on sys.FS +func (a *AdaptFS) OpenFile(path string, flag experimentalsys.Oflag, perm fs.FileMode) (experimentalsys.File, experimentalsys.Errno) { + return OpenFSFile(a.FS, cleanPath(path), flag, perm) +} + +// Lstat implements the same method as documented on sys.FS +func (a *AdaptFS) Lstat(path string) (sys.Stat_t, experimentalsys.Errno) { + // At this time, we make the assumption sys.FS instances do not support + // symbolic links, therefore Lstat is the same as Stat. This is obviously + // not true, but until FS.FS has a solid story for how to handle symlinks, + // we are better off not making a decision that would be difficult to + // revert later on. + // + // For further discussions on the topic, see: + // https://github.com/golang/go/issues/49580 + return a.Stat(path) +} + +// Stat implements the same method as documented on sys.FS +func (a *AdaptFS) Stat(path string) (sys.Stat_t, experimentalsys.Errno) { + f, errno := a.OpenFile(path, experimentalsys.O_RDONLY, 0) + if errno != 0 { + return sys.Stat_t{}, errno + } + defer f.Close() + return f.Stat() +} + +// Readlink implements the same method as documented on sys.FS +func (a *AdaptFS) Readlink(string) (string, experimentalsys.Errno) { + return "", experimentalsys.ENOSYS +} + +// Mkdir implements the same method as documented on sys.FS +func (a *AdaptFS) Mkdir(string, fs.FileMode) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Chmod implements the same method as documented on sys.FS +func (a *AdaptFS) Chmod(string, fs.FileMode) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Rename implements the same method as documented on sys.FS +func (a *AdaptFS) Rename(string, string) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Rmdir implements the same method as documented on sys.FS +func (a *AdaptFS) Rmdir(string) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Link implements the same method as documented on sys.FS +func (a *AdaptFS) Link(string, string) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Symlink implements the same method as documented on sys.FS +func (a *AdaptFS) Symlink(string, string) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Unlink implements the same method as documented on sys.FS +func (a *AdaptFS) Unlink(string) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Utimens implements the same method as documented on sys.FS +func (a *AdaptFS) Utimens(string, int64, int64) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +func cleanPath(name string) string { + if len(name) == 0 { + return name + } + // fs.ValidFile cannot be rooted (start with '/') + cleaned := name + if name[0] == '/' { + cleaned = name[1:] + } + cleaned = path.Clean(cleaned) // e.g. "sub/." -> "sub" + return cleaned +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/datasync_linux.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/datasync_linux.go new file mode 100644 index 00000000000..792d0db2e96 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/datasync_linux.go @@ -0,0 +1,12 @@ +package sysfs + +import ( + "os" + "syscall" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +func datasync(f *os.File) sys.Errno { + return sys.UnwrapOSError(syscall.Fdatasync(int(f.Fd()))) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/datasync_unsupported.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/datasync_unsupported.go new file mode 100644 index 00000000000..aa05719be61 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/datasync_unsupported.go @@ -0,0 +1,14 @@ +//go:build !linux + +package sysfs + +import ( + "os" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +func datasync(f *os.File) sys.Errno { + // Attempt to sync everything, even if we only need to sync the data. + return fsync(f) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/dir.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/dir.go new file mode 100644 index 00000000000..f9823287cf3 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/dir.go @@ -0,0 +1,24 @@ +package sysfs + +import ( + "io" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +func adjustReaddirErr(f sys.File, isClosed bool, err error) sys.Errno { + if err == io.EOF { + return 0 // e.g. Readdir on darwin returns io.EOF, but linux doesn't. + } else if errno := sys.UnwrapOSError(err); errno != 0 { + errno = dirError(f, isClosed, errno) + // Comply with errors allowed on sys.File Readdir + switch errno { + case sys.EINVAL: // os.File Readdir can return this + return sys.EBADF + case sys.ENOTDIR: // dirError can return this + return sys.EBADF + } + return errno + } + return 0 +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/dirfs.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/dirfs.go new file mode 100644 index 00000000000..aba7a65e243 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/dirfs.go @@ -0,0 +1,137 @@ +package sysfs + +import ( + "io/fs" + "os" + "path" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/platform" + "github.com/tetratelabs/wazero/sys" +) + +func DirFS(dir string) experimentalsys.FS { + return &dirFS{ + dir: dir, + cleanedDir: ensureTrailingPathSeparator(dir), + } +} + +func ensureTrailingPathSeparator(dir string) string { + if !os.IsPathSeparator(dir[len(dir)-1]) { + return dir + string(os.PathSeparator) + } + return dir +} + +// dirFS is not exported because the input fields must be maintained together. +// This is likely why os.DirFS doesn't, either! +type dirFS struct { + experimentalsys.UnimplementedFS + + dir string + // cleanedDir is for easier OS-specific concatenation, as it always has + // a trailing path separator. + cleanedDir string +} + +// String implements fmt.Stringer +func (d *dirFS) String() string { + return d.dir +} + +// OpenFile implements the same method as documented on sys.FS +func (d *dirFS) OpenFile(path string, flag experimentalsys.Oflag, perm fs.FileMode) (experimentalsys.File, experimentalsys.Errno) { + return OpenOSFile(d.join(path), flag, perm) +} + +// Lstat implements the same method as documented on sys.FS +func (d *dirFS) Lstat(path string) (sys.Stat_t, experimentalsys.Errno) { + return lstat(d.join(path)) +} + +// Stat implements the same method as documented on sys.FS +func (d *dirFS) Stat(path string) (sys.Stat_t, experimentalsys.Errno) { + return stat(d.join(path)) +} + +// Mkdir implements the same method as documented on sys.FS +func (d *dirFS) Mkdir(path string, perm fs.FileMode) (errno experimentalsys.Errno) { + err := os.Mkdir(d.join(path), perm) + if errno = experimentalsys.UnwrapOSError(err); errno == experimentalsys.ENOTDIR { + errno = experimentalsys.ENOENT + } + return +} + +// Chmod implements the same method as documented on sys.FS +func (d *dirFS) Chmod(path string, perm fs.FileMode) experimentalsys.Errno { + err := os.Chmod(d.join(path), perm) + return experimentalsys.UnwrapOSError(err) +} + +// Rename implements the same method as documented on sys.FS +func (d *dirFS) Rename(from, to string) experimentalsys.Errno { + from, to = d.join(from), d.join(to) + return rename(from, to) +} + +// Rmdir implements the same method as documented on sys.FS +func (d *dirFS) Rmdir(path string) experimentalsys.Errno { + return rmdir(d.join(path)) +} + +// Unlink implements the same method as documented on sys.FS +func (d *dirFS) Unlink(path string) (err experimentalsys.Errno) { + return unlink(d.join(path)) +} + +// Link implements the same method as documented on sys.FS +func (d *dirFS) Link(oldName, newName string) experimentalsys.Errno { + err := os.Link(d.join(oldName), d.join(newName)) + return experimentalsys.UnwrapOSError(err) +} + +// Symlink implements the same method as documented on sys.FS +func (d *dirFS) Symlink(oldName, link string) experimentalsys.Errno { + // Creating a symlink with an absolute path string fails with a "not permitted" error. + // https://github.com/WebAssembly/wasi-filesystem/blob/v0.2.0/path-resolution.md#symlinks + if path.IsAbs(oldName) { + return experimentalsys.EPERM + } + // Note: do not resolve `oldName` relative to this dirFS. The link result is always resolved + // when dereference the `link` on its usage (e.g. readlink, read, etc). + // https://github.com/bytecodealliance/cap-std/blob/v1.0.4/cap-std/src/fs/dir.rs#L404-L409 + err := os.Symlink(oldName, d.join(link)) + return experimentalsys.UnwrapOSError(err) +} + +// Readlink implements the same method as documented on sys.FS +func (d *dirFS) Readlink(path string) (string, experimentalsys.Errno) { + // Note: do not use syscall.Readlink as that causes race on Windows. + // In any case, syscall.Readlink does almost the same logic as os.Readlink. + dst, err := os.Readlink(d.join(path)) + if err != nil { + return "", experimentalsys.UnwrapOSError(err) + } + return platform.ToPosixPath(dst), 0 +} + +// Utimens implements the same method as documented on sys.FS +func (d *dirFS) Utimens(path string, atim, mtim int64) experimentalsys.Errno { + return utimens(d.join(path), atim, mtim) +} + +func (d *dirFS) join(path string) string { + switch path { + case "", ".", "/": + if d.cleanedDir == "/" { + return "/" + } + // cleanedDir includes an unnecessary delimiter for the root path. + return d.cleanedDir[:len(d.cleanedDir)-1] + } + // TODO: Enforce similar to safefilepath.FromFS(path), but be careful as + // relative path inputs are allowed. e.g. dir or path == ../ + return d.cleanedDir + path +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/file.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/file.go new file mode 100644 index 00000000000..1b6d5f3dec8 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/file.go @@ -0,0 +1,523 @@ +package sysfs + +import ( + "io" + "io/fs" + "os" + "time" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" + "github.com/tetratelabs/wazero/sys" +) + +func NewStdioFile(stdin bool, f fs.File) (fsapi.File, error) { + // Return constant stat, which has fake times, but keep the underlying + // file mode. Fake times are needed to pass wasi-testsuite. + // https://github.com/WebAssembly/wasi-testsuite/blob/af57727/tests/rust/src/bin/fd_filestat_get.rs#L1-L19 + var mode fs.FileMode + if st, err := f.Stat(); err != nil { + return nil, err + } else { + mode = st.Mode() + } + var flag experimentalsys.Oflag + if stdin { + flag = experimentalsys.O_RDONLY + } else { + flag = experimentalsys.O_WRONLY + } + var file fsapi.File + if of, ok := f.(*os.File); ok { + // This is ok because functions that need path aren't used by stdioFile + file = newOsFile("", flag, 0, of) + } else { + file = &fsFile{file: f} + } + return &stdioFile{File: file, st: sys.Stat_t{Mode: mode, Nlink: 1}}, nil +} + +func OpenFile(path string, flag experimentalsys.Oflag, perm fs.FileMode) (*os.File, experimentalsys.Errno) { + return openFile(path, flag, perm) +} + +func OpenOSFile(path string, flag experimentalsys.Oflag, perm fs.FileMode) (experimentalsys.File, experimentalsys.Errno) { + f, errno := OpenFile(path, flag, perm) + if errno != 0 { + return nil, errno + } + return newOsFile(path, flag, perm, f), 0 +} + +func OpenFSFile(fs fs.FS, path string, flag experimentalsys.Oflag, perm fs.FileMode) (experimentalsys.File, experimentalsys.Errno) { + if flag&experimentalsys.O_DIRECTORY != 0 && flag&(experimentalsys.O_WRONLY|experimentalsys.O_RDWR) != 0 { + return nil, experimentalsys.EISDIR // invalid to open a directory writeable + } + f, err := fs.Open(path) + if errno := experimentalsys.UnwrapOSError(err); errno != 0 { + return nil, errno + } + // Don't return an os.File because the path is not absolute. osFile needs + // the path to be real and certain FS.File impls are subrooted. + return &fsFile{fs: fs, name: path, file: f}, 0 +} + +type stdioFile struct { + fsapi.File + st sys.Stat_t +} + +// SetAppend implements File.SetAppend +func (f *stdioFile) SetAppend(bool) experimentalsys.Errno { + // Ignore for stdio. + return 0 +} + +// IsAppend implements File.SetAppend +func (f *stdioFile) IsAppend() bool { + return true +} + +// Stat implements File.Stat +func (f *stdioFile) Stat() (sys.Stat_t, experimentalsys.Errno) { + return f.st, 0 +} + +// Close implements File.Close +func (f *stdioFile) Close() experimentalsys.Errno { + return 0 +} + +// fsFile is used for wrapped fs.File, like os.Stdin or any fs.File +// implementation. Notably, this does not have access to the full file path. +// so certain operations can't be supported, such as inode lookups on Windows. +type fsFile struct { + experimentalsys.UnimplementedFile + + // fs is the file-system that opened the file, or nil when wrapped for + // pre-opens like stdio. + fs fs.FS + + // name is what was used in fs for Open, so it may not be the actual path. + name string + + // file is always set, possibly an os.File like os.Stdin. + file fs.File + + // reopenDir is true if reopen should be called before Readdir. This flag + // is deferred until Readdir to prevent redundant rewinds. This could + // happen if Seek(0) was called twice, or if in Windows, Seek(0) was called + // before Readdir. + reopenDir bool + + // closed is true when closed was called. This ensures proper sys.EBADF + closed bool + + // cachedStat includes fields that won't change while a file is open. + cachedSt *cachedStat +} + +type cachedStat struct { + // dev is the same as sys.Stat_t Dev. + dev uint64 + + // dev is the same as sys.Stat_t Ino. + ino sys.Inode + + // isDir is sys.Stat_t Mode masked with fs.ModeDir + isDir bool +} + +// cachedStat returns the cacheable parts of sys.Stat_t or an error if they +// couldn't be retrieved. +func (f *fsFile) cachedStat() (dev uint64, ino sys.Inode, isDir bool, errno experimentalsys.Errno) { + if f.cachedSt == nil { + if _, errno = f.Stat(); errno != 0 { + return + } + } + return f.cachedSt.dev, f.cachedSt.ino, f.cachedSt.isDir, 0 +} + +// Dev implements the same method as documented on sys.File +func (f *fsFile) Dev() (uint64, experimentalsys.Errno) { + dev, _, _, errno := f.cachedStat() + return dev, errno +} + +// Ino implements the same method as documented on sys.File +func (f *fsFile) Ino() (sys.Inode, experimentalsys.Errno) { + _, ino, _, errno := f.cachedStat() + return ino, errno +} + +// IsDir implements the same method as documented on sys.File +func (f *fsFile) IsDir() (bool, experimentalsys.Errno) { + _, _, isDir, errno := f.cachedStat() + return isDir, errno +} + +// IsAppend implements the same method as documented on sys.File +func (f *fsFile) IsAppend() bool { + return false +} + +// SetAppend implements the same method as documented on sys.File +func (f *fsFile) SetAppend(bool) (errno experimentalsys.Errno) { + return fileError(f, f.closed, experimentalsys.ENOSYS) +} + +// Stat implements the same method as documented on sys.File +func (f *fsFile) Stat() (sys.Stat_t, experimentalsys.Errno) { + if f.closed { + return sys.Stat_t{}, experimentalsys.EBADF + } + + st, errno := statFile(f.file) + switch errno { + case 0: + f.cachedSt = &cachedStat{dev: st.Dev, ino: st.Ino, isDir: st.Mode&fs.ModeDir == fs.ModeDir} + case experimentalsys.EIO: + errno = experimentalsys.EBADF + } + return st, errno +} + +// Read implements the same method as documented on sys.File +func (f *fsFile) Read(buf []byte) (n int, errno experimentalsys.Errno) { + if n, errno = read(f.file, buf); errno != 0 { + // Defer validation overhead until we've already had an error. + errno = fileError(f, f.closed, errno) + } + return +} + +// Pread implements the same method as documented on sys.File +func (f *fsFile) Pread(buf []byte, off int64) (n int, errno experimentalsys.Errno) { + if ra, ok := f.file.(io.ReaderAt); ok { + if n, errno = pread(ra, buf, off); errno != 0 { + // Defer validation overhead until we've already had an error. + errno = fileError(f, f.closed, errno) + } + return + } + + // See /RATIONALE.md "fd_pread: io.Seeker fallback when io.ReaderAt is not supported" + if rs, ok := f.file.(io.ReadSeeker); ok { + // Determine the current position in the file, as we need to revert it. + currentOffset, err := rs.Seek(0, io.SeekCurrent) + if err != nil { + return 0, fileError(f, f.closed, experimentalsys.UnwrapOSError(err)) + } + + // Put the read position back when complete. + defer func() { _, _ = rs.Seek(currentOffset, io.SeekStart) }() + + // If the current offset isn't in sync with this reader, move it. + if off != currentOffset { + if _, err = rs.Seek(off, io.SeekStart); err != nil { + return 0, fileError(f, f.closed, experimentalsys.UnwrapOSError(err)) + } + } + + n, err = rs.Read(buf) + if errno = experimentalsys.UnwrapOSError(err); errno != 0 { + // Defer validation overhead until we've already had an error. + errno = fileError(f, f.closed, errno) + } + } else { + errno = experimentalsys.ENOSYS // unsupported + } + return +} + +// Seek implements the same method as documented on sys.File +func (f *fsFile) Seek(offset int64, whence int) (newOffset int64, errno experimentalsys.Errno) { + // If this is a directory, and we're attempting to seek to position zero, + // we have to re-open the file to ensure the directory state is reset. + var isDir bool + if offset == 0 && whence == io.SeekStart { + if isDir, errno = f.IsDir(); errno == 0 && isDir { + f.reopenDir = true + return + } + } + + if s, ok := f.file.(io.Seeker); ok { + if newOffset, errno = seek(s, offset, whence); errno != 0 { + // Defer validation overhead until we've already had an error. + errno = fileError(f, f.closed, errno) + } + } else { + errno = experimentalsys.ENOSYS // unsupported + } + return +} + +// Readdir implements the same method as documented on sys.File +// +// Notably, this uses readdirFile or fs.ReadDirFile if available. This does not +// return inodes on windows. +func (f *fsFile) Readdir(n int) (dirents []experimentalsys.Dirent, errno experimentalsys.Errno) { + // Windows lets you Readdir after close, FS.File also may not implement + // close in a meaningful way. read our closed field to return consistent + // results. + if f.closed { + errno = experimentalsys.EBADF + return + } + + if f.reopenDir { // re-open the directory if needed. + f.reopenDir = false + if errno = adjustReaddirErr(f, f.closed, f.rewindDir()); errno != 0 { + return + } + } + + if of, ok := f.file.(readdirFile); ok { + // We can't use f.name here because it is the path up to the sys.FS, + // not necessarily the real path. For this reason, Windows may not be + // able to populate inodes. However, Darwin and Linux will. + if dirents, errno = readdir(of, "", n); errno != 0 { + errno = adjustReaddirErr(f, f.closed, errno) + } + return + } + + // Try with FS.ReadDirFile which is available on api.FS implementations + // like embed:FS. + if rdf, ok := f.file.(fs.ReadDirFile); ok { + entries, e := rdf.ReadDir(n) + if errno = adjustReaddirErr(f, f.closed, e); errno != 0 { + return + } + dirents = make([]experimentalsys.Dirent, 0, len(entries)) + for _, e := range entries { + // By default, we don't attempt to read inode data + dirents = append(dirents, experimentalsys.Dirent{Name: e.Name(), Type: e.Type()}) + } + } else { + errno = experimentalsys.EBADF // not a directory + } + return +} + +// Write implements the same method as documented on sys.File. +func (f *fsFile) Write(buf []byte) (n int, errno experimentalsys.Errno) { + if w, ok := f.file.(io.Writer); ok { + if n, errno = write(w, buf); errno != 0 { + // Defer validation overhead until we've already had an error. + errno = fileError(f, f.closed, errno) + } + } else { + errno = experimentalsys.ENOSYS // unsupported + } + return +} + +// Pwrite implements the same method as documented on sys.File. +func (f *fsFile) Pwrite(buf []byte, off int64) (n int, errno experimentalsys.Errno) { + if wa, ok := f.file.(io.WriterAt); ok { + if n, errno = pwrite(wa, buf, off); errno != 0 { + // Defer validation overhead until we've already had an error. + errno = fileError(f, f.closed, errno) + } + } else { + errno = experimentalsys.ENOSYS // unsupported + } + return +} + +// Close implements the same method as documented on sys.File. +func (f *fsFile) Close() experimentalsys.Errno { + if f.closed { + return 0 + } + f.closed = true + return f.close() +} + +func (f *fsFile) close() experimentalsys.Errno { + return experimentalsys.UnwrapOSError(f.file.Close()) +} + +// IsNonblock implements the same method as documented on fsapi.File +func (f *fsFile) IsNonblock() bool { + return false +} + +// SetNonblock implements the same method as documented on fsapi.File +func (f *fsFile) SetNonblock(bool) experimentalsys.Errno { + return experimentalsys.ENOSYS +} + +// Poll implements the same method as documented on fsapi.File +func (f *fsFile) Poll(fsapi.Pflag, int32) (ready bool, errno experimentalsys.Errno) { + return false, experimentalsys.ENOSYS +} + +// dirError is used for commands that work against a directory, but not a file. +func dirError(f experimentalsys.File, isClosed bool, errno experimentalsys.Errno) experimentalsys.Errno { + if vErrno := validate(f, isClosed, false, true); vErrno != 0 { + return vErrno + } + return errno +} + +// fileError is used for commands that work against a file, but not a directory. +func fileError(f experimentalsys.File, isClosed bool, errno experimentalsys.Errno) experimentalsys.Errno { + if vErrno := validate(f, isClosed, true, false); vErrno != 0 { + return vErrno + } + return errno +} + +// validate is used to making syscalls which will fail. +func validate(f experimentalsys.File, isClosed, wantFile, wantDir bool) experimentalsys.Errno { + if isClosed { + return experimentalsys.EBADF + } + + isDir, errno := f.IsDir() + if errno != 0 { + return errno + } + + if wantFile && isDir { + return experimentalsys.EISDIR + } else if wantDir && !isDir { + return experimentalsys.ENOTDIR + } + return 0 +} + +func read(r io.Reader, buf []byte) (n int, errno experimentalsys.Errno) { + if len(buf) == 0 { + return 0, 0 // less overhead on zero-length reads. + } + + n, err := r.Read(buf) + return n, experimentalsys.UnwrapOSError(err) +} + +func pread(ra io.ReaderAt, buf []byte, off int64) (n int, errno experimentalsys.Errno) { + if len(buf) == 0 { + return 0, 0 // less overhead on zero-length reads. + } + + n, err := ra.ReadAt(buf, off) + return n, experimentalsys.UnwrapOSError(err) +} + +func seek(s io.Seeker, offset int64, whence int) (int64, experimentalsys.Errno) { + if uint(whence) > io.SeekEnd { + return 0, experimentalsys.EINVAL // negative or exceeds the largest valid whence + } + + newOffset, err := s.Seek(offset, whence) + return newOffset, experimentalsys.UnwrapOSError(err) +} + +func (f *fsFile) rewindDir() experimentalsys.Errno { + // Reopen the directory to rewind it. + file, err := f.fs.Open(f.name) + if err != nil { + return experimentalsys.UnwrapOSError(err) + } + fi, err := file.Stat() + if err != nil { + return experimentalsys.UnwrapOSError(err) + } + // Can't check if it's still the same file, + // but is it still a directory, at least? + if !fi.IsDir() { + return experimentalsys.ENOTDIR + } + // Only update f on success. + _ = f.file.Close() + f.file = file + return 0 +} + +// readdirFile allows masking the `Readdir` function on os.File. +type readdirFile interface { + Readdir(n int) ([]fs.FileInfo, error) +} + +// readdir uses readdirFile.Readdir, special casing windows when path !="". +func readdir(f readdirFile, path string, n int) (dirents []experimentalsys.Dirent, errno experimentalsys.Errno) { + fis, e := f.Readdir(n) + if errno = experimentalsys.UnwrapOSError(e); errno != 0 { + return + } + + dirents = make([]experimentalsys.Dirent, 0, len(fis)) + + // linux/darwin won't have to fan out to lstat, but windows will. + var ino sys.Inode + for fi := range fis { + t := fis[fi] + // inoFromFileInfo is more efficient than sys.NewStat_t, as it gets the + // inode without allocating an instance and filling other fields. + if ino, errno = inoFromFileInfo(path, t); errno != 0 { + return + } + dirents = append(dirents, experimentalsys.Dirent{Name: t.Name(), Ino: ino, Type: t.Mode().Type()}) + } + return +} + +func write(w io.Writer, buf []byte) (n int, errno experimentalsys.Errno) { + if len(buf) == 0 { + return 0, 0 // less overhead on zero-length writes. + } + + n, err := w.Write(buf) + return n, experimentalsys.UnwrapOSError(err) +} + +func pwrite(w io.WriterAt, buf []byte, off int64) (n int, errno experimentalsys.Errno) { + if len(buf) == 0 { + return 0, 0 // less overhead on zero-length writes. + } + + n, err := w.WriteAt(buf, off) + return n, experimentalsys.UnwrapOSError(err) +} + +func chtimes(path string, atim, mtim int64) (errno experimentalsys.Errno) { //nolint:unused + // When both inputs are omitted, there is nothing to change. + if atim == experimentalsys.UTIME_OMIT && mtim == experimentalsys.UTIME_OMIT { + return + } + + // UTIME_OMIT is expensive until progress is made in Go, as it requires a + // stat to read-back the value to re-apply. + // - https://github.com/golang/go/issues/32558. + // - https://go-review.googlesource.com/c/go/+/219638 (unmerged) + var st sys.Stat_t + if atim == experimentalsys.UTIME_OMIT || mtim == experimentalsys.UTIME_OMIT { + if st, errno = stat(path); errno != 0 { + return + } + } + + var atime, mtime time.Time + if atim == experimentalsys.UTIME_OMIT { + atime = epochNanosToTime(st.Atim) + mtime = epochNanosToTime(mtim) + } else if mtim == experimentalsys.UTIME_OMIT { + atime = epochNanosToTime(atim) + mtime = epochNanosToTime(st.Mtim) + } else { + atime = epochNanosToTime(atim) + mtime = epochNanosToTime(mtim) + } + return experimentalsys.UnwrapOSError(os.Chtimes(path, atime, mtime)) +} + +func epochNanosToTime(epochNanos int64) time.Time { //nolint:unused + seconds := epochNanos / 1e9 + nanos := epochNanos % 1e9 + return time.Unix(seconds, nanos) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/file_unix.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/file_unix.go new file mode 100644 index 00000000000..92eaf354231 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/file_unix.go @@ -0,0 +1,39 @@ +//go:build unix + +package sysfs + +import ( + "syscall" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +const ( + nonBlockingFileReadSupported = true + nonBlockingFileWriteSupported = true +) + +func rmdir(path string) sys.Errno { + err := syscall.Rmdir(path) + return sys.UnwrapOSError(err) +} + +// readFd exposes syscall.Read. +func readFd(fd uintptr, buf []byte) (int, sys.Errno) { + if len(buf) == 0 { + return 0, 0 // Short-circuit 0-len reads. + } + n, err := syscall.Read(int(fd), buf) + errno := sys.UnwrapOSError(err) + return n, errno +} + +// writeFd exposes syscall.Write. +func writeFd(fd uintptr, buf []byte) (int, sys.Errno) { + if len(buf) == 0 { + return 0, 0 // Short-circuit 0-len writes. + } + n, err := syscall.Write(int(fd), buf) + errno := sys.UnwrapOSError(err) + return n, errno +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/file_unsupported.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/file_unsupported.go new file mode 100644 index 00000000000..d75cb384154 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/file_unsupported.go @@ -0,0 +1,28 @@ +//go:build !(unix || windows) + +package sysfs + +import ( + "os" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +const ( + nonBlockingFileReadSupported = false + nonBlockingFileWriteSupported = false +) + +func rmdir(path string) sys.Errno { + return sys.UnwrapOSError(os.Remove(path)) +} + +// readFd returns ENOSYS on unsupported platforms. +func readFd(fd uintptr, buf []byte) (int, sys.Errno) { + return -1, sys.ENOSYS +} + +// writeFd returns ENOSYS on unsupported platforms. +func writeFd(fd uintptr, buf []byte) (int, sys.Errno) { + return -1, sys.ENOSYS +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/file_windows.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/file_windows.go new file mode 100644 index 00000000000..2048d0db9ed --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/file_windows.go @@ -0,0 +1,133 @@ +package sysfs + +import ( + "errors" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +const ( + nonBlockingFileReadSupported = true + nonBlockingFileWriteSupported = false +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + // procPeekNamedPipe exposes PeekNamedPipe from kernel32. + procPeekNamedPipe = kernel32.NewProc("PeekNamedPipe") +) + +// readFd returns ENOSYS on unsupported platforms. +// +// PeekNamedPipe: https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-peeknamedpipe +// "GetFileType can assist in determining what device type the handle refers to. A console handle presents as FILE_TYPE_CHAR." +// https://learn.microsoft.com/en-us/windows/console/console-handles +func readFd(fd uintptr, buf []byte) (int, sys.Errno) { + handle := syscall.Handle(fd) + fileType, err := syscall.GetFileType(handle) + if err != nil { + return 0, sys.UnwrapOSError(err) + } + if fileType&syscall.FILE_TYPE_CHAR == 0 { + return -1, sys.ENOSYS + } + n, errno := peekNamedPipe(handle) + if errno == syscall.ERROR_BROKEN_PIPE { + return 0, 0 + } + if n == 0 { + return -1, sys.EAGAIN + } + un, err := syscall.Read(handle, buf[0:n]) + return un, sys.UnwrapOSError(err) +} + +func writeFd(uintptr, []byte) (int, sys.Errno) { + return -1, sys.ENOSYS +} + +func readSocket(h uintptr, buf []byte) (int, sys.Errno) { + // Poll the socket to ensure that we never perform a blocking/overlapped Read. + // + // When the socket is closed by the remote peer, wsaPoll will return n=1 and + // errno=0, and windows.ReadFile will return n=0 and errno=0 -- which indicates + // io.EOF. + if n, errno := wsaPoll( + []pollFd{newPollFd(h, _POLLIN, 0)}, 0); !errors.Is(errno, sys.Errno(0)) { + return 0, sys.UnwrapOSError(errno) + } else if n <= 0 { + return 0, sys.EAGAIN + } + + // Properly use overlapped result. + // + // If hFile was opened with FILE_FLAG_OVERLAPPED, the following conditions are in effect: + // - The lpOverlapped parameter must point to a valid and unique OVERLAPPED structure, + // otherwise the function can incorrectly report that the read operation is complete. + // - The lpNumberOfBytesRead parameter should be set to NULL. Use the GetOverlappedResult + // function to get the actual number of bytes read. If the hFile parameter is associated + // with an I/O completion port, you can also get the number of bytes read by calling the + // GetQueuedCompletionStatus function. + // + // We are currently skipping checking if hFile was opened with FILE_FLAG_OVERLAPPED but using + // both lpOverlapped and lpNumberOfBytesRead. + var overlapped windows.Overlapped + + // Create an event to wait on. + if hEvent, err := windows.CreateEvent(nil, 1, 0, nil); err != nil { + return 0, sys.UnwrapOSError(err) + } else { + overlapped.HEvent = windows.Handle(hEvent) + } + + var done uint32 + errno := windows.ReadFile(windows.Handle(h), buf, &done, &overlapped) + if errors.Is(errno, windows.ERROR_IO_PENDING) { + errno = windows.CancelIo(windows.Handle(h)) + if errno != nil { + return 0, sys.UnwrapOSError(errno) // This is a fatal error. CancelIo failed. + } + + errno = windows.GetOverlappedResult(windows.Handle(h), &overlapped, &done, true) // wait for I/O to complete(cancel or finish). Overwrite done and errno. + if errors.Is(errno, windows.ERROR_OPERATION_ABORTED) { + return int(done), sys.EAGAIN // This is one of the expected behavior, I/O was cancelled(completed) before finished. + } + } + + return int(done), sys.UnwrapOSError(errno) +} + +func writeSocket(fd uintptr, buf []byte) (int, sys.Errno) { + var done uint32 + var overlapped windows.Overlapped + errno := windows.WriteFile(windows.Handle(fd), buf, &done, &overlapped) + if errors.Is(errno, windows.ERROR_IO_PENDING) { + errno = syscall.EAGAIN + } + return int(done), sys.UnwrapOSError(errno) +} + +// peekNamedPipe partially exposes PeekNamedPipe from the Win32 API +// see https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-peeknamedpipe +func peekNamedPipe(handle syscall.Handle) (uint32, syscall.Errno) { + var totalBytesAvail uint32 + totalBytesPtr := unsafe.Pointer(&totalBytesAvail) + _, _, errno := syscall.SyscallN( + procPeekNamedPipe.Addr(), + uintptr(handle), // [in] HANDLE hNamedPipe, + 0, // [out, optional] LPVOID lpBuffer, + 0, // [in] DWORD nBufferSize, + 0, // [out, optional] LPDWORD lpBytesRead + uintptr(totalBytesPtr), // [out, optional] LPDWORD lpTotalBytesAvail, + 0) // [out, optional] LPDWORD lpBytesLeftThisMessage + return totalBytesAvail, errno +} + +func rmdir(path string) sys.Errno { + err := syscall.Rmdir(path) + return sys.UnwrapOSError(err) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/futimens.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/futimens.go new file mode 100644 index 00000000000..7a647bff3aa --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/futimens.go @@ -0,0 +1,29 @@ +//go:build linux || darwin + +package sysfs + +import ( + "syscall" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +func timesToTimespecs(atim int64, mtim int64) (times *[2]syscall.Timespec) { + // When both inputs are omitted, there is nothing to change. + if atim == sys.UTIME_OMIT && mtim == sys.UTIME_OMIT { + return + } + + times = &[2]syscall.Timespec{} + if atim == sys.UTIME_OMIT { + times[0] = syscall.Timespec{Nsec: _UTIME_OMIT} + times[1] = syscall.NsecToTimespec(mtim) + } else if mtim == sys.UTIME_OMIT { + times[0] = syscall.NsecToTimespec(atim) + times[1] = syscall.Timespec{Nsec: _UTIME_OMIT} + } else { + times[0] = syscall.NsecToTimespec(atim) + times[1] = syscall.NsecToTimespec(mtim) + } + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/futimens_darwin.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/futimens_darwin.go new file mode 100644 index 00000000000..d97fc3531db --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/futimens_darwin.go @@ -0,0 +1,42 @@ +package sysfs + +import ( + "syscall" + "unsafe" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" +) + +const _UTIME_OMIT = -2 + +func utimens(path string, atim, mtim int64) experimentalsys.Errno { + times := timesToTimespecs(atim, mtim) + if times == nil { + return 0 + } + return experimentalsys.UnwrapOSError(syscall.UtimesNano(path, times[:])) +} + +func futimens(fd uintptr, atim, mtim int64) experimentalsys.Errno { + times := timesToTimespecs(atim, mtim) + if times == nil { + return 0 + } + _p0 := unsafe.Pointer(×[0]) + + // Warning: futimens only exists since High Sierra (10.13). + _, _, e1 := syscall_syscall6(libc_futimens_trampoline_addr, fd, uintptr(_p0), 0, 0, 0, 0) + return experimentalsys.UnwrapOSError(e1) +} + +// libc_futimens_trampoline_addr is the address of the +// `libc_futimens_trampoline` symbol, defined in `futimens_darwin.s`. +// +// We use this to invoke the syscall through syscall_syscall6 imported below. +var libc_futimens_trampoline_addr uintptr + +// Imports the futimens symbol from libc as `libc_futimens`. +// +// Note: CGO mechanisms are used in darwin regardless of the CGO_ENABLED value +// or the "cgo" build flag. See /RATIONALE.md for why. +//go:cgo_import_dynamic libc_futimens futimens "/usr/lib/libSystem.B.dylib" diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/futimens_darwin.s b/vendor/github.com/tetratelabs/wazero/internal/sysfs/futimens_darwin.s new file mode 100644 index 00000000000..b86aecdf099 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/futimens_darwin.s @@ -0,0 +1,8 @@ +// lifted from golang.org/x/sys unix +#include "textflag.h" + +TEXT libc_futimens_trampoline<>(SB), NOSPLIT, $0-0 + JMP libc_futimens(SB) + +GLOBL ·libc_futimens_trampoline_addr(SB), RODATA, $8 +DATA ·libc_futimens_trampoline_addr(SB)/8, $libc_futimens_trampoline<>(SB) diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/futimens_linux.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/futimens_linux.go new file mode 100644 index 00000000000..a53af60bba4 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/futimens_linux.go @@ -0,0 +1,38 @@ +package sysfs + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/unix" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" +) + +const _UTIME_OMIT = unix.UTIME_OMIT + +func utimens(path string, atim, mtim int64) experimentalsys.Errno { + times := timesToTimespecs(atim, mtim) + if times == nil { + return 0 + } + return experimentalsys.UnwrapOSError(syscall.UtimesNano(path, times[:])) +} + +// On linux, implement futimens via utimensat with the NUL path. +func futimens(fd uintptr, atim, mtim int64) experimentalsys.Errno { + times := timesToTimespecs(atim, mtim) + if times == nil { + return 0 + } + return experimentalsys.UnwrapOSError(utimensat(int(fd), 0 /* NUL */, times, 0)) +} + +// utimensat is like syscall.utimensat special-cased to accept a NUL string for the path value. +func utimensat(dirfd int, strPtr uintptr, times *[2]syscall.Timespec, flags int) (err error) { + _, _, e1 := syscall.Syscall6(syscall.SYS_UTIMENSAT, uintptr(dirfd), strPtr, uintptr(unsafe.Pointer(times)), uintptr(flags), 0, 0) + if e1 != 0 { + err = e1 + } + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/futimens_unsupported.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/futimens_unsupported.go new file mode 100644 index 00000000000..668f3a61e12 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/futimens_unsupported.go @@ -0,0 +1,18 @@ +//go:build !(windows || linux || darwin) + +package sysfs + +import ( + "github.com/tetratelabs/wazero/experimental/sys" +) + +func utimens(path string, atim, mtim int64) sys.Errno { + return chtimes(path, atim, mtim) +} + +func futimens(fd uintptr, atim, mtim int64) error { + // Go exports syscall.Futimes, which is microsecond granularity, and + // WASI tests expect nanosecond. We don't yet have a way to invoke the + // futimens syscall portably. + return sys.ENOSYS +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/futimens_windows.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/futimens_windows.go new file mode 100644 index 00000000000..e0c89f30399 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/futimens_windows.go @@ -0,0 +1,42 @@ +package sysfs + +import ( + "syscall" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +func utimens(path string, atim, mtim int64) sys.Errno { + return chtimes(path, atim, mtim) +} + +func futimens(fd uintptr, atim, mtim int64) error { + // Per docs, zero isn't a valid timestamp as it cannot be differentiated + // from nil. In both cases, it is a marker like sys.UTIME_OMIT. + // See https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfiletime + a, w := timespecToFiletime(atim, mtim) + + if a == nil && w == nil { + return nil // both omitted, so nothing to change + } + + // Attempt to get the stat by handle, which works for normal files + h := syscall.Handle(fd) + + // Note: This returns ERROR_ACCESS_DENIED when the input is a directory. + return syscall.SetFileTime(h, nil, a, w) +} + +func timespecToFiletime(atim, mtim int64) (a, w *syscall.Filetime) { + a = timespecToFileTime(atim) + w = timespecToFileTime(mtim) + return +} + +func timespecToFileTime(tim int64) *syscall.Filetime { + if tim == sys.UTIME_OMIT { + return nil + } + ft := syscall.NsecToFiletime(tim) + return &ft +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/ino_unix.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/ino_unix.go new file mode 100644 index 00000000000..969fcd63f8c --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/ino_unix.go @@ -0,0 +1,22 @@ +//go:build unix + +package sysfs + +import ( + "io/fs" + "syscall" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/sys" +) + +func inoFromFileInfo(_ string, info fs.FileInfo) (sys.Inode, experimentalsys.Errno) { + switch v := info.Sys().(type) { + case *sys.Stat_t: + return v.Ino, 0 + case *syscall.Stat_t: + return v.Ino, 0 + default: + return 0, 0 + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/ino_unsupported.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/ino_unsupported.go new file mode 100644 index 00000000000..64bdebdf3a8 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/ino_unsupported.go @@ -0,0 +1,17 @@ +//go:build !(unix || windows) + +package sysfs + +import ( + "io/fs" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/sys" +) + +func inoFromFileInfo(_ string, info fs.FileInfo) (sys.Inode, experimentalsys.Errno) { + if v, ok := info.Sys().(*sys.Stat_t); ok { + return v.Ino, 0 + } + return 0, 0 +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/ino_windows.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/ino_windows.go new file mode 100644 index 00000000000..d163b3601e8 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/ino_windows.go @@ -0,0 +1,28 @@ +package sysfs + +import ( + "io/fs" + "path" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/sys" +) + +// inoFromFileInfo uses stat to get the inode information of the file. +func inoFromFileInfo(dirPath string, info fs.FileInfo) (ino sys.Inode, errno experimentalsys.Errno) { + if v, ok := info.Sys().(*sys.Stat_t); ok { + return v.Ino, 0 + } + if dirPath == "" { + // This is a FS.File backed implementation which doesn't have access to + // the original file path. + return + } + // Ino is no not in Win32FileAttributeData + inoPath := path.Clean(path.Join(dirPath, info.Name())) + var st sys.Stat_t + if st, errno = lstat(inoPath); errno == 0 { + ino = st.Ino + } + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/nonblock_unix.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/nonblock_unix.go new file mode 100644 index 00000000000..568ded18d92 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/nonblock_unix.go @@ -0,0 +1,17 @@ +//go:build unix + +package sysfs + +import ( + "syscall" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +func setNonblock(fd uintptr, enable bool) sys.Errno { + return sys.UnwrapOSError(syscall.SetNonblock(int(fd), enable)) +} + +func isNonblock(f *osFile) bool { + return f.flag&sys.O_NONBLOCK == sys.O_NONBLOCK +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/nonblock_unsupported.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/nonblock_unsupported.go new file mode 100644 index 00000000000..52caa4e4bbe --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/nonblock_unsupported.go @@ -0,0 +1,13 @@ +//go:build !(unix || windows) + +package sysfs + +import "github.com/tetratelabs/wazero/experimental/sys" + +func setNonblock(fd uintptr, enable bool) sys.Errno { + return sys.ENOSYS +} + +func isNonblock(f *osFile) bool { + return false +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/nonblock_windows.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/nonblock_windows.go new file mode 100644 index 00000000000..eb38ea5afa9 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/nonblock_windows.go @@ -0,0 +1,23 @@ +package sysfs + +import ( + "io/fs" + "syscall" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +func setNonblock(fd uintptr, enable bool) sys.Errno { + // We invoke the syscall, but this is currently no-op. + return sys.UnwrapOSError(syscall.SetNonblock(syscall.Handle(fd), enable)) +} + +func isNonblock(f *osFile) bool { + // On Windows, we support non-blocking reads only on named pipes. + isValid := false + st, errno := f.Stat() + if errno == 0 { + isValid = st.Mode&fs.ModeNamedPipe != 0 + } + return isValid && f.flag&sys.O_NONBLOCK == sys.O_NONBLOCK +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/oflag.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/oflag.go new file mode 100644 index 00000000000..be6d2c35f65 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/oflag.go @@ -0,0 +1,38 @@ +package sysfs + +import ( + "os" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +// toOsOpenFlag converts the input to the flag parameter of os.OpenFile +func toOsOpenFlag(oflag sys.Oflag) (flag int) { + // First flags are exclusive + switch oflag & (sys.O_RDONLY | sys.O_RDWR | sys.O_WRONLY) { + case sys.O_RDONLY: + flag |= os.O_RDONLY + case sys.O_RDWR: + flag |= os.O_RDWR + case sys.O_WRONLY: + flag |= os.O_WRONLY + } + + // Run down the flags defined in the os package + if oflag&sys.O_APPEND != 0 { + flag |= os.O_APPEND + } + if oflag&sys.O_CREAT != 0 { + flag |= os.O_CREATE + } + if oflag&sys.O_EXCL != 0 { + flag |= os.O_EXCL + } + if oflag&sys.O_SYNC != 0 { + flag |= os.O_SYNC + } + if oflag&sys.O_TRUNC != 0 { + flag |= os.O_TRUNC + } + return withSyscallOflag(oflag, flag) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/open_file_bsd.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/open_file_bsd.go new file mode 100644 index 00000000000..755f98525a0 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/open_file_bsd.go @@ -0,0 +1,26 @@ +//go:build freebsd || dragonfly + +package sysfs + +import ( + "syscall" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +const supportedSyscallOflag = sys.O_DIRECTORY | sys.O_NOFOLLOW | sys.O_NONBLOCK + +func withSyscallOflag(oflag sys.Oflag, flag int) int { + if oflag&sys.O_DIRECTORY != 0 { + flag |= syscall.O_DIRECTORY + } + // syscall.O_DSYNC not defined on darwin + if oflag&sys.O_NOFOLLOW != 0 { + flag |= syscall.O_NOFOLLOW + } + if oflag&sys.O_NONBLOCK != 0 { + flag |= syscall.O_NONBLOCK + } + // syscall.O_RSYNC not defined on darwin + return flag +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/open_file_darwin.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/open_file_darwin.go new file mode 100644 index 00000000000..a4f54ca2cc1 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/open_file_darwin.go @@ -0,0 +1,26 @@ +package sysfs + +import ( + "syscall" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +const supportedSyscallOflag = sys.O_DIRECTORY | sys.O_DSYNC | sys.O_NOFOLLOW | sys.O_NONBLOCK + +func withSyscallOflag(oflag sys.Oflag, flag int) int { + if oflag&sys.O_DIRECTORY != 0 { + flag |= syscall.O_DIRECTORY + } + if oflag&sys.O_DSYNC != 0 { + flag |= syscall.O_DSYNC + } + if oflag&sys.O_NOFOLLOW != 0 { + flag |= syscall.O_NOFOLLOW + } + if oflag&sys.O_NONBLOCK != 0 { + flag |= syscall.O_NONBLOCK + } + // syscall.O_RSYNC not defined on darwin + return flag +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/open_file_supported.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/open_file_supported.go new file mode 100644 index 00000000000..4443696a4e7 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/open_file_supported.go @@ -0,0 +1,30 @@ +//go:build linux || netbsd || openbsd || solaris + +package sysfs + +import ( + "syscall" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +const supportedSyscallOflag = sys.O_DIRECTORY | sys.O_DSYNC | sys.O_NOFOLLOW | sys.O_NONBLOCK | sys.O_RSYNC + +func withSyscallOflag(oflag sys.Oflag, flag int) int { + if oflag&sys.O_DIRECTORY != 0 { + flag |= syscall.O_DIRECTORY + } + if oflag&sys.O_DSYNC != 0 { + flag |= syscall.O_DSYNC + } + if oflag&sys.O_NOFOLLOW != 0 { + flag |= syscall.O_NOFOLLOW + } + if oflag&sys.O_NONBLOCK != 0 { + flag |= syscall.O_NONBLOCK + } + if oflag&sys.O_RSYNC != 0 { + flag |= syscall.O_RSYNC + } + return flag +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/open_file_unix.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/open_file_unix.go new file mode 100644 index 00000000000..9ae1e2bcdb7 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/open_file_unix.go @@ -0,0 +1,20 @@ +//go:build !windows + +package sysfs + +import ( + "io/fs" + "os" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +// openFile is like os.OpenFile except it accepts a sys.Oflag and returns +// sys.Errno. A zero sys.Errno is success. +func openFile(path string, oflag sys.Oflag, perm fs.FileMode) (*os.File, sys.Errno) { + f, err := os.OpenFile(path, toOsOpenFlag(oflag), perm) + // Note: This does not return a sys.File because sys.FS that returns + // one may want to hide the real OS path. For example, this is needed for + // pre-opens. + return f, sys.UnwrapOSError(err) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/open_file_unsupported.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/open_file_unsupported.go new file mode 100644 index 00000000000..234fa46874e --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/open_file_unsupported.go @@ -0,0 +1,16 @@ +//go:build !(freebsd || dragonfly || darwin || linux || netbsd || openbsd || solaris || windows) + +package sysfs + +import "github.com/tetratelabs/wazero/experimental/sys" + +const supportedSyscallOflag = sys.Oflag(0) + +func withSyscallOflag(oflag sys.Oflag, flag int) int { + // O_DIRECTORY not defined + // O_DSYNC not defined + // O_NOFOLLOW not defined + // O_NONBLOCK not defined + // O_RSYNC not defined + return flag +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/open_file_windows.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/open_file_windows.go new file mode 100644 index 00000000000..f5a3710b056 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/open_file_windows.go @@ -0,0 +1,163 @@ +package sysfs + +import ( + "io/fs" + "os" + "strings" + "syscall" + "unsafe" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +func openFile(path string, oflag sys.Oflag, perm fs.FileMode) (*os.File, sys.Errno) { + isDir := oflag&sys.O_DIRECTORY > 0 + flag := toOsOpenFlag(oflag) + + // TODO: document why we are opening twice + fd, err := open(path, flag|syscall.O_CLOEXEC, uint32(perm)) + if err == nil { + return os.NewFile(uintptr(fd), path), 0 + } + + // TODO: Set FILE_SHARE_DELETE for directory as well. + f, err := os.OpenFile(path, flag, perm) + errno := sys.UnwrapOSError(err) + if errno == 0 { + return f, 0 + } + + switch errno { + case sys.EINVAL: + // WASI expects ENOTDIR for a file path with a trailing slash. + if strings.HasSuffix(path, "/") { + errno = sys.ENOTDIR + } + // To match expectations of WASI, e.g. TinyGo TestStatBadDir, return + // ENOENT, not ENOTDIR. + case sys.ENOTDIR: + if !strings.HasSuffix(path, "/") { + errno = sys.ENOENT + } + case sys.ENOENT: + if isSymlink(path) { + // Either symlink or hard link not found. We change the returned + // errno depending on if it is symlink or not to have consistent + // behavior across OSes. + if isDir { + // Dangling symlink dir must raise ENOTDIR. + errno = sys.ENOTDIR + } else { + errno = sys.ELOOP + } + } + } + return f, errno +} + +const supportedSyscallOflag = sys.O_NONBLOCK + +// Map to synthetic values here https://github.com/golang/go/blob/go1.24.0/src/syscall/types_windows.go#L35-L52 +func withSyscallOflag(oflag sys.Oflag, flag int) int { + // O_DIRECTORY not defined in windows + // O_DSYNC not defined in windows + // O_NOFOLLOW not defined in windows + if oflag&sys.O_NONBLOCK != 0 { + flag |= syscall.O_NONBLOCK + } + // O_RSYNC not defined in windows + return flag +} + +func isSymlink(path string) bool { + if st, e := os.Lstat(path); e == nil && st.Mode()&os.ModeSymlink != 0 { + return true + } + return false +} + +// # Differences from syscall.Open +// +// This code is based on syscall.Open from the below link with some differences +// https://github.com/golang/go/blame/go1.20/src/syscall/syscall_windows.go#L308-L379 +// +// - syscall.O_CREAT doesn't imply syscall.GENERIC_WRITE as that breaks +// flag expectations in wasi. +// - add support for setting FILE_SHARE_DELETE. +func open(path string, mode int, perm uint32) (fd syscall.Handle, err error) { + if len(path) == 0 { + return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND + } + pathp, err := syscall.UTF16PtrFromString(path) + if err != nil { + return syscall.InvalidHandle, err + } + var access uint32 + switch mode & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) { + case syscall.O_RDONLY: + access = syscall.GENERIC_READ + case syscall.O_WRONLY: + access = syscall.GENERIC_WRITE + case syscall.O_RDWR: + access = syscall.GENERIC_READ | syscall.GENERIC_WRITE + } + if mode&syscall.O_APPEND != 0 { + access &^= syscall.GENERIC_WRITE + access |= syscall.FILE_APPEND_DATA + } + sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE | syscall.FILE_SHARE_DELETE) + var sa *syscall.SecurityAttributes + if mode&syscall.O_CLOEXEC == 0 { + var _sa syscall.SecurityAttributes + _sa.Length = uint32(unsafe.Sizeof(sa)) + _sa.InheritHandle = 1 + sa = &_sa + } + var createmode uint32 + switch { + case mode&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL): + createmode = syscall.CREATE_NEW + case mode&(syscall.O_CREAT|syscall.O_TRUNC) == (syscall.O_CREAT | syscall.O_TRUNC): + createmode = syscall.CREATE_ALWAYS + case mode&syscall.O_CREAT == syscall.O_CREAT: + createmode = syscall.OPEN_ALWAYS + case mode&syscall.O_TRUNC == syscall.O_TRUNC: + createmode = syscall.TRUNCATE_EXISTING + default: + createmode = syscall.OPEN_EXISTING + } + var attrs uint32 = syscall.FILE_ATTRIBUTE_NORMAL + if perm&syscall.S_IWRITE == 0 { + attrs = syscall.FILE_ATTRIBUTE_READONLY + if createmode == syscall.CREATE_ALWAYS { + // We have been asked to create a read-only file. + // If the file already exists, the semantics of + // the Unix open system call is to preserve the + // existing permissions. If we pass CREATE_ALWAYS + // and FILE_ATTRIBUTE_READONLY to CreateFile, + // and the file already exists, CreateFile will + // change the file permissions. + // Avoid that to preserve the Unix semantics. + h, e := syscall.CreateFile(pathp, access, sharemode, sa, syscall.TRUNCATE_EXISTING, syscall.FILE_ATTRIBUTE_NORMAL, 0) + switch e { + case syscall.ERROR_FILE_NOT_FOUND, syscall.ERROR_PATH_NOT_FOUND: + // File does not exist. These are the same + // errors as Errno.Is checks for ErrNotExist. + // Carry on to create the file. + default: + // Success or some different error. + return h, e + } + } + } + + // This shouldn't be included before 1.20 to have consistent behavior. + // https://github.com/golang/go/commit/0f0aa5d8a6a0253627d58b3aa083b24a1091933f + if createmode == syscall.OPEN_EXISTING && access == syscall.GENERIC_READ { + // Necessary for opening directory handles. + attrs |= syscall.FILE_FLAG_BACKUP_SEMANTICS + } + + h, e := syscall.CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0) + return h, e +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/osfile.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/osfile.go new file mode 100644 index 00000000000..981f7139d06 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/osfile.go @@ -0,0 +1,319 @@ +package sysfs + +import ( + "io" + "io/fs" + "os" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" + "github.com/tetratelabs/wazero/sys" +) + +func newOsFile(path string, flag experimentalsys.Oflag, perm fs.FileMode, f *os.File) fsapi.File { + // On POSIX, if a file is removed from or added to the directory after the + // most recent call to opendir() or rewinddir(), whether a subsequent call + // to readdir() returns an entry for that file is unspecified. + // + // And Windows cannot read files added to a directory after it was opened. + // This was noticed in #1087 in zig tests. + // + // So there is no guarantee that files added after opendir() will be visible + // in readdir(). Since we want those files to be visible, we need to + // reopendir() to get the new state of the directory before readdir(). + return &osFile{path: path, flag: flag, perm: perm, reopenDir: true, file: f, fd: f.Fd()} +} + +// osFile is a file opened with this package, and uses os.File or syscalls to +// implement api.File. +type osFile struct { + path string + flag experimentalsys.Oflag + perm fs.FileMode + file *os.File + fd uintptr + + // reopenDir is true if reopen should be called before Readdir. This flag + // is deferred until Readdir to prevent redundant rewinds. This could + // happen if Seek(0) was called twice, or if in Windows, Seek(0) was called + // before Readdir. + reopenDir bool + + // closed is true when closed was called. This ensures proper sys.EBADF + closed bool + + // cachedStat includes fields that won't change while a file is open. + cachedSt *cachedStat +} + +// cachedStat returns the cacheable parts of sys.Stat_t or an error if they +// couldn't be retrieved. +func (f *osFile) cachedStat() (dev uint64, ino sys.Inode, isDir bool, errno experimentalsys.Errno) { + if f.cachedSt == nil { + if _, errno = f.Stat(); errno != 0 { + return + } + } + return f.cachedSt.dev, f.cachedSt.ino, f.cachedSt.isDir, 0 +} + +// Dev implements the same method as documented on sys.File +func (f *osFile) Dev() (uint64, experimentalsys.Errno) { + dev, _, _, errno := f.cachedStat() + return dev, errno +} + +// Ino implements the same method as documented on sys.File +func (f *osFile) Ino() (sys.Inode, experimentalsys.Errno) { + _, ino, _, errno := f.cachedStat() + return ino, errno +} + +// IsDir implements the same method as documented on sys.File +func (f *osFile) IsDir() (bool, experimentalsys.Errno) { + _, _, isDir, errno := f.cachedStat() + return isDir, errno +} + +// IsAppend implements File.IsAppend +func (f *osFile) IsAppend() bool { + return f.flag&experimentalsys.O_APPEND == experimentalsys.O_APPEND +} + +// SetAppend implements the same method as documented on sys.File +func (f *osFile) SetAppend(enable bool) (errno experimentalsys.Errno) { + if enable { + f.flag |= experimentalsys.O_APPEND + } else { + f.flag &= ^experimentalsys.O_APPEND + } + + // appendMode cannot be changed later, so we have to re-open the file + // https://github.com/golang/go/blob/go1.24.0/src/os/file_unix.go#L65 + return fileError(f, f.closed, f.reopen()) +} + +func (f *osFile) reopen() (errno experimentalsys.Errno) { + var ( + isDir bool + offset int64 + err error + ) + + isDir, errno = f.IsDir() + if errno != 0 { + return errno + } + + if !isDir { + offset, err = f.file.Seek(0, io.SeekCurrent) + if err != nil { + return experimentalsys.UnwrapOSError(err) + } + } + + // Clear any create or trunc flag, as we are re-opening, not re-creating. + flag := f.flag &^ (experimentalsys.O_CREAT | experimentalsys.O_TRUNC) + file, errno := OpenFile(f.path, flag, f.perm) + if errno != 0 { + return errno + } + errno = f.checkSameFile(file) + if errno != 0 { + return errno + } + + if !isDir { + _, err = file.Seek(offset, io.SeekStart) + if err != nil { + _ = file.Close() + return experimentalsys.UnwrapOSError(err) + } + } + + // Only update f on success. + _ = f.file.Close() + f.file = file + f.fd = file.Fd() + return 0 +} + +func (f *osFile) checkSameFile(osf *os.File) experimentalsys.Errno { + fi1, err := f.file.Stat() + if err != nil { + return experimentalsys.UnwrapOSError(err) + } + fi2, err := osf.Stat() + if err != nil { + return experimentalsys.UnwrapOSError(err) + } + if os.SameFile(fi1, fi2) { + return 0 + } + return experimentalsys.ENOENT +} + +// IsNonblock implements the same method as documented on fsapi.File +func (f *osFile) IsNonblock() bool { + return isNonblock(f) +} + +// SetNonblock implements the same method as documented on fsapi.File +func (f *osFile) SetNonblock(enable bool) (errno experimentalsys.Errno) { + if enable { + f.flag |= experimentalsys.O_NONBLOCK + } else { + f.flag &= ^experimentalsys.O_NONBLOCK + } + if errno = setNonblock(f.fd, enable); errno != 0 { + return fileError(f, f.closed, errno) + } + return 0 +} + +// Stat implements the same method as documented on sys.File +func (f *osFile) Stat() (sys.Stat_t, experimentalsys.Errno) { + if f.closed { + return sys.Stat_t{}, experimentalsys.EBADF + } + + st, errno := statFile(f.file) + switch errno { + case 0: + f.cachedSt = &cachedStat{dev: st.Dev, ino: st.Ino, isDir: st.Mode&fs.ModeDir == fs.ModeDir} + case experimentalsys.EIO: + errno = experimentalsys.EBADF + } + return st, errno +} + +// Read implements the same method as documented on sys.File +func (f *osFile) Read(buf []byte) (n int, errno experimentalsys.Errno) { + if len(buf) == 0 { + return 0, 0 // Short-circuit 0-len reads. + } + if nonBlockingFileReadSupported && f.IsNonblock() { + n, errno = readFd(f.fd, buf) + } else { + n, errno = read(f.file, buf) + } + if errno != 0 { + // Defer validation overhead until we've already had an error. + errno = fileError(f, f.closed, errno) + } + return +} + +// Pread implements the same method as documented on sys.File +func (f *osFile) Pread(buf []byte, off int64) (n int, errno experimentalsys.Errno) { + if n, errno = pread(f.file, buf, off); errno != 0 { + // Defer validation overhead until we've already had an error. + errno = fileError(f, f.closed, errno) + } + return +} + +// Seek implements the same method as documented on sys.File +func (f *osFile) Seek(offset int64, whence int) (newOffset int64, errno experimentalsys.Errno) { + if newOffset, errno = seek(f.file, offset, whence); errno != 0 { + // Defer validation overhead until we've already had an error. + errno = fileError(f, f.closed, errno) + + // If the error was trying to rewind a directory, re-open it. Notably, + // seeking to zero on a directory doesn't work on Windows with Go 1.19. + if errno == experimentalsys.EISDIR && offset == 0 && whence == io.SeekStart { + errno = 0 + f.reopenDir = true + } + } + return +} + +// Poll implements the same method as documented on fsapi.File +func (f *osFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { + return poll(f.fd, flag, timeoutMillis) +} + +// Readdir implements File.Readdir. Notably, this uses "Readdir", not +// "ReadDir", from os.File. +func (f *osFile) Readdir(n int) (dirents []experimentalsys.Dirent, errno experimentalsys.Errno) { + if f.reopenDir { // re-open the directory if needed. + f.reopenDir = false + if errno = adjustReaddirErr(f, f.closed, f.reopen()); errno != 0 { + return + } + } + + if dirents, errno = readdir(f.file, f.path, n); errno != 0 { + errno = adjustReaddirErr(f, f.closed, errno) + } + return +} + +// Write implements the same method as documented on sys.File +func (f *osFile) Write(buf []byte) (n int, errno experimentalsys.Errno) { + if len(buf) == 0 { + return 0, 0 // Short-circuit 0-len writes. + } + if nonBlockingFileWriteSupported && f.IsNonblock() { + n, errno = writeFd(f.fd, buf) + } else if n, errno = write(f.file, buf); errno != 0 { + // Defer validation overhead until we've already had an error. + errno = fileError(f, f.closed, errno) + } + return +} + +// Pwrite implements the same method as documented on sys.File +func (f *osFile) Pwrite(buf []byte, off int64) (n int, errno experimentalsys.Errno) { + if n, errno = pwrite(f.file, buf, off); errno != 0 { + // Defer validation overhead until we've already had an error. + errno = fileError(f, f.closed, errno) + } + return +} + +// Truncate implements the same method as documented on sys.File +func (f *osFile) Truncate(size int64) (errno experimentalsys.Errno) { + if size < 0 { + return experimentalsys.EINVAL + } + if errno = experimentalsys.UnwrapOSError(f.file.Truncate(size)); errno != 0 { + // Defer validation overhead until we've already had an error. + errno = fileError(f, f.closed, errno) + } + return +} + +// Sync implements the same method as documented on sys.File +func (f *osFile) Sync() experimentalsys.Errno { + return fsync(f.file) +} + +// Datasync implements the same method as documented on sys.File +func (f *osFile) Datasync() experimentalsys.Errno { + return datasync(f.file) +} + +// Utimens implements the same method as documented on sys.File +func (f *osFile) Utimens(atim, mtim int64) experimentalsys.Errno { + if f.closed { + return experimentalsys.EBADF + } + + err := futimens(f.fd, atim, mtim) + return experimentalsys.UnwrapOSError(err) +} + +// Close implements the same method as documented on sys.File +func (f *osFile) Close() experimentalsys.Errno { + if f.closed { + return 0 + } + f.closed = true + return f.close() +} + +func (f *osFile) close() experimentalsys.Errno { + return experimentalsys.UnwrapOSError(f.file.Close()) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/poll.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/poll.go new file mode 100644 index 00000000000..f5c98295291 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/poll.go @@ -0,0 +1,18 @@ +//go:build windows || linux || darwin + +package sysfs + +import ( + "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" +) + +// poll implements `Poll` as documented on sys.File via a file descriptor. +func poll(fd uintptr, flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno sys.Errno) { + if flag != fsapi.POLLIN { + return false, sys.ENOTSUP + } + fds := []pollFd{newPollFd(fd, _POLLIN, 0)} + count, errno := _poll(fds, timeoutMillis) + return count > 0, errno +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/poll_darwin.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/poll_darwin.go new file mode 100644 index 00000000000..1f7f8909371 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/poll_darwin.go @@ -0,0 +1,55 @@ +package sysfs + +import ( + "unsafe" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +// pollFd is the struct to query for file descriptor events using poll. +type pollFd struct { + // fd is the file descriptor. + fd int32 + // events is a bitmap containing the requested events. + events int16 + // revents is a bitmap containing the returned events. + revents int16 +} + +// newPollFd is a constructor for pollFd that abstracts the platform-specific type of file descriptors. +func newPollFd(fd uintptr, events, revents int16) pollFd { + return pollFd{fd: int32(fd), events: events, revents: revents} +} + +// _POLLIN subscribes a notification when any readable data is available. +const _POLLIN = 0x0001 + +// _poll implements poll on Darwin via the corresponding libc function. +func _poll(fds []pollFd, timeoutMillis int32) (n int, errno sys.Errno) { + var fdptr *pollFd + nfds := len(fds) + if nfds > 0 { + fdptr = &fds[0] + } + n1, _, err := syscall_syscall6( + libc_poll_trampoline_addr, + uintptr(unsafe.Pointer(fdptr)), + uintptr(nfds), + uintptr(int(timeoutMillis)), + uintptr(unsafe.Pointer(nil)), + uintptr(unsafe.Pointer(nil)), + uintptr(unsafe.Pointer(nil))) + return int(n1), sys.UnwrapOSError(err) +} + +// libc_poll_trampoline_addr is the address of the +// `libc_poll_trampoline` symbol, defined in `poll_darwin.s`. +// +// We use this to invoke the syscall through syscall_syscall6 imported below. +var libc_poll_trampoline_addr uintptr + +// Imports the select symbol from libc as `libc_poll`. +// +// Note: CGO mechanisms are used in darwin regardless of the CGO_ENABLED value +// or the "cgo" build flag. See /RATIONALE.md for why. +//go:cgo_import_dynamic libc_poll poll "/usr/lib/libSystem.B.dylib" diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/poll_darwin.s b/vendor/github.com/tetratelabs/wazero/internal/sysfs/poll_darwin.s new file mode 100644 index 00000000000..e04fca583b1 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/poll_darwin.s @@ -0,0 +1,8 @@ +// lifted from golang.org/x/sys unix +#include "textflag.h" + +TEXT libc_poll_trampoline<>(SB), NOSPLIT, $0-0 + JMP libc_poll(SB) + +GLOBL ·libc_poll_trampoline_addr(SB), RODATA, $8 +DATA ·libc_poll_trampoline_addr(SB)/8, $libc_poll_trampoline<>(SB) diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/poll_linux.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/poll_linux.go new file mode 100644 index 00000000000..dab7bb2cab6 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/poll_linux.go @@ -0,0 +1,57 @@ +package sysfs + +import ( + "syscall" + "time" + "unsafe" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +// pollFd is the struct to query for file descriptor events using poll. +type pollFd struct { + // fd is the file descriptor. + fd int32 + // events is a bitmap containing the requested events. + events int16 + // revents is a bitmap containing the returned events. + revents int16 +} + +// newPollFd is a constructor for pollFd that abstracts the platform-specific type of file descriptors. +func newPollFd(fd uintptr, events, revents int16) pollFd { + return pollFd{fd: int32(fd), events: events, revents: revents} +} + +// _POLLIN subscribes a notification when any readable data is available. +const _POLLIN = 0x0001 + +// _poll implements poll on Linux via ppoll. +func _poll(fds []pollFd, timeoutMillis int32) (n int, errno sys.Errno) { + var ts syscall.Timespec + if timeoutMillis >= 0 { + ts = syscall.NsecToTimespec(int64(time.Duration(timeoutMillis) * time.Millisecond)) + } + return ppoll(fds, &ts) +} + +// ppoll is a poll variant that allows to subscribe to a mask of signals. +// However, we do not need such mask, so the corresponding argument is always nil. +func ppoll(fds []pollFd, timespec *syscall.Timespec) (n int, err sys.Errno) { + var fdptr *pollFd + nfd := len(fds) + if nfd != 0 { + fdptr = &fds[0] + } + + n1, _, errno := syscall.Syscall6( + uintptr(syscall.SYS_PPOLL), + uintptr(unsafe.Pointer(fdptr)), + uintptr(nfd), + uintptr(unsafe.Pointer(timespec)), + uintptr(unsafe.Pointer(nil)), // sigmask is currently always ignored + uintptr(unsafe.Pointer(nil)), + uintptr(unsafe.Pointer(nil))) + + return int(n1), sys.UnwrapOSError(errno) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/poll_unsupported.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/poll_unsupported.go new file mode 100644 index 00000000000..53d09d8af60 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/poll_unsupported.go @@ -0,0 +1,13 @@ +//go:build !(linux || darwin || windows) + +package sysfs + +import ( + "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" +) + +// poll implements `Poll` as documented on fsapi.File via a file descriptor. +func poll(uintptr, fsapi.Pflag, int32) (bool, sys.Errno) { + return false, sys.ENOSYS +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/poll_windows.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/poll_windows.go new file mode 100644 index 00000000000..82c8b2bafda --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/poll_windows.go @@ -0,0 +1,224 @@ +package sysfs + +import ( + "syscall" + "time" + "unsafe" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +var ( + procWSAPoll = modws2_32.NewProc("WSAPoll") + procGetNamedPipeInfo = kernel32.NewProc("GetNamedPipeInfo") +) + +const ( + // _POLLRDNORM subscribes to normal data for read. + _POLLRDNORM = 0x0100 + // _POLLRDBAND subscribes to priority band (out-of-band) data for read. + _POLLRDBAND = 0x0200 + // _POLLIN subscribes a notification when any readable data is available. + _POLLIN = (_POLLRDNORM | _POLLRDBAND) +) + +// pollFd is the struct to query for file descriptor events using poll. +type pollFd struct { + // fd is the file descriptor. + fd uintptr + // events is a bitmap containing the requested events. + events int16 + // revents is a bitmap containing the returned events. + revents int16 +} + +// newPollFd is a constructor for pollFd that abstracts the platform-specific type of file descriptors. +func newPollFd(fd uintptr, events, revents int16) pollFd { + return pollFd{fd: fd, events: events, revents: revents} +} + +// pollInterval is the interval between each calls to peekNamedPipe in selectAllHandles +const pollInterval = 100 * time.Millisecond + +// _poll implements poll on Windows, for a subset of cases. +// +// fds may contain any number of file handles, but regular files and pipes are only processed for _POLLIN. +// Stdin is a pipe, thus it is checked for readiness when present. Pipes are checked using PeekNamedPipe. +// Regular files always immediately reported as ready, regardless their actual state and timeouts. +// +// If n==0 it will wait for the given timeout duration, but it will return sys.ENOSYS if timeout is nil, +// i.e. it won't block indefinitely. The given ctx is used to allow for cancellation, +// and it is currently used only in tests. +// +// The implementation actually polls every 100 milliseconds (pollInterval) until it reaches the +// given timeout (in millis). +// +// The duration may be negative, in which case it will wait indefinitely. The given ctx is +// used to allow for cancellation, and it is currently used only in tests. +func _poll(fds []pollFd, timeoutMillis int32) (n int, errno sys.Errno) { + if fds == nil { + return -1, sys.ENOSYS + } + + regular, pipes, sockets, errno := partionByFtype(fds) + nregular := len(regular) + if errno != 0 { + return -1, errno + } + + // Ticker that emits at every pollInterval. + tick := time.NewTicker(pollInterval) + tickCh := tick.C + defer tick.Stop() + + // Timer that expires after the given duration. + // Initialize afterCh as nil: the select below will wait forever. + var afterCh <-chan time.Time + if timeoutMillis >= 0 { + // If duration is not nil, instantiate the timer. + after := time.NewTimer(time.Duration(timeoutMillis) * time.Millisecond) + defer after.Stop() + afterCh = after.C + } + + npipes, nsockets, errno := peekAll(pipes, sockets) + if errno != 0 { + return -1, errno + } + count := nregular + npipes + nsockets + if count > 0 { + return count, 0 + } + + for { + select { + case <-afterCh: + return 0, 0 + case <-tickCh: + npipes, nsockets, errno := peekAll(pipes, sockets) + if errno != 0 { + return -1, errno + } + count = nregular + npipes + nsockets + if count > 0 { + return count, 0 + } + } + } +} + +func peekAll(pipes, sockets []pollFd) (npipes, nsockets int, errno sys.Errno) { + npipes, errno = peekPipes(pipes) + if errno != 0 { + return + } + + // Invoke wsaPoll with a 0-timeout to avoid blocking. + // Timeouts are handled in pollWithContext instead. + nsockets, errno = wsaPoll(sockets, 0) + if errno != 0 { + return + } + + count := npipes + nsockets + if count > 0 { + return + } + + return +} + +func peekPipes(fds []pollFd) (n int, errno sys.Errno) { + for _, fd := range fds { + bytes, errno := peekNamedPipe(syscall.Handle(fd.fd)) + if errno != 0 { + return -1, sys.UnwrapOSError(errno) + } + if bytes > 0 { + n++ + } + } + return +} + +// wsaPoll is the WSAPoll function from winsock2. +// +// See https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsapoll +func wsaPoll(fds []pollFd, timeout int) (n int, errno sys.Errno) { + if len(fds) > 0 { + sockptr := &fds[0] + ns, _, e := syscall.SyscallN( + procWSAPoll.Addr(), + uintptr(unsafe.Pointer(sockptr)), + uintptr(len(fds)), + uintptr(timeout)) + if e != 0 { + return -1, sys.UnwrapOSError(e) + } + n = int(ns) + } + return +} + +// ftype is a type of file that can be handled by poll. +type ftype uint8 + +const ( + ftype_regular ftype = iota + ftype_pipe + ftype_socket +) + +// partionByFtype checks the type of each fd in fds and returns 3 distinct partitions +// for regular files, named pipes and sockets. +func partionByFtype(fds []pollFd) (regular, pipe, socket []pollFd, errno sys.Errno) { + for _, pfd := range fds { + t, errno := ftypeOf(pfd.fd) + if errno != 0 { + return nil, nil, nil, errno + } + switch t { + case ftype_regular: + regular = append(regular, pfd) + case ftype_pipe: + pipe = append(pipe, pfd) + case ftype_socket: + socket = append(socket, pfd) + } + } + return +} + +// ftypeOf checks the type of fd and return the corresponding ftype. +func ftypeOf(fd uintptr) (ftype, sys.Errno) { + h := syscall.Handle(fd) + t, err := syscall.GetFileType(h) + if err != nil { + return 0, sys.UnwrapOSError(err) + } + switch t { + case syscall.FILE_TYPE_CHAR, syscall.FILE_TYPE_DISK: + return ftype_regular, 0 + case syscall.FILE_TYPE_PIPE: + if isSocket(h) { + return ftype_socket, 0 + } else { + return ftype_pipe, 0 + } + default: + return ftype_regular, 0 + } +} + +// isSocket returns true if the given file handle +// is a pipe. +func isSocket(fd syscall.Handle) bool { + r, _, errno := syscall.SyscallN( + procGetNamedPipeInfo.Addr(), + uintptr(fd), + uintptr(unsafe.Pointer(nil)), + uintptr(unsafe.Pointer(nil)), + uintptr(unsafe.Pointer(nil)), + uintptr(unsafe.Pointer(nil))) + return r == 0 || errno != 0 +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/readfs.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/readfs.go new file mode 100644 index 00000000000..59e331a2981 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/readfs.go @@ -0,0 +1,117 @@ +package sysfs + +import ( + "io/fs" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" +) + +type ReadFS struct { + experimentalsys.FS +} + +// OpenFile implements the same method as documented on sys.FS +func (r *ReadFS) OpenFile(path string, flag experimentalsys.Oflag, perm fs.FileMode) (experimentalsys.File, experimentalsys.Errno) { + // Mask the mutually exclusive bits as they determine write mode. + switch flag & (experimentalsys.O_RDONLY | experimentalsys.O_WRONLY | experimentalsys.O_RDWR) { + case experimentalsys.O_WRONLY, experimentalsys.O_RDWR: + // Return the correct error if a directory was opened for write. + if flag&experimentalsys.O_DIRECTORY != 0 { + return nil, experimentalsys.EISDIR + } + return nil, experimentalsys.ENOSYS + default: // sys.O_RDONLY (integer zero) so we are ok! + } + + f, errno := r.FS.OpenFile(path, flag, perm) + if errno != 0 { + return nil, errno + } + return &readFile{f}, 0 +} + +// Mkdir implements the same method as documented on sys.FS +func (r *ReadFS) Mkdir(path string, perm fs.FileMode) experimentalsys.Errno { + return experimentalsys.EROFS +} + +// Chmod implements the same method as documented on sys.FS +func (r *ReadFS) Chmod(path string, perm fs.FileMode) experimentalsys.Errno { + return experimentalsys.EROFS +} + +// Rename implements the same method as documented on sys.FS +func (r *ReadFS) Rename(from, to string) experimentalsys.Errno { + return experimentalsys.EROFS +} + +// Rmdir implements the same method as documented on sys.FS +func (r *ReadFS) Rmdir(path string) experimentalsys.Errno { + return experimentalsys.EROFS +} + +// Link implements the same method as documented on sys.FS +func (r *ReadFS) Link(_, _ string) experimentalsys.Errno { + return experimentalsys.EROFS +} + +// Symlink implements the same method as documented on sys.FS +func (r *ReadFS) Symlink(_, _ string) experimentalsys.Errno { + return experimentalsys.EROFS +} + +// Unlink implements the same method as documented on sys.FS +func (r *ReadFS) Unlink(path string) experimentalsys.Errno { + return experimentalsys.EROFS +} + +// Utimens implements the same method as documented on sys.FS +func (r *ReadFS) Utimens(path string, atim, mtim int64) experimentalsys.Errno { + return experimentalsys.EROFS +} + +// compile-time check to ensure readFile implements api.File. +var _ experimentalsys.File = (*readFile)(nil) + +type readFile struct { + experimentalsys.File +} + +// Write implements the same method as documented on sys.File. +func (r *readFile) Write([]byte) (int, experimentalsys.Errno) { + return 0, r.writeErr() +} + +// Pwrite implements the same method as documented on sys.File. +func (r *readFile) Pwrite([]byte, int64) (n int, errno experimentalsys.Errno) { + return 0, r.writeErr() +} + +// Truncate implements the same method as documented on sys.File. +func (r *readFile) Truncate(int64) experimentalsys.Errno { + return r.writeErr() +} + +// Sync implements the same method as documented on sys.File. +func (r *readFile) Sync() experimentalsys.Errno { + return experimentalsys.EBADF +} + +// Datasync implements the same method as documented on sys.File. +func (r *readFile) Datasync() experimentalsys.Errno { + return experimentalsys.EBADF +} + +// Utimens implements the same method as documented on sys.File. +func (r *readFile) Utimens(int64, int64) experimentalsys.Errno { + return experimentalsys.EBADF +} + +func (r *readFile) writeErr() experimentalsys.Errno { + if isDir, errno := r.IsDir(); errno != 0 { + return errno + } else if isDir { + return experimentalsys.EISDIR + } + return experimentalsys.EBADF +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/rename_unix.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/rename_unix.go new file mode 100644 index 00000000000..d3d75fc5808 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/rename_unix.go @@ -0,0 +1,16 @@ +//go:build unix + +package sysfs + +import ( + "syscall" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +func rename(from, to string) sys.Errno { + if from == to { + return 0 + } + return sys.UnwrapOSError(syscall.Rename(from, to)) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/rename_unsupported.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/rename_unsupported.go new file mode 100644 index 00000000000..49c8360eb15 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/rename_unsupported.go @@ -0,0 +1,16 @@ +//go:build !(unix || windows) + +package sysfs + +import ( + "os" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +func rename(from, to string) sys.Errno { + if from == to { + return 0 + } + return sys.UnwrapOSError(os.Rename(from, to)) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/rename_windows.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/rename_windows.go new file mode 100644 index 00000000000..5e810223917 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/rename_windows.go @@ -0,0 +1,55 @@ +package sysfs + +import ( + "os" + "syscall" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +func rename(from, to string) sys.Errno { + if from == to { + return 0 + } + + var fromIsDir, toIsDir bool + if fromStat, errno := stat(from); errno != 0 { + return errno // failed to stat from + } else { + fromIsDir = fromStat.Mode.IsDir() + } + if toStat, errno := stat(to); errno == sys.ENOENT { + return syscallRename(from, to) // file or dir to not-exist is ok + } else if errno != 0 { + return errno // failed to stat to + } else { + toIsDir = toStat.Mode.IsDir() + } + + // Now, handle known cases + switch { + case !fromIsDir && toIsDir: // file to dir + return sys.EISDIR + case !fromIsDir && !toIsDir: // file to file + // Use os.Rename instead of syscall.Rename to overwrite a file. + // This uses MoveFileEx instead of MoveFile (used by syscall.Rename). + return sys.UnwrapOSError(os.Rename(from, to)) + case fromIsDir && !toIsDir: // dir to file + return sys.ENOTDIR + default: // dir to dir + + // We can't tell if a directory is empty or not, via stat information. + // Reading the directory is expensive, as it can buffer large amounts + // of data on fail. Instead, speculatively try to remove the directory. + // This is only one syscall and won't buffer anything. + if errno := rmdir(to); errno == 0 || errno == sys.ENOENT { + return syscallRename(from, to) + } else { + return errno + } + } +} + +func syscallRename(from string, to string) sys.Errno { + return sys.UnwrapOSError(syscall.Rename(from, to)) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/sock.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/sock.go new file mode 100644 index 00000000000..ab9bb1ffa76 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/sock.go @@ -0,0 +1,187 @@ +package sysfs + +import ( + "net" + "os" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" + socketapi "github.com/tetratelabs/wazero/internal/sock" + "github.com/tetratelabs/wazero/sys" +) + +// NewTCPListenerFile creates a socketapi.TCPSock for a given *net.TCPListener. +func NewTCPListenerFile(tl *net.TCPListener) socketapi.TCPSock { + return newTCPListenerFile(tl) +} + +// baseSockFile implements base behavior for all TCPSock, TCPConn files, +// regardless the platform. +type baseSockFile struct { + experimentalsys.UnimplementedFile +} + +var _ experimentalsys.File = (*baseSockFile)(nil) + +// IsDir implements the same method as documented on File.IsDir +func (*baseSockFile) IsDir() (bool, experimentalsys.Errno) { + // We need to override this method because WASI-libc prestats the FD + // and the default impl returns ENOSYS otherwise. + return false, 0 +} + +// Stat implements the same method as documented on File.Stat +func (f *baseSockFile) Stat() (fs sys.Stat_t, errno experimentalsys.Errno) { + // The mode is not really important, but it should be neither a regular file nor a directory. + fs.Mode = os.ModeIrregular + return +} + +var _ socketapi.TCPSock = (*tcpListenerFile)(nil) + +type tcpListenerFile struct { + baseSockFile + + tl *net.TCPListener + closed bool + nonblock bool +} + +// newTCPListenerFile is a constructor for a socketapi.TCPSock. +// +// The current strategy is to wrap a net.TCPListener +// and invoking raw syscalls using syscallConnControl: +// this internal calls RawConn.Control(func(fd)), making sure +// that the underlying file descriptor is valid throughout +// the duration of the syscall. +func newDefaultTCPListenerFile(tl *net.TCPListener) socketapi.TCPSock { + return &tcpListenerFile{tl: tl} +} + +// Close implements the same method as documented on experimentalsys.File +func (f *tcpListenerFile) Close() experimentalsys.Errno { + if !f.closed { + return experimentalsys.UnwrapOSError(f.tl.Close()) + } + return 0 +} + +// Addr is exposed for testing. +func (f *tcpListenerFile) Addr() *net.TCPAddr { + return f.tl.Addr().(*net.TCPAddr) +} + +// IsNonblock implements the same method as documented on fsapi.File +func (f *tcpListenerFile) IsNonblock() bool { + return f.nonblock +} + +// Poll implements the same method as documented on fsapi.File +func (f *tcpListenerFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { + return false, experimentalsys.ENOSYS +} + +var _ socketapi.TCPConn = (*tcpConnFile)(nil) + +type tcpConnFile struct { + baseSockFile + + tc *net.TCPConn + + // nonblock is true when the underlying connection is flagged as non-blocking. + // This ensures that reads and writes return experimentalsys.EAGAIN without blocking the caller. + nonblock bool + // closed is true when closed was called. This ensures proper experimentalsys.EBADF + closed bool +} + +func newTcpConn(tc *net.TCPConn) socketapi.TCPConn { + return &tcpConnFile{tc: tc} +} + +// Read implements the same method as documented on experimentalsys.File +func (f *tcpConnFile) Read(buf []byte) (n int, errno experimentalsys.Errno) { + if len(buf) == 0 { + return 0, 0 // Short-circuit 0-len reads. + } + if nonBlockingFileReadSupported && f.IsNonblock() { + n, errno = syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) { + n, err := readSocket(fd, buf) + errno = experimentalsys.UnwrapOSError(err) + errno = fileError(f, f.closed, errno) + return n, errno + }) + } else { + n, errno = read(f.tc, buf) + } + if errno != 0 { + // Defer validation overhead until we've already had an error. + errno = fileError(f, f.closed, errno) + } + return +} + +// Write implements the same method as documented on experimentalsys.File +func (f *tcpConnFile) Write(buf []byte) (n int, errno experimentalsys.Errno) { + if nonBlockingFileWriteSupported && f.IsNonblock() { + return syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) { + n, err := writeSocket(fd, buf) + errno = experimentalsys.UnwrapOSError(err) + errno = fileError(f, f.closed, errno) + return n, errno + }) + } else { + n, errno = write(f.tc, buf) + } + if errno != 0 { + // Defer validation overhead until we've already had an error. + errno = fileError(f, f.closed, errno) + } + return +} + +// Recvfrom implements the same method as documented on socketapi.TCPConn +func (f *tcpConnFile) Recvfrom(p []byte, flags int) (n int, errno experimentalsys.Errno) { + if flags != MSG_PEEK { + errno = experimentalsys.EINVAL + return + } + return syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) { + n, err := recvfrom(fd, p, MSG_PEEK) + errno = experimentalsys.UnwrapOSError(err) + errno = fileError(f, f.closed, errno) + return n, errno + }) +} + +// Close implements the same method as documented on experimentalsys.File +func (f *tcpConnFile) Close() experimentalsys.Errno { + return f.close() +} + +func (f *tcpConnFile) close() experimentalsys.Errno { + if f.closed { + return 0 + } + f.closed = true + return f.Shutdown(socketapi.SHUT_RDWR) +} + +// SetNonblock implements the same method as documented on fsapi.File +func (f *tcpConnFile) SetNonblock(enabled bool) (errno experimentalsys.Errno) { + f.nonblock = enabled + _, errno = syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) { + return 0, experimentalsys.UnwrapOSError(setNonblockSocket(fd, enabled)) + }) + return +} + +// IsNonblock implements the same method as documented on fsapi.File +func (f *tcpConnFile) IsNonblock() bool { + return f.nonblock +} + +// Poll implements the same method as documented on fsapi.File +func (f *tcpConnFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { + return false, experimentalsys.ENOSYS +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/sock_supported.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/sock_supported.go new file mode 100644 index 00000000000..165dcf2929a --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/sock_supported.go @@ -0,0 +1,77 @@ +//go:build linux || darwin || windows + +package sysfs + +import ( + "net" + "syscall" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" + socketapi "github.com/tetratelabs/wazero/internal/sock" +) + +// Accept implements the same method as documented on socketapi.TCPSock +func (f *tcpListenerFile) Accept() (socketapi.TCPConn, experimentalsys.Errno) { + // Ensure we have an incoming connection, otherwise return immediately. + if f.nonblock { + if ready, errno := _pollSock(f.tl, fsapi.POLLIN, 0); !ready || errno != 0 { + return nil, experimentalsys.EAGAIN + } + } + + // Accept normally blocks goroutines, but we + // made sure that we have an incoming connection, + // so we should be safe. + if conn, err := f.tl.Accept(); err != nil { + return nil, experimentalsys.UnwrapOSError(err) + } else { + return newTcpConn(conn.(*net.TCPConn)), 0 + } +} + +// SetNonblock implements the same method as documented on fsapi.File +func (f *tcpListenerFile) SetNonblock(enabled bool) (errno experimentalsys.Errno) { + f.nonblock = enabled + _, errno = syscallConnControl(f.tl, func(fd uintptr) (int, experimentalsys.Errno) { + return 0, setNonblockSocket(fd, enabled) + }) + return +} + +// Shutdown implements the same method as documented on experimentalsys.Conn +func (f *tcpConnFile) Shutdown(how int) experimentalsys.Errno { + // FIXME: can userland shutdown listeners? + var err error + switch how { + case socketapi.SHUT_RD: + err = f.tc.CloseRead() + case socketapi.SHUT_WR: + err = f.tc.CloseWrite() + case socketapi.SHUT_RDWR: + return f.close() + default: + return experimentalsys.EINVAL + } + return experimentalsys.UnwrapOSError(err) +} + +// syscallConnControl extracts a syscall.RawConn from the given syscall.Conn and applies +// the given fn to a file descriptor, returning an integer or a nonzero syscall.Errno on failure. +// +// syscallConnControl streamlines the pattern of extracting the syscall.Rawconn, +// invoking its syscall.RawConn.Control method, then handling properly the errors that may occur +// within fn or returned by syscall.RawConn.Control itself. +func syscallConnControl(conn syscall.Conn, fn func(fd uintptr) (int, experimentalsys.Errno)) (n int, errno experimentalsys.Errno) { + syscallConn, err := conn.SyscallConn() + if err != nil { + return 0, experimentalsys.UnwrapOSError(err) + } + // Prioritize the inner errno over Control + if controlErr := syscallConn.Control(func(fd uintptr) { + n, errno = fn(fd) + }); errno == 0 { + errno = experimentalsys.UnwrapOSError(controlErr) + } + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/sock_unix.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/sock_unix.go new file mode 100644 index 00000000000..0f570f37b70 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/sock_unix.go @@ -0,0 +1,49 @@ +//go:build linux || darwin + +package sysfs + +import ( + "net" + "syscall" + + "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" + socketapi "github.com/tetratelabs/wazero/internal/sock" +) + +// MSG_PEEK is the constant syscall.MSG_PEEK +const MSG_PEEK = syscall.MSG_PEEK + +func newTCPListenerFile(tl *net.TCPListener) socketapi.TCPSock { + return newDefaultTCPListenerFile(tl) +} + +func _pollSock(conn syscall.Conn, flag fsapi.Pflag, timeoutMillis int32) (bool, sys.Errno) { + n, errno := syscallConnControl(conn, func(fd uintptr) (int, sys.Errno) { + if ready, errno := poll(fd, fsapi.POLLIN, 0); !ready || errno != 0 { + return -1, errno + } else { + return 0, errno + } + }) + return n >= 0, errno +} + +func setNonblockSocket(fd uintptr, enabled bool) sys.Errno { + return sys.UnwrapOSError(setNonblock(fd, enabled)) +} + +func readSocket(fd uintptr, buf []byte) (int, sys.Errno) { + n, err := syscall.Read(int(fd), buf) + return n, sys.UnwrapOSError(err) +} + +func writeSocket(fd uintptr, buf []byte) (int, sys.Errno) { + n, err := syscall.Write(int(fd), buf) + return n, sys.UnwrapOSError(err) +} + +func recvfrom(fd uintptr, buf []byte, flags int32) (n int, errno sys.Errno) { + n, _, err := syscall.Recvfrom(int(fd), buf, int(flags)) + return n, sys.UnwrapOSError(err) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/sock_unsupported.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/sock_unsupported.go new file mode 100644 index 00000000000..cd53b070a48 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/sock_unsupported.go @@ -0,0 +1,81 @@ +//go:build !(linux || darwin || windows) + +package sysfs + +import ( + "net" + "syscall" + + "github.com/tetratelabs/wazero/experimental/sys" + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" + socketapi "github.com/tetratelabs/wazero/internal/sock" +) + +// MSG_PEEK is a filler value. +const MSG_PEEK = 0x2 + +func newTCPListenerFile(tl *net.TCPListener) socketapi.TCPSock { + return &unsupportedSockFile{} +} + +type unsupportedSockFile struct { + baseSockFile +} + +// Accept implements the same method as documented on socketapi.TCPSock +func (f *unsupportedSockFile) Accept() (socketapi.TCPConn, sys.Errno) { + return nil, sys.ENOSYS +} + +func _pollSock(conn syscall.Conn, flag fsapi.Pflag, timeoutMillis int32) (bool, sys.Errno) { + return false, sys.ENOTSUP +} + +func setNonblockSocket(fd uintptr, enabled bool) sys.Errno { + return sys.ENOTSUP +} + +func readSocket(fd uintptr, buf []byte) (int, sys.Errno) { + return -1, sys.ENOTSUP +} + +func writeSocket(fd uintptr, buf []byte) (int, sys.Errno) { + return -1, sys.ENOTSUP +} + +func recvfrom(fd uintptr, buf []byte, flags int32) (n int, errno sys.Errno) { + return -1, sys.ENOTSUP +} + +// syscallConnControl extracts a syscall.RawConn from the given syscall.Conn and applies +// the given fn to a file descriptor, returning an integer or a nonzero syscall.Errno on failure. +// +// syscallConnControl streamlines the pattern of extracting the syscall.Rawconn, +// invoking its syscall.RawConn.Control method, then handling properly the errors that may occur +// within fn or returned by syscall.RawConn.Control itself. +func syscallConnControl(conn syscall.Conn, fn func(fd uintptr) (int, experimentalsys.Errno)) (n int, errno sys.Errno) { + return -1, sys.ENOTSUP +} + +// Accept implements the same method as documented on socketapi.TCPSock +func (f *tcpListenerFile) Accept() (socketapi.TCPConn, experimentalsys.Errno) { + return nil, experimentalsys.ENOSYS +} + +// Shutdown implements the same method as documented on experimentalsys.Conn +func (f *tcpConnFile) Shutdown(how int) experimentalsys.Errno { + // FIXME: can userland shutdown listeners? + var err error + switch how { + case socketapi.SHUT_RD: + err = f.tc.Close() + case socketapi.SHUT_WR: + err = f.tc.Close() + case socketapi.SHUT_RDWR: + return f.close() + default: + return experimentalsys.EINVAL + } + return experimentalsys.UnwrapOSError(err) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/sock_windows.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/sock_windows.go new file mode 100644 index 00000000000..b1e04af0388 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/sock_windows.go @@ -0,0 +1,81 @@ +//go:build windows + +package sysfs + +import ( + "net" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" + + "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" + socketapi "github.com/tetratelabs/wazero/internal/sock" +) + +const ( + // MSG_PEEK is the flag PEEK for syscall.Recvfrom on Windows. + MSG_PEEK = windows.MSG_PEEK + // _FIONBIO is the flag to set the O_NONBLOCK flag on socket handles using ioctlsocket. + _FIONBIO = 0x8004667e +) + +var ( + // modws2_32 is WinSock. + modws2_32 = syscall.NewLazyDLL("ws2_32.dll") + // procrecvfrom exposes recvfrom from WinSock. + procrecvfrom = modws2_32.NewProc("recvfrom") + // procioctlsocket exposes ioctlsocket from WinSock. + procioctlsocket = modws2_32.NewProc("ioctlsocket") +) + +func newTCPListenerFile(tl *net.TCPListener) socketapi.TCPSock { + return newDefaultTCPListenerFile(tl) +} + +// recvfrom exposes the underlying syscall in Windows. +// +// Note: since we are only using this to expose MSG_PEEK, +// we do not need really need all the parameters that are actually +// allowed in WinSock. +// We ignore `from *sockaddr` and `fromlen *int`. +func recvfrom(s uintptr, buf []byte, flags int32) (n int, errno sys.Errno) { + var _p0 *byte + if len(buf) > 0 { + _p0 = &buf[0] + } + r0, _, e1 := syscall.SyscallN( + procrecvfrom.Addr(), + s, + uintptr(unsafe.Pointer(_p0)), + uintptr(len(buf)), + uintptr(flags), + 0, // from *sockaddr (optional) + 0) // fromlen *int (optional) + return int(r0), sys.UnwrapOSError(e1) +} + +func setNonblockSocket(fd uintptr, enabled bool) sys.Errno { + opt := uint64(0) + if enabled { + opt = 1 + } + // ioctlsocket(fd, FIONBIO, &opt) + _, _, errno := syscall.SyscallN( + procioctlsocket.Addr(), + uintptr(fd), + uintptr(_FIONBIO), + uintptr(unsafe.Pointer(&opt))) + return sys.UnwrapOSError(errno) +} + +func _pollSock(conn syscall.Conn, flag fsapi.Pflag, timeoutMillis int32) (bool, sys.Errno) { + if flag != fsapi.POLLIN { + return false, sys.ENOTSUP + } + n, errno := syscallConnControl(conn, func(fd uintptr) (int, sys.Errno) { + return _poll([]pollFd{newPollFd(fd, _POLLIN, 0)}, timeoutMillis) + }) + return n > 0, errno +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/stat.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/stat.go new file mode 100644 index 00000000000..2d973b16cbf --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/stat.go @@ -0,0 +1,16 @@ +package sysfs + +import ( + "io/fs" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/sys" +) + +func defaultStatFile(f fs.File) (sys.Stat_t, experimentalsys.Errno) { + if info, err := f.Stat(); err != nil { + return sys.Stat_t{}, experimentalsys.UnwrapOSError(err) + } else { + return sys.NewStat_t(info), 0 + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/stat_supported.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/stat_supported.go new file mode 100644 index 00000000000..93b63906dc9 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/stat_supported.go @@ -0,0 +1,40 @@ +//go:build linux || darwin || freebsd || netbsd || openbsd || dragonfly || solaris + +// Note: This expression is not the same as compiler support, even if it looks +// similar. Platform functions here are used in interpreter mode as well. + +package sysfs + +import ( + "io/fs" + "os" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/sys" +) + +// dirNlinkIncludesDot is true because even though os.File filters out dot +// entries, the underlying syscall.Stat includes them. +// +// Note: this is only used in tests +const dirNlinkIncludesDot = true + +func lstat(path string) (sys.Stat_t, experimentalsys.Errno) { + if info, err := os.Lstat(path); err != nil { + return sys.Stat_t{}, experimentalsys.UnwrapOSError(err) + } else { + return sys.NewStat_t(info), 0 + } +} + +func stat(path string) (sys.Stat_t, experimentalsys.Errno) { + if info, err := os.Stat(path); err != nil { + return sys.Stat_t{}, experimentalsys.UnwrapOSError(err) + } else { + return sys.NewStat_t(info), 0 + } +} + +func statFile(f fs.File) (sys.Stat_t, experimentalsys.Errno) { + return defaultStatFile(f) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/stat_unsupported.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/stat_unsupported.go new file mode 100644 index 00000000000..d73027fab10 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/stat_unsupported.go @@ -0,0 +1,40 @@ +//go:build !(linux || darwin || freebsd || netbsd || openbsd || dragonfly || solaris || windows) + +package sysfs + +import ( + "io/fs" + "os" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/sys" +) + +// Note: go:build constraints must be the same as /sys.stat_unsupported.go for +// the same reasons. + +// dirNlinkIncludesDot might be true for some operating systems, which can have +// new stat_XX.go files as necessary. +// +// Note: this is only used in tests +const dirNlinkIncludesDot = false + +func lstat(path string) (sys.Stat_t, experimentalsys.Errno) { + if info, err := os.Lstat(path); err != nil { + return sys.Stat_t{}, experimentalsys.UnwrapOSError(err) + } else { + return sys.NewStat_t(info), 0 + } +} + +func stat(path string) (sys.Stat_t, experimentalsys.Errno) { + if info, err := os.Stat(path); err != nil { + return sys.Stat_t{}, experimentalsys.UnwrapOSError(err) + } else { + return sys.NewStat_t(info), 0 + } +} + +func statFile(f fs.File) (sys.Stat_t, experimentalsys.Errno) { + return defaultStatFile(f) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/stat_windows.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/stat_windows.go new file mode 100644 index 00000000000..23e76a98692 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/stat_windows.go @@ -0,0 +1,118 @@ +package sysfs + +import ( + "io/fs" + "syscall" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/sys" +) + +// dirNlinkIncludesDot is false because Windows does not return dot entries. +// +// Note: this is only used in tests +const dirNlinkIncludesDot = false + +func lstat(path string) (sys.Stat_t, experimentalsys.Errno) { + attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS) + // Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink. + // See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted + attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT + return statPath(attrs, path) +} + +func stat(path string) (sys.Stat_t, experimentalsys.Errno) { + attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS) + return statPath(attrs, path) +} + +func statPath(createFileAttrs uint32, path string) (sys.Stat_t, experimentalsys.Errno) { + if len(path) == 0 { + return sys.Stat_t{}, experimentalsys.ENOENT + } + pathp, err := syscall.UTF16PtrFromString(path) + if err != nil { + return sys.Stat_t{}, experimentalsys.EINVAL + } + + // open the file handle + h, err := syscall.CreateFile(pathp, 0, 0, nil, + syscall.OPEN_EXISTING, createFileAttrs, 0) + if err != nil { + errno := experimentalsys.UnwrapOSError(err) + // To match expectations of WASI, e.g. TinyGo TestStatBadDir, return + // ENOENT, not ENOTDIR. + if errno == experimentalsys.ENOTDIR { + errno = experimentalsys.ENOENT + } + return sys.Stat_t{}, errno + } + defer syscall.CloseHandle(h) + + return statHandle(h) +} + +// fdFile allows masking the `Fd` function on os.File. +type fdFile interface { + Fd() uintptr +} + +func statFile(f fs.File) (sys.Stat_t, experimentalsys.Errno) { + if osF, ok := f.(fdFile); ok { + // Attempt to get the stat by handle, which works for normal files + st, err := statHandle(syscall.Handle(osF.Fd())) + + // ERROR_INVALID_HANDLE happens before Go 1.20. Don't fail as we only + // use that approach to fill in inode data, which is not critical. + // + // Note: statHandle uses UnwrapOSError which coerces + // ERROR_INVALID_HANDLE to EBADF. + if err != experimentalsys.EBADF { + return st, err + } + } + return defaultStatFile(f) +} + +func statHandle(h syscall.Handle) (sys.Stat_t, experimentalsys.Errno) { + winFt, err := syscall.GetFileType(h) + if err != nil { + return sys.Stat_t{}, experimentalsys.UnwrapOSError(err) + } + + var fi syscall.ByHandleFileInformation + if err = syscall.GetFileInformationByHandle(h, &fi); err != nil { + return sys.Stat_t{}, experimentalsys.UnwrapOSError(err) + } + + var m fs.FileMode + if fi.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 { + m |= 0o444 + } else { + m |= 0o666 + } + + switch { // check whether this is a symlink first + case fi.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0: + m |= fs.ModeSymlink + case winFt == syscall.FILE_TYPE_PIPE: + m |= fs.ModeNamedPipe + case winFt == syscall.FILE_TYPE_CHAR: + m |= fs.ModeDevice | fs.ModeCharDevice + case fi.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0: + m |= fs.ModeDir | 0o111 // e.g. 0o444 -> 0o555 + } + + st := sys.Stat_t{} + // FileIndex{High,Low} can be combined and used as a unique identifier like inode. + // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/ns-fileapi-by_handle_file_information + st.Dev = uint64(fi.VolumeSerialNumber) + st.Ino = (uint64(fi.FileIndexHigh) << 32) | uint64(fi.FileIndexLow) + st.Mode = m + st.Nlink = uint64(fi.NumberOfLinks) + st.Size = int64(fi.FileSizeHigh)<<32 + int64(fi.FileSizeLow) + st.Atim = fi.LastAccessTime.Nanoseconds() + st.Mtim = fi.LastWriteTime.Nanoseconds() + st.Ctim = fi.CreationTime.Nanoseconds() + return st, 0 +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/sync.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/sync.go new file mode 100644 index 00000000000..86f9a0865d5 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/sync.go @@ -0,0 +1,13 @@ +//go:build !windows + +package sysfs + +import ( + "os" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +func fsync(f *os.File) sys.Errno { + return sys.UnwrapOSError(f.Sync()) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/sync_windows.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/sync_windows.go new file mode 100644 index 00000000000..f288eb25bd5 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/sync_windows.go @@ -0,0 +1,20 @@ +package sysfs + +import ( + "os" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +func fsync(f *os.File) sys.Errno { + errno := sys.UnwrapOSError(f.Sync()) + // Coerce error performing stat on a directory to 0, as it won't work + // on Windows. + switch errno { + case sys.EACCES /* Go 1.20 */, sys.EBADF /* Go 1.19 */ : + if st, err := f.Stat(); err == nil && st.IsDir() { + errno = 0 + } + } + return errno +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/syscall6_darwin.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/syscall6_darwin.go new file mode 100644 index 00000000000..9fde5baa503 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/syscall6_darwin.go @@ -0,0 +1,13 @@ +package sysfs + +import ( + "syscall" + _ "unsafe" +) + +// syscall_syscall6 is a private symbol that we link below. We need to use this +// instead of syscall.Syscall6 because the public syscall.Syscall6 won't work +// when fn is an address. +// +//go:linkname syscall_syscall6 syscall.syscall6 +func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno) diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/sysfs.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/sysfs.go new file mode 100644 index 00000000000..dd0a8882e52 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/sysfs.go @@ -0,0 +1,6 @@ +// Package sysfs includes a low-level filesystem interface and utilities needed +// for WebAssembly host functions (ABI) such as WASI. +// +// The name sysfs was chosen because wazero's public API has a "sys" package, +// which was named after https://github.com/golang/sys. +package sysfs diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/unlink_unix.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/unlink_unix.go new file mode 100644 index 00000000000..14e4a9f720a --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/unlink_unix.go @@ -0,0 +1,17 @@ +//go:build unix + +package sysfs + +import ( + "syscall" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +func unlink(name string) (errno sys.Errno) { + err := syscall.Unlink(name) + if errno = sys.UnwrapOSError(err); errno == sys.EPERM { + errno = sys.EISDIR + } + return errno +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/unlink_unsupported.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/unlink_unsupported.go new file mode 100644 index 00000000000..cc36502e818 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/unlink_unsupported.go @@ -0,0 +1,14 @@ +//go:build !(unix || windows) + +package sysfs + +import ( + "os" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +func unlink(name string) sys.Errno { + err := os.Remove(name) + return sys.UnwrapOSError(err) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/sysfs/unlink_windows.go b/vendor/github.com/tetratelabs/wazero/internal/sysfs/unlink_windows.go new file mode 100644 index 00000000000..be31c7b9115 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/sysfs/unlink_windows.go @@ -0,0 +1,25 @@ +package sysfs + +import ( + "os" + "syscall" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +func unlink(name string) sys.Errno { + err := syscall.Unlink(name) + if err == nil { + return 0 + } + errno := sys.UnwrapOSError(err) + if errno == sys.EBADF { + lstat, errLstat := os.Lstat(name) + if errLstat == nil && lstat.Mode()&os.ModeSymlink != 0 { + errno = sys.UnwrapOSError(os.Remove(name)) + } else { + errno = sys.EISDIR + } + } + return errno +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/u32/u32.go b/vendor/github.com/tetratelabs/wazero/internal/u32/u32.go new file mode 100644 index 00000000000..5960a6f0cc8 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/u32/u32.go @@ -0,0 +1,11 @@ +package u32 + +// LeBytes returns a byte slice corresponding to the 4 bytes in the uint32 in little-endian byte order. +func LeBytes(v uint32) []byte { + return []byte{ + byte(v), + byte(v >> 8), + byte(v >> 16), + byte(v >> 24), + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/u64/u64.go b/vendor/github.com/tetratelabs/wazero/internal/u64/u64.go new file mode 100644 index 00000000000..65c7cd124b4 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/u64/u64.go @@ -0,0 +1,15 @@ +package u64 + +// LeBytes returns a byte slice corresponding to the 8 bytes in the uint64 in little-endian byte order. +func LeBytes(v uint64) []byte { + return []byte{ + byte(v), + byte(v >> 8), + byte(v >> 16), + byte(v >> 24), + byte(v >> 32), + byte(v >> 40), + byte(v >> 48), + byte(v >> 56), + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/version/version.go b/vendor/github.com/tetratelabs/wazero/internal/version/version.go new file mode 100644 index 00000000000..9261df0f7e9 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/version/version.go @@ -0,0 +1,52 @@ +package version + +import ( + "runtime/debug" + "strings" +) + +// Default is the default version value used when none was found. +const Default = "dev" + +// version holds the current version from the go.mod of downstream users or set by ldflag for wazero CLI. +var version string + +// GetWazeroVersion returns the current version of wazero either in the go.mod or set by ldflag for wazero CLI. +// +// If this is not CLI, this assumes that downstream users of wazero imports wazero as "github.com/tetratelabs/wazero". +// To be precise, the returned string matches the require statement there. +// For example, if the go.mod has "require github.com/tetratelabs/wazero 0.1.2-12314124-abcd", +// then this returns "0.1.2-12314124-abcd". +// +// Note: this is tested in ./testdata/main_test.go with a separate go.mod to pretend as the wazero user. +func GetWazeroVersion() (ret string) { + if len(version) != 0 { + return version + } + + info, ok := debug.ReadBuildInfo() + if ok { + for _, dep := range info.Deps { + // Note: here's the assumption that wazero is imported as github.com/tetratelabs/wazero. + if strings.Contains(dep.Path, "github.com/tetratelabs/wazero") { + ret = dep.Version + } + } + + // In wazero CLI, wazero is a main module, so we have to get the version info from info.Main. + if versionMissing(ret) { + ret = info.Main.Version + } + } + if versionMissing(ret) { + return Default // don't return parens + } + + // Cache for the subsequent calls. + version = ret + return ret +} + +func versionMissing(ret string) bool { + return ret == "" || ret == "(devel)" // pkg.go defaults to (devel) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/args.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/args.go new file mode 100644 index 00000000000..212d3b2de26 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/args.go @@ -0,0 +1,6 @@ +package wasip1 + +const ( + ArgsGetName = "args_get" + ArgsSizesGetName = "args_sizes_get" +) diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/clock.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/clock.go new file mode 100644 index 00000000000..1d1b8c12d87 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/clock.go @@ -0,0 +1,16 @@ +package wasip1 + +const ( + ClockResGetName = "clock_res_get" + ClockTimeGetName = "clock_time_get" +) + +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clockid-enumu32 +const ( + // ClockIDRealtime is the name ID named "realtime" like sys.Walltime + ClockIDRealtime = iota + // ClockIDMonotonic is the name ID named "monotonic" like sys.Nanotime + ClockIDMonotonic + // Note: clockIDProcessCputime and clockIDThreadCputime were removed by + // WASI maintainers: https://github.com/WebAssembly/wasi-libc/pull/294 +) diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/environ.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/environ.go new file mode 100644 index 00000000000..2b0d598283c --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/environ.go @@ -0,0 +1,6 @@ +package wasip1 + +const ( + EnvironGetName = "environ_get" + EnvironSizesGetName = "environ_sizes_get" +) diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/errno.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/errno.go new file mode 100644 index 00000000000..028573d2fe7 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/errno.go @@ -0,0 +1,314 @@ +package wasip1 + +import ( + "fmt" + + "github.com/tetratelabs/wazero/experimental/sys" +) + +// Errno is neither uint16 nor an alias for parity with wasm.ValueType. +type Errno = uint32 + +// ErrnoName returns the POSIX error code name, except ErrnoSuccess, which is +// not an error. e.g. Errno2big -> "E2BIG" +func ErrnoName(errno uint32) string { + if int(errno) < len(errnoToString) { + return errnoToString[errno] + } + return fmt.Sprintf("errno(%d)", errno) +} + +// Note: Below prefers POSIX symbol names over WASI ones, even if the docs are from WASI. +// See https://linux.die.net/man/3/errno +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#variants-1 +const ( + // ErrnoSuccess No error occurred. System call completed successfully. + ErrnoSuccess Errno = iota + // Errno2big Argument list too long. + Errno2big + // ErrnoAcces Permission denied. + ErrnoAcces + // ErrnoAddrinuse Address in use. + ErrnoAddrinuse + // ErrnoAddrnotavail Address not available. + ErrnoAddrnotavail + // ErrnoAfnosupport Address family not supported. + ErrnoAfnosupport + // ErrnoAgain Resource unavailable, or operation would block. + ErrnoAgain + // ErrnoAlready Connection already in progress. + ErrnoAlready + // ErrnoBadf Bad file descriptor. + ErrnoBadf + // ErrnoBadmsg Bad message. + ErrnoBadmsg + // ErrnoBusy Device or resource busy. + ErrnoBusy + // ErrnoCanceled Operation canceled. + ErrnoCanceled + // ErrnoChild No child processes. + ErrnoChild + // ErrnoConnaborted Connection aborted. + ErrnoConnaborted + // ErrnoConnrefused Connection refused. + ErrnoConnrefused + // ErrnoConnreset Connection reset. + ErrnoConnreset + // ErrnoDeadlk Resource deadlock would occur. + ErrnoDeadlk + // ErrnoDestaddrreq Destination address required. + ErrnoDestaddrreq + // ErrnoDom Mathematics argument out of domain of function. + ErrnoDom + // ErrnoDquot Reserved. + ErrnoDquot + // ErrnoExist File exists. + ErrnoExist + // ErrnoFault Bad address. + ErrnoFault + // ErrnoFbig File too large. + ErrnoFbig + // ErrnoHostunreach Host is unreachable. + ErrnoHostunreach + // ErrnoIdrm Identifier removed. + ErrnoIdrm + // ErrnoIlseq Illegal byte sequence. + ErrnoIlseq + // ErrnoInprogress Operation in progress. + ErrnoInprogress + // ErrnoIntr Interrupted function. + ErrnoIntr + // ErrnoInval Invalid argument. + ErrnoInval + // ErrnoIo I/O error. + ErrnoIo + // ErrnoIsconn Socket is connected. + ErrnoIsconn + // ErrnoIsdir Is a directory. + ErrnoIsdir + // ErrnoLoop Too many levels of symbolic links. + ErrnoLoop + // ErrnoMfile File descriptor value too large. + ErrnoMfile + // ErrnoMlink Too many links. + ErrnoMlink + // ErrnoMsgsize Message too large. + ErrnoMsgsize + // ErrnoMultihop Reserved. + ErrnoMultihop + // ErrnoNametoolong Filename too long. + ErrnoNametoolong + // ErrnoNetdown Network is down. + ErrnoNetdown + // ErrnoNetreset Connection aborted by network. + ErrnoNetreset + // ErrnoNetunreach Network unreachable. + ErrnoNetunreach + // ErrnoNfile Too many files open in system. + ErrnoNfile + // ErrnoNobufs No buffer space available. + ErrnoNobufs + // ErrnoNodev No such device. + ErrnoNodev + // ErrnoNoent No such file or directory. + ErrnoNoent + // ErrnoNoexec Executable file format error. + ErrnoNoexec + // ErrnoNolck No locks available. + ErrnoNolck + // ErrnoNolink Reserved. + ErrnoNolink + // ErrnoNomem Not enough space. + ErrnoNomem + // ErrnoNomsg No message of the desired type. + ErrnoNomsg + // ErrnoNoprotoopt No message of the desired type. + ErrnoNoprotoopt + // ErrnoNospc No space left on device. + ErrnoNospc + // ErrnoNosys function not supported. + ErrnoNosys + // ErrnoNotconn The socket is not connected. + ErrnoNotconn + // ErrnoNotdir Not a directory or a symbolic link to a directory. + ErrnoNotdir + // ErrnoNotempty Directory not empty. + ErrnoNotempty + // ErrnoNotrecoverable State not recoverable. + ErrnoNotrecoverable + // ErrnoNotsock Not a socket. + ErrnoNotsock + // ErrnoNotsup Not supported, or operation not supported on socket. + ErrnoNotsup + // ErrnoNotty Inappropriate I/O control operation. + ErrnoNotty + // ErrnoNxio No such device or address. + ErrnoNxio + // ErrnoOverflow Value too large to be stored in data type. + ErrnoOverflow + // ErrnoOwnerdead Previous owner died. + ErrnoOwnerdead + // ErrnoPerm Operation not permitted. + ErrnoPerm + // ErrnoPipe Broken pipe. + ErrnoPipe + // ErrnoProto Protocol error. + ErrnoProto + // ErrnoProtonosupport Protocol error. + ErrnoProtonosupport + // ErrnoPrototype Protocol wrong type for socket. + ErrnoPrototype + // ErrnoRange Result too large. + ErrnoRange + // ErrnoRofs Read-only file system. + ErrnoRofs + // ErrnoSpipe Invalid seek. + ErrnoSpipe + // ErrnoSrch No such process. + ErrnoSrch + // ErrnoStale Reserved. + ErrnoStale + // ErrnoTimedout Connection timed out. + ErrnoTimedout + // ErrnoTxtbsy Text file busy. + ErrnoTxtbsy + // ErrnoXdev Cross-device link. + ErrnoXdev + + // Note: ErrnoNotcapable was removed by WASI maintainers. + // See https://github.com/WebAssembly/wasi-libc/pull/294 +) + +var errnoToString = [...]string{ + "ESUCCESS", + "E2BIG", + "EACCES", + "EADDRINUSE", + "EADDRNOTAVAIL", + "EAFNOSUPPORT", + "EAGAIN", + "EALREADY", + "EBADF", + "EBADMSG", + "EBUSY", + "ECANCELED", + "ECHILD", + "ECONNABORTED", + "ECONNREFUSED", + "ECONNRESET", + "EDEADLK", + "EDESTADDRREQ", + "EDOM", + "EDQUOT", + "EEXIST", + "EFAULT", + "EFBIG", + "EHOSTUNREACH", + "EIDRM", + "EILSEQ", + "EINPROGRESS", + "EINTR", + "EINVAL", + "EIO", + "EISCONN", + "EISDIR", + "ELOOP", + "EMFILE", + "EMLINK", + "EMSGSIZE", + "EMULTIHOP", + "ENAMETOOLONG", + "ENETDOWN", + "ENETRESET", + "ENETUNREACH", + "ENFILE", + "ENOBUFS", + "ENODEV", + "ENOENT", + "ENOEXEC", + "ENOLCK", + "ENOLINK", + "ENOMEM", + "ENOMSG", + "ENOPROTOOPT", + "ENOSPC", + "ENOSYS", + "ENOTCONN", + "ENOTDIR", + "ENOTEMPTY", + "ENOTRECOVERABLE", + "ENOTSOCK", + "ENOTSUP", + "ENOTTY", + "ENXIO", + "EOVERFLOW", + "EOWNERDEAD", + "EPERM", + "EPIPE", + "EPROTO", + "EPROTONOSUPPORT", + "EPROTOTYPE", + "ERANGE", + "EROFS", + "ESPIPE", + "ESRCH", + "ESTALE", + "ETIMEDOUT", + "ETXTBSY", + "EXDEV", + "ENOTCAPABLE", +} + +// ToErrno coerces the error to a WASI Errno. +// +// Note: Coercion isn't centralized in sys.FSContext because ABI use different +// error codes. For example, wasi-filesystem doesn't map to these +// Errno. +func ToErrno(errno sys.Errno) Errno { + switch errno { + case 0: + return ErrnoSuccess + case sys.EACCES: + return ErrnoAcces + case sys.EAGAIN: + return ErrnoAgain + case sys.EBADF: + return ErrnoBadf + case sys.EEXIST: + return ErrnoExist + case sys.EFAULT: + return ErrnoFault + case sys.EINTR: + return ErrnoIntr + case sys.EINVAL: + return ErrnoInval + case sys.EIO: + return ErrnoIo + case sys.EISDIR: + return ErrnoIsdir + case sys.ELOOP: + return ErrnoLoop + case sys.ENAMETOOLONG: + return ErrnoNametoolong + case sys.ENOENT: + return ErrnoNoent + case sys.ENOSYS: + return ErrnoNosys + case sys.ENOTDIR: + return ErrnoNotdir + case sys.ERANGE: + return ErrnoRange + case sys.ENOTEMPTY: + return ErrnoNotempty + case sys.ENOTSOCK: + return ErrnoNotsock + case sys.ENOTSUP: + return ErrnoNotsup + case sys.EPERM: + return ErrnoPerm + case sys.EROFS: + return ErrnoRofs + default: + return ErrnoIo + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/fs.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/fs.go new file mode 100644 index 00000000000..ed8df1edc44 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/fs.go @@ -0,0 +1,164 @@ +package wasip1 + +import ( + "fmt" +) + +const ( + FdAdviseName = "fd_advise" + FdAllocateName = "fd_allocate" + FdCloseName = "fd_close" + FdDatasyncName = "fd_datasync" + FdFdstatGetName = "fd_fdstat_get" + FdFdstatSetFlagsName = "fd_fdstat_set_flags" + FdFdstatSetRightsName = "fd_fdstat_set_rights" + FdFilestatGetName = "fd_filestat_get" + FdFilestatSetSizeName = "fd_filestat_set_size" + FdFilestatSetTimesName = "fd_filestat_set_times" + FdPreadName = "fd_pread" + FdPrestatGetName = "fd_prestat_get" + FdPrestatDirNameName = "fd_prestat_dir_name" + FdPwriteName = "fd_pwrite" + FdReadName = "fd_read" + FdReaddirName = "fd_readdir" + FdRenumberName = "fd_renumber" + FdSeekName = "fd_seek" + FdSyncName = "fd_sync" + FdTellName = "fd_tell" + FdWriteName = "fd_write" + + PathCreateDirectoryName = "path_create_directory" + PathFilestatGetName = "path_filestat_get" + PathFilestatSetTimesName = "path_filestat_set_times" + PathLinkName = "path_link" + PathOpenName = "path_open" + PathReadlinkName = "path_readlink" + PathRemoveDirectoryName = "path_remove_directory" + PathRenameName = "path_rename" + PathSymlinkName = "path_symlink" + PathUnlinkFileName = "path_unlink_file" +) + +// oflags are open flags used by path_open +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-oflags-flagsu16 +const ( + // O_CREAT creates a file if it does not exist. + O_CREAT uint16 = 1 << iota //nolint + // O_DIRECTORY fails if not a directory. + O_DIRECTORY + // O_EXCL fails if file already exists. + O_EXCL //nolint + // O_TRUNC truncates the file to size 0. + O_TRUNC //nolint +) + +func OflagsString(oflags int) string { + return flagsString(oflagNames[:], oflags) +} + +var oflagNames = [...]string{ + "CREAT", + "DIRECTORY", + "EXCL", + "TRUNC", +} + +// file descriptor flags +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fdflags +const ( + FD_APPEND uint16 = 1 << iota //nolint + FD_DSYNC + FD_NONBLOCK + FD_RSYNC + FD_SYNC +) + +func FdFlagsString(fdflags int) string { + return flagsString(fdflagNames[:], fdflags) +} + +var fdflagNames = [...]string{ + "APPEND", + "DSYNC", + "NONBLOCK", + "RSYNC", + "SYNC", +} + +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#lookupflags +const ( + // LOOKUP_SYMLINK_FOLLOW expands a path if it resolves into a symbolic + // link. + LOOKUP_SYMLINK_FOLLOW uint16 = 1 << iota //nolint +) + +var lookupflagNames = [...]string{ + "SYMLINK_FOLLOW", +} + +func LookupflagsString(lookupflags int) string { + return flagsString(lookupflagNames[:], lookupflags) +} + +// DirentSize is the size of the dirent struct, which should be followed by the +// length of a file name. +const DirentSize = uint32(24) + +const ( + FILETYPE_UNKNOWN uint8 = iota + FILETYPE_BLOCK_DEVICE + FILETYPE_CHARACTER_DEVICE + FILETYPE_DIRECTORY + FILETYPE_REGULAR_FILE + FILETYPE_SOCKET_DGRAM + FILETYPE_SOCKET_STREAM + FILETYPE_SYMBOLIC_LINK +) + +// FiletypeName returns string name of the file type. +func FiletypeName(filetype uint8) string { + if int(filetype) < len(filetypeToString) { + return filetypeToString[filetype] + } + return fmt.Sprintf("filetype(%d)", filetype) +} + +var filetypeToString = [...]string{ + "UNKNOWN", + "BLOCK_DEVICE", + "CHARACTER_DEVICE", + "DIRECTORY", + "REGULAR_FILE", + "SOCKET_DGRAM", + "SOCKET_STREAM", + "SYMBOLIC_LINK", +} + +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fstflags +const ( + FstflagsAtim uint16 = 1 << iota + FstflagsAtimNow + FstflagsMtim + FstflagsMtimNow +) + +var fstflagNames = [...]string{ + "ATIM", + "ATIM_NOW", + "MTIM", + "MTIM_NOW", +} + +func FstflagsString(fdflags int) string { + return flagsString(fstflagNames[:], fdflags) +} + +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-advice-enumu8 +const ( + FdAdviceNormal byte = iota + FdAdviceSequential + FdAdviceRandom + FdAdviceWillNeed + FdAdviceDontNeed + FdAdviceNoReuse +) diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/poll.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/poll.go new file mode 100644 index 00000000000..9bde768f2b3 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/poll.go @@ -0,0 +1,15 @@ +package wasip1 + +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-eventtype-enumu8 +const ( + // EventTypeClock is the timeout event named "name". + EventTypeClock = iota + // EventTypeFdRead is the data available event named "fd_read". + EventTypeFdRead + // EventTypeFdWrite is the capacity available event named "fd_write". + EventTypeFdWrite +) + +const ( + PollOneoffName = "poll_oneoff" +) diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/proc.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/proc.go new file mode 100644 index 00000000000..50b040c98e8 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/proc.go @@ -0,0 +1,6 @@ +package wasip1 + +const ( + ProcExitName = "proc_exit" + ProcRaiseName = "proc_raise" +) diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/random.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/random.go new file mode 100644 index 00000000000..2364533740f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/random.go @@ -0,0 +1,3 @@ +package wasip1 + +const RandomGetName = "random_get" diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/rights.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/rights.go new file mode 100644 index 00000000000..2ab56c60413 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/rights.go @@ -0,0 +1,148 @@ +package wasip1 + +// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-rights-flagsu64 +const ( + // RIGHT_FD_DATASYNC is the right to invoke fd_datasync. If RIGHT_PATH_OPEN + // is set, includes the right to invoke path_open with FD_DSYNC. + RIGHT_FD_DATASYNC uint32 = 1 << iota //nolint + + // RIGHT_FD_READ is he right to invoke fd_read and sock_recv. If + // RIGHT_FD_SYNC is set, includes the right to invoke fd_pread. + RIGHT_FD_READ + + // RIGHT_FD_SEEK is the right to invoke fd_seek. This flag implies + // RIGHT_FD_TELL. + RIGHT_FD_SEEK + + // RIGHT_FDSTAT_SET_FLAGS is the right to invoke fd_fdstat_set_flags. + RIGHT_FDSTAT_SET_FLAGS + + // RIGHT_FD_SYNC The right to invoke fd_sync. If path_open is set, includes + // the right to invoke path_open with FD_RSYNC and FD_DSYNC. + RIGHT_FD_SYNC + + // RIGHT_FD_TELL is the right to invoke fd_seek in such a way that the file + // offset remains unaltered (i.e., whence::cur with offset zero), or to + // invoke fd_tell. + RIGHT_FD_TELL + + // RIGHT_FD_WRITE is the right to invoke fd_write and sock_send. If + // RIGHT_FD_SEEK is set, includes the right to invoke fd_pwrite. + RIGHT_FD_WRITE + + // RIGHT_FD_ADVISE is the right to invoke fd_advise. + RIGHT_FD_ADVISE + + // RIGHT_FD_ALLOCATE is the right to invoke fd_allocate. + RIGHT_FD_ALLOCATE + + // RIGHT_PATH_CREATE_DIRECTORY is the right to invoke + // path_create_directory. + RIGHT_PATH_CREATE_DIRECTORY + + // RIGHT_PATH_CREATE_FILE when RIGHT_PATH_OPEN is set, the right to invoke + // path_open with O_CREAT. + RIGHT_PATH_CREATE_FILE + + // RIGHT_PATH_LINK_SOURCE is the right to invoke path_link with the file + // descriptor as the source directory. + RIGHT_PATH_LINK_SOURCE + + // RIGHT_PATH_LINK_TARGET is the right to invoke path_link with the file + // descriptor as the target directory. + RIGHT_PATH_LINK_TARGET + + // RIGHT_PATH_OPEN is the right to invoke path_open. + RIGHT_PATH_OPEN + + // RIGHT_FD_READDIR is the right to invoke fd_readdir. + RIGHT_FD_READDIR + + // RIGHT_PATH_READLINK is the right to invoke path_readlink. + RIGHT_PATH_READLINK + + // RIGHT_PATH_RENAME_SOURCE is the right to invoke path_rename with the + // file descriptor as the source directory. + RIGHT_PATH_RENAME_SOURCE + + // RIGHT_PATH_RENAME_TARGET is the right to invoke path_rename with the + // file descriptor as the target directory. + RIGHT_PATH_RENAME_TARGET + + // RIGHT_PATH_FILESTAT_GET is the right to invoke path_filestat_get. + RIGHT_PATH_FILESTAT_GET + + // RIGHT_PATH_FILESTAT_SET_SIZE is the right to change a file's size (there + // is no path_filestat_set_size). If RIGHT_PATH_OPEN is set, includes the + // right to invoke path_open with O_TRUNC. + RIGHT_PATH_FILESTAT_SET_SIZE + + // RIGHT_PATH_FILESTAT_SET_TIMES is the right to invoke + // path_filestat_set_times. + RIGHT_PATH_FILESTAT_SET_TIMES + + // RIGHT_FD_FILESTAT_GET is the right to invoke fd_filestat_get. + RIGHT_FD_FILESTAT_GET + + // RIGHT_FD_FILESTAT_SET_SIZE is the right to invoke fd_filestat_set_size. + RIGHT_FD_FILESTAT_SET_SIZE + + // RIGHT_FD_FILESTAT_SET_TIMES is the right to invoke + // fd_filestat_set_times. + RIGHT_FD_FILESTAT_SET_TIMES + + // RIGHT_PATH_SYMLINK is the right to invoke path_symlink. + RIGHT_PATH_SYMLINK + + // RIGHT_PATH_REMOVE_DIRECTORY is the right to invoke + // path_remove_directory. + RIGHT_PATH_REMOVE_DIRECTORY + + // RIGHT_PATH_UNLINK_FILE is the right to invoke path_unlink_file. + RIGHT_PATH_UNLINK_FILE + + // RIGHT_POLL_FD_READWRITE when RIGHT_FD_READ is set, includes the right to + // invoke poll_oneoff to subscribe to eventtype::fd_read. If RIGHT_FD_WRITE + // is set, includes the right to invoke poll_oneoff to subscribe to + // eventtype::fd_write. + RIGHT_POLL_FD_READWRITE + + // RIGHT_SOCK_SHUTDOWN is the right to invoke sock_shutdown. + RIGHT_SOCK_SHUTDOWN +) + +func RightsString(rights int) string { + return flagsString(rightNames[:], rights) +} + +var rightNames = [...]string{ + "FD_DATASYNC", + "FD_READ", + "FD_SEEK", + "FDSTAT_SET_FLAGS", + "FD_SYNC", + "FD_TELL", + "FD_WRITE", + "FD_ADVISE", + "FD_ALLOCATE", + "PATH_CREATE_DIRECTORY", + "PATH_CREATE_FILE", + "PATH_LINK_SOURCE", + "PATH_LINK_TARGET", + "PATH_OPEN", + "FD_READDIR", + "PATH_READLINK", + "PATH_RENAME_SOURCE", + "PATH_RENAME_TARGET", + "PATH_FILESTAT_GET", + "PATH_FILESTAT_SET_SIZE", + "PATH_FILESTAT_SET_TIMES", + "FD_FILESTAT_GET", + "FD_FILESTAT_SET_SIZE", + "FD_FILESTAT_SET_TIMES", + "PATH_SYMLINK", + "PATH_REMOVE_DIRECTORY", + "PATH_UNLINK_FILE", + "POLL_FD_READWRITE", + "SOCK_SHUTDOWN", +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/sched.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/sched.go new file mode 100644 index 00000000000..bc6e393858d --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/sched.go @@ -0,0 +1,3 @@ +package wasip1 + +const SchedYieldName = "sched_yield" diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/sock.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/sock.go new file mode 100644 index 00000000000..90d33ece8f6 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/sock.go @@ -0,0 +1,71 @@ +package wasip1 + +import "strconv" + +const ( + SockAcceptName = "sock_accept" + SockRecvName = "sock_recv" + SockSendName = "sock_send" + SockShutdownName = "sock_shutdown" +) + +// SD Flags indicate which channels on a socket to shut down. +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sdflags-flagsu8 +const ( + // SD_RD disables further receive operations. + SD_RD uint8 = 1 << iota //nolint + // SD_WR disables further send operations. + SD_WR +) + +func SdFlagsString(sdflags int) string { + return flagsString(sdflagNames[:], sdflags) +} + +var sdflagNames = [...]string{ + "RD", + "WR", +} + +// SI Flags are flags provided to sock_send. As there are currently no flags defined, it must be set to zero. +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-siflags-u16 + +func SiFlagsString(siflags int) string { + if siflags == 0 { + return "" + } + return strconv.Itoa(siflags) +} + +// RI Flags are flags provided to sock_recv. +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-riflags-flagsu16 +const ( + // RI_RECV_PEEK returns the message without removing it from the socket's receive queue + RI_RECV_PEEK uint8 = 1 << iota //nolint + // RI_RECV_WAITALL on byte-stream sockets, block until the full amount of data can be returned. + RI_RECV_WAITALL +) + +func RiFlagsString(riflags int) string { + return flagsString(riflagNames[:], riflags) +} + +var riflagNames = [...]string{ + "RECV_PEEK", + "RECV_WAITALL", +} + +// RO Flags are flags returned by sock_recv. +// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-roflags-flagsu16 +const ( + // RO_RECV_DATA_TRUNCATED is returned by sock_recv when message data has been truncated. + RO_RECV_DATA_TRUNCATED uint8 = 1 << iota //nolint +) + +func RoFlagsString(roflags int) string { + return flagsString(roflagNames[:], roflags) +} + +var roflagNames = [...]string{ + "RECV_DATA_TRUNCATED", +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasip1/wasi.go b/vendor/github.com/tetratelabs/wazero/internal/wasip1/wasi.go new file mode 100644 index 00000000000..299feea2f93 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasip1/wasi.go @@ -0,0 +1,26 @@ +// Package wasip1 is a helper to remove package cycles re-using constants. +package wasip1 + +import ( + "strings" +) + +// InternalModuleName is not named ModuleName, to avoid a clash on dot imports. +const InternalModuleName = "wasi_snapshot_preview1" + +func flagsString(names []string, f int) string { + var builder strings.Builder + first := true + for i, sf := range names { + target := 1 << i + if target&f != 0 { + if !first { + builder.WriteByte('|') + } else { + first = false + } + builder.WriteString(sf) + } + } + return builder.String() +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/code.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/code.go new file mode 100644 index 00000000000..2fac9196c1c --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/code.go @@ -0,0 +1,100 @@ +package binary + +import ( + "bytes" + "fmt" + "io" + "math" + + "github.com/tetratelabs/wazero/internal/leb128" + "github.com/tetratelabs/wazero/internal/wasm" +) + +func decodeCode(r *bytes.Reader, codeSectionStart uint64, ret *wasm.Code) (err error) { + ss, _, err := leb128.DecodeUint32(r) + if err != nil { + return fmt.Errorf("get the size of code: %w", err) + } + remaining := int64(ss) + + // Parse #locals. + ls, bytesRead, err := leb128.DecodeUint32(r) + remaining -= int64(bytesRead) + if err != nil { + return fmt.Errorf("get the size locals: %v", err) + } else if remaining < 0 { + return io.EOF + } + + // Validate the locals. + bytesRead = 0 + var sum uint64 + for i := uint32(0); i < ls; i++ { + num, n, err := leb128.DecodeUint32(r) + if err != nil { + return fmt.Errorf("read n of locals: %v", err) + } else if remaining < 0 { + return io.EOF + } + + sum += uint64(num) + + b, err := r.ReadByte() + if err != nil { + return fmt.Errorf("read type of local: %v", err) + } + + bytesRead += n + 1 + switch vt := b; vt { + case wasm.ValueTypeI32, wasm.ValueTypeF32, wasm.ValueTypeI64, wasm.ValueTypeF64, + wasm.ValueTypeFuncref, wasm.ValueTypeExternref, wasm.ValueTypeV128: + default: + return fmt.Errorf("invalid local type: 0x%x", vt) + } + } + + if sum > math.MaxUint32 { + return fmt.Errorf("too many locals: %d", sum) + } + + // Rewind the buffer. + _, err = r.Seek(-int64(bytesRead), io.SeekCurrent) + if err != nil { + return err + } + + localTypes := make([]wasm.ValueType, 0, sum) + for i := uint32(0); i < ls; i++ { + num, bytesRead, err := leb128.DecodeUint32(r) + remaining -= int64(bytesRead) + 1 // +1 for the subsequent ReadByte + if err != nil { + return fmt.Errorf("read n of locals: %v", err) + } else if remaining < 0 { + return io.EOF + } + + b, err := r.ReadByte() + if err != nil { + return fmt.Errorf("read type of local: %v", err) + } + + for j := uint32(0); j < num; j++ { + localTypes = append(localTypes, b) + } + } + + bodyOffsetInCodeSection := codeSectionStart - uint64(r.Len()) + body := make([]byte, remaining) + if _, err = io.ReadFull(r, body); err != nil { + return fmt.Errorf("read body: %w", err) + } + + if endIndex := len(body) - 1; endIndex < 0 || body[endIndex] != wasm.OpcodeEnd { + return fmt.Errorf("expr not end with OpcodeEnd") + } + + ret.BodyOffsetInCodeSection = bodyOffsetInCodeSection + ret.LocalTypes = localTypes + ret.Body = body + return nil +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/const_expr.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/const_expr.go new file mode 100644 index 00000000000..edfc0a086bf --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/const_expr.go @@ -0,0 +1,105 @@ +package binary + +import ( + "bytes" + "fmt" + "io" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/ieee754" + "github.com/tetratelabs/wazero/internal/leb128" + "github.com/tetratelabs/wazero/internal/wasm" +) + +func decodeConstantExpression(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.ConstantExpression) error { + b, err := r.ReadByte() + if err != nil { + return fmt.Errorf("read opcode: %v", err) + } + + remainingBeforeData := int64(r.Len()) + offsetAtData := r.Size() - remainingBeforeData + + opcode := b + switch opcode { + case wasm.OpcodeI32Const: + // Treat constants as signed as their interpretation is not yet known per /RATIONALE.md + _, _, err = leb128.DecodeInt32(r) + case wasm.OpcodeI64Const: + // Treat constants as signed as their interpretation is not yet known per /RATIONALE.md + _, _, err = leb128.DecodeInt64(r) + case wasm.OpcodeF32Const: + buf := make([]byte, 4) + if _, err := io.ReadFull(r, buf); err != nil { + return fmt.Errorf("read f32 constant: %v", err) + } + _, err = ieee754.DecodeFloat32(buf) + case wasm.OpcodeF64Const: + buf := make([]byte, 8) + if _, err := io.ReadFull(r, buf); err != nil { + return fmt.Errorf("read f64 constant: %v", err) + } + _, err = ieee754.DecodeFloat64(buf) + case wasm.OpcodeGlobalGet: + _, _, err = leb128.DecodeUint32(r) + case wasm.OpcodeRefNull: + if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil { + return fmt.Errorf("ref.null is not supported as %w", err) + } + reftype, err := r.ReadByte() + if err != nil { + return fmt.Errorf("read reference type for ref.null: %w", err) + } else if reftype != wasm.RefTypeFuncref && reftype != wasm.RefTypeExternref { + return fmt.Errorf("invalid type for ref.null: 0x%x", reftype) + } + case wasm.OpcodeRefFunc: + if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil { + return fmt.Errorf("ref.func is not supported as %w", err) + } + // Parsing index. + _, _, err = leb128.DecodeUint32(r) + case wasm.OpcodeVecPrefix: + if err := enabledFeatures.RequireEnabled(api.CoreFeatureSIMD); err != nil { + return fmt.Errorf("vector instructions are not supported as %w", err) + } + opcode, err = r.ReadByte() + if err != nil { + return fmt.Errorf("read vector instruction opcode suffix: %w", err) + } + + if opcode != wasm.OpcodeVecV128Const { + return fmt.Errorf("invalid vector opcode for const expression: %#x", opcode) + } + + remainingBeforeData = int64(r.Len()) + offsetAtData = r.Size() - remainingBeforeData + + n, err := r.Read(make([]byte, 16)) + if err != nil { + return fmt.Errorf("read vector const instruction immediates: %w", err) + } else if n != 16 { + return fmt.Errorf("read vector const instruction immediates: needs 16 bytes but was %d bytes", n) + } + default: + return fmt.Errorf("%v for const expression opt code: %#x", ErrInvalidByte, b) + } + + if err != nil { + return fmt.Errorf("read value: %v", err) + } + + if b, err = r.ReadByte(); err != nil { + return fmt.Errorf("look for end opcode: %v", err) + } + + if b != wasm.OpcodeEnd { + return fmt.Errorf("constant expression has been not terminated") + } + + ret.Data = make([]byte, remainingBeforeData-int64(r.Len())-1) + if _, err = r.ReadAt(ret.Data, offsetAtData); err != nil { + return fmt.Errorf("error re-buffering ConstantExpression.Data") + } + ret.Opcode = opcode + return nil +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/custom.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/custom.go new file mode 100644 index 00000000000..771f8c32731 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/custom.go @@ -0,0 +1,22 @@ +package binary + +import ( + "bytes" + + "github.com/tetratelabs/wazero/internal/wasm" +) + +// decodeCustomSection deserializes the data **not** associated with the "name" key in SectionIDCustom. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0 +func decodeCustomSection(r *bytes.Reader, name string, limit uint64) (result *wasm.CustomSection, err error) { + buf := make([]byte, limit) + _, err = r.Read(buf) + + result = &wasm.CustomSection{ + Name: name, + Data: buf, + } + + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/data.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/data.go new file mode 100644 index 00000000000..054ccb3c66f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/data.go @@ -0,0 +1,79 @@ +package binary + +import ( + "bytes" + "fmt" + "io" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/leb128" + "github.com/tetratelabs/wazero/internal/wasm" +) + +// dataSegmentPrefix represents three types of data segments. +// +// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-section +type dataSegmentPrefix = uint32 + +const ( + // dataSegmentPrefixActive is the prefix for the version 1.0 compatible data segment, which is classified as "active" in 2.0. + dataSegmentPrefixActive dataSegmentPrefix = 0x0 + // dataSegmentPrefixPassive prefixes the "passive" data segment as in version 2.0 specification. + dataSegmentPrefixPassive dataSegmentPrefix = 0x1 + // dataSegmentPrefixActiveWithMemoryIndex is the active prefix with memory index encoded which is defined for futur use as of 2.0. + dataSegmentPrefixActiveWithMemoryIndex dataSegmentPrefix = 0x2 +) + +func decodeDataSegment(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.DataSegment) (err error) { + dataSegmentPrefx, _, err := leb128.DecodeUint32(r) + if err != nil { + err = fmt.Errorf("read data segment prefix: %w", err) + return + } + + if dataSegmentPrefx != dataSegmentPrefixActive { + if err = enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil { + err = fmt.Errorf("non-zero prefix for data segment is invalid as %w", err) + return + } + } + + switch dataSegmentPrefx { + case dataSegmentPrefixActive, + dataSegmentPrefixActiveWithMemoryIndex: + // Active data segment as in + // https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-section + if dataSegmentPrefx == 0x2 { + d, _, err := leb128.DecodeUint32(r) + if err != nil { + return fmt.Errorf("read memory index: %v", err) + } else if d != 0 { + return fmt.Errorf("memory index must be zero but was %d", d) + } + } + + err = decodeConstantExpression(r, enabledFeatures, &ret.OffsetExpression) + if err != nil { + return fmt.Errorf("read offset expression: %v", err) + } + case dataSegmentPrefixPassive: + // Passive data segment doesn't need const expr nor memory index encoded. + // https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-section + ret.Passive = true + default: + err = fmt.Errorf("invalid data segment prefix: 0x%x", dataSegmentPrefx) + return + } + + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + err = fmt.Errorf("get the size of vector: %v", err) + return + } + + ret.Init = make([]byte, vs) + if _, err = io.ReadFull(r, ret.Init); err != nil { + err = fmt.Errorf("read bytes for init: %v", err) + } + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/decoder.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/decoder.go new file mode 100644 index 00000000000..8eca1d14986 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/decoder.go @@ -0,0 +1,225 @@ +package binary + +import ( + "bytes" + "debug/dwarf" + "errors" + "fmt" + "io" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/leb128" + "github.com/tetratelabs/wazero/internal/wasm" + "github.com/tetratelabs/wazero/internal/wasmdebug" +) + +// DecodeModule implements wasm.DecodeModule for the WebAssembly 1.0 (20191205) Binary Format +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-format%E2%91%A0 +func DecodeModule( + binary []byte, + enabledFeatures api.CoreFeatures, + memoryLimitPages uint32, + memoryCapacityFromMax, + dwarfEnabled, storeCustomSections bool, +) (*wasm.Module, error) { + r := bytes.NewReader(binary) + + // Magic number. + buf := make([]byte, 4) + if _, err := io.ReadFull(r, buf); err != nil || !bytes.Equal(buf, Magic) { + return nil, ErrInvalidMagicNumber + } + + // Version. + if _, err := io.ReadFull(r, buf); err != nil || !bytes.Equal(buf, version) { + return nil, ErrInvalidVersion + } + + memSizer := newMemorySizer(memoryLimitPages, memoryCapacityFromMax) + + m := &wasm.Module{} + var lastSectionID wasm.SectionID + var info, line, str, abbrev, ranges []byte // For DWARF Data. + for { + // TODO: except custom sections, all others are required to be in order, but we aren't checking yet. + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#modules%E2%91%A0%E2%93%AA + sectionID, err := r.ReadByte() + if err == io.EOF { + break + } else if err != nil { + return nil, fmt.Errorf("read section id: %w", err) + } + + sectionSize, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get size of section %s: %v", wasm.SectionIDName(sectionID), err) + } + + var ok bool + lastSectionID, ok = checkSectionOrder(sectionID, lastSectionID) + if !ok { + return nil, errors.New("invalid section order") + } + + sectionContentStart := r.Len() + switch sectionID { + case wasm.SectionIDCustom: + // First, validate the section and determine if the section for this name has already been set + name, nameSize, decodeErr := decodeUTF8(r, "custom section name") + if decodeErr != nil { + err = decodeErr + break + } else if sectionSize < nameSize { + err = fmt.Errorf("malformed custom section %s", name) + break + } else if name == "name" && m.NameSection != nil { + err = fmt.Errorf("redundant custom section %s", name) + break + } + + // Now, either decode the NameSection or CustomSection + limit := sectionSize - nameSize + + var c *wasm.CustomSection + if name != "name" { + if storeCustomSections || dwarfEnabled { + c, err = decodeCustomSection(r, name, uint64(limit)) + if err != nil { + return nil, fmt.Errorf("failed to read custom section name[%s]: %w", name, err) + } + m.CustomSections = append(m.CustomSections, c) + if dwarfEnabled { + switch name { + case ".debug_info": + info = c.Data + case ".debug_line": + line = c.Data + case ".debug_str": + str = c.Data + case ".debug_abbrev": + abbrev = c.Data + case ".debug_ranges": + ranges = c.Data + } + } + } else { + if _, err = io.CopyN(io.Discard, r, int64(limit)); err != nil { + return nil, fmt.Errorf("failed to skip name[%s]: %w", name, err) + } + } + } else { + m.NameSection, err = decodeNameSection(r, uint64(limit)) + } + case wasm.SectionIDType: + m.TypeSection, err = decodeTypeSection(enabledFeatures, r) + case wasm.SectionIDImport: + m.ImportSection, m.ImportPerModule, m.ImportFunctionCount, m.ImportGlobalCount, m.ImportMemoryCount, m.ImportTableCount, err = decodeImportSection(r, memSizer, memoryLimitPages, enabledFeatures) + if err != nil { + return nil, err // avoid re-wrapping the error. + } + case wasm.SectionIDFunction: + m.FunctionSection, err = decodeFunctionSection(r) + case wasm.SectionIDTable: + m.TableSection, err = decodeTableSection(r, enabledFeatures) + case wasm.SectionIDMemory: + m.MemorySection, err = decodeMemorySection(r, enabledFeatures, memSizer, memoryLimitPages) + case wasm.SectionIDGlobal: + if m.GlobalSection, err = decodeGlobalSection(r, enabledFeatures); err != nil { + return nil, err // avoid re-wrapping the error. + } + case wasm.SectionIDExport: + m.ExportSection, m.Exports, err = decodeExportSection(r) + case wasm.SectionIDStart: + m.StartSection, err = decodeStartSection(r) + case wasm.SectionIDElement: + m.ElementSection, err = decodeElementSection(r, enabledFeatures) + case wasm.SectionIDCode: + m.CodeSection, err = decodeCodeSection(r) + case wasm.SectionIDData: + m.DataSection, err = decodeDataSection(r, enabledFeatures) + case wasm.SectionIDDataCount: + if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil { + return nil, fmt.Errorf("data count section not supported as %v", err) + } + m.DataCountSection, err = decodeDataCountSection(r) + default: + err = ErrInvalidSectionID + } + + readBytes := sectionContentStart - r.Len() + if err == nil && int(sectionSize) != readBytes { + err = fmt.Errorf("invalid section length: expected to be %d but got %d", sectionSize, readBytes) + } + + if err != nil { + return nil, fmt.Errorf("section %s: %v", wasm.SectionIDName(sectionID), err) + } + } + + if dwarfEnabled { + d, _ := dwarf.New(abbrev, nil, nil, info, line, nil, ranges, str) + m.DWARFLines = wasmdebug.NewDWARFLines(d) + } + + functionCount, codeCount := m.SectionElementCount(wasm.SectionIDFunction), m.SectionElementCount(wasm.SectionIDCode) + if functionCount != codeCount { + return nil, fmt.Errorf("function and code section have inconsistent lengths: %d != %d", functionCount, codeCount) + } + return m, nil +} + +func checkSectionOrder(current, previous wasm.SectionID) (byte, bool) { + // https://webassembly.github.io/spec/core/binary/modules.html#binary-module + + // Custom sections can show up anywhere. + if current == wasm.SectionIDCustom { + return previous, true + } + + // DataCount was introduced in Wasm 2.0, + // and it's the maximum we support so far. + // It must come after Element and before Code. + if current > wasm.SectionIDDataCount { + return current, false + } + if current == wasm.SectionIDDataCount { + return current, previous <= wasm.SectionIDElement + } + if previous == wasm.SectionIDDataCount { + return current, current >= wasm.SectionIDCode + } + + // Tag will be introduced in Wasm 3.0. + // It must come after Memory and before Global. + + // Otherwise, strictly increasing order. + return current, current > previous +} + +// memorySizer derives min, capacity and max pages from decoded wasm. +type memorySizer func(minPages uint32, maxPages *uint32) (min uint32, capacity uint32, max uint32) + +// newMemorySizer sets capacity to minPages unless max is defined and +// memoryCapacityFromMax is true. +func newMemorySizer(memoryLimitPages uint32, memoryCapacityFromMax bool) memorySizer { + return func(minPages uint32, maxPages *uint32) (min, capacity, max uint32) { + if maxPages != nil { + if memoryCapacityFromMax { + return minPages, *maxPages, *maxPages + } + // This is an invalid value: let it propagate, we will fail later. + if *maxPages > wasm.MemoryLimitPages { + return minPages, minPages, *maxPages + } + // This is a valid value, but it goes over the run-time limit: return the limit. + if *maxPages > memoryLimitPages { + return minPages, minPages, memoryLimitPages + } + return minPages, minPages, *maxPages + } + if memoryCapacityFromMax { + return minPages, memoryLimitPages, memoryLimitPages + } + return minPages, minPages, memoryLimitPages + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/element.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/element.go new file mode 100644 index 00000000000..7ab4b48eb4d --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/element.go @@ -0,0 +1,269 @@ +package binary + +import ( + "bytes" + "errors" + "fmt" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/leb128" + "github.com/tetratelabs/wazero/internal/wasm" +) + +func ensureElementKindFuncRef(r *bytes.Reader) error { + elemKind, err := r.ReadByte() + if err != nil { + return fmt.Errorf("read element prefix: %w", err) + } + if elemKind != 0x0 { // ElemKind is fixed to 0x0 now: https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#element-section + return fmt.Errorf("element kind must be zero but was 0x%x", elemKind) + } + return nil +} + +func decodeElementInitValueVector(r *bytes.Reader) ([]wasm.Index, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get size of vector: %w", err) + } + + vec := make([]wasm.Index, vs) + for i := range vec { + u32, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("read function index: %w", err) + } + + if u32 >= wasm.MaximumFunctionIndex { + return nil, fmt.Errorf("too large function index in Element init: %d", u32) + } + vec[i] = u32 + } + return vec, nil +} + +func decodeElementConstExprVector(r *bytes.Reader, elemType wasm.RefType, enabledFeatures api.CoreFeatures) ([]wasm.Index, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("failed to get the size of constexpr vector: %w", err) + } + vec := make([]wasm.Index, vs) + for i := range vec { + var expr wasm.ConstantExpression + err := decodeConstantExpression(r, enabledFeatures, &expr) + if err != nil { + return nil, err + } + switch expr.Opcode { + case wasm.OpcodeRefFunc: + if elemType != wasm.RefTypeFuncref { + return nil, fmt.Errorf("element type mismatch: want %s, but constexpr has funcref", wasm.RefTypeName(elemType)) + } + v, _, _ := leb128.LoadUint32(expr.Data) + if v >= wasm.MaximumFunctionIndex { + return nil, fmt.Errorf("too large function index in Element init: %d", v) + } + vec[i] = v + case wasm.OpcodeRefNull: + if elemType != expr.Data[0] { + return nil, fmt.Errorf("element type mismatch: want %s, but constexpr has %s", + wasm.RefTypeName(elemType), wasm.RefTypeName(expr.Data[0])) + } + vec[i] = wasm.ElementInitNullReference + case wasm.OpcodeGlobalGet: + i32, _, _ := leb128.LoadInt32(expr.Data) + // Resolving the reference type from globals is done at instantiation phase. See the comment on + // wasm.elementInitImportedGlobalReferenceType. + vec[i] = wasm.WrapGlobalIndexAsElementInit(wasm.Index(i32)) + default: + return nil, fmt.Errorf("const expr must be either ref.null or ref.func but was %s", wasm.InstructionName(expr.Opcode)) + } + } + return vec, nil +} + +func decodeElementRefType(r *bytes.Reader) (ret wasm.RefType, err error) { + ret, err = r.ReadByte() + if err != nil { + err = fmt.Errorf("read element ref type: %w", err) + return + } + if ret != wasm.RefTypeFuncref && ret != wasm.RefTypeExternref { + return 0, errors.New("ref type must be funcref or externref for element as of WebAssembly 2.0") + } + return +} + +const ( + // The prefix is explained at https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#element-section + + // elementSegmentPrefixLegacy is the legacy prefix and is only valid one before CoreFeatureBulkMemoryOperations. + elementSegmentPrefixLegacy = iota + // elementSegmentPrefixPassiveFuncrefValueVector is the passive element whose indexes are encoded as vec(varint), and reftype is fixed to funcref. + elementSegmentPrefixPassiveFuncrefValueVector + // elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex is the same as elementSegmentPrefixPassiveFuncrefValueVector but active and table index is encoded. + elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex + // elementSegmentPrefixDeclarativeFuncrefValueVector is the same as elementSegmentPrefixPassiveFuncrefValueVector but declarative. + elementSegmentPrefixDeclarativeFuncrefValueVector + // elementSegmentPrefixActiveFuncrefConstExprVector is active whoce reftype is fixed to funcref and indexes are encoded as vec(const_expr). + elementSegmentPrefixActiveFuncrefConstExprVector + // elementSegmentPrefixPassiveConstExprVector is passive whoce indexes are encoded as vec(const_expr), and reftype is encoded. + elementSegmentPrefixPassiveConstExprVector + // elementSegmentPrefixPassiveConstExprVector is active whoce indexes are encoded as vec(const_expr), and reftype and table index are encoded. + elementSegmentPrefixActiveConstExprVector + // elementSegmentPrefixDeclarativeConstExprVector is declarative whoce indexes are encoded as vec(const_expr), and reftype is encoded. + elementSegmentPrefixDeclarativeConstExprVector +) + +func decodeElementSegment(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.ElementSegment) error { + prefix, _, err := leb128.DecodeUint32(r) + if err != nil { + return fmt.Errorf("read element prefix: %w", err) + } + + if prefix != elementSegmentPrefixLegacy { + if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil { + return fmt.Errorf("non-zero prefix for element segment is invalid as %w", err) + } + } + + // Encoding depends on the prefix and described at https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#element-section + switch prefix { + case elementSegmentPrefixLegacy: + // Legacy prefix which is WebAssembly 1.0 compatible. + err = decodeConstantExpression(r, enabledFeatures, &ret.OffsetExpr) + if err != nil { + return fmt.Errorf("read expr for offset: %w", err) + } + + ret.Init, err = decodeElementInitValueVector(r) + if err != nil { + return err + } + + ret.Mode = wasm.ElementModeActive + ret.Type = wasm.RefTypeFuncref + return nil + case elementSegmentPrefixPassiveFuncrefValueVector: + // Prefix 1 requires funcref. + if err = ensureElementKindFuncRef(r); err != nil { + return err + } + + ret.Init, err = decodeElementInitValueVector(r) + if err != nil { + return err + } + ret.Mode = wasm.ElementModePassive + ret.Type = wasm.RefTypeFuncref + return nil + case elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex: + ret.TableIndex, _, err = leb128.DecodeUint32(r) + if err != nil { + return fmt.Errorf("get size of vector: %w", err) + } + + if ret.TableIndex != 0 { + if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil { + return fmt.Errorf("table index must be zero but was %d: %w", ret.TableIndex, err) + } + } + + err := decodeConstantExpression(r, enabledFeatures, &ret.OffsetExpr) + if err != nil { + return fmt.Errorf("read expr for offset: %w", err) + } + + // Prefix 2 requires funcref. + if err = ensureElementKindFuncRef(r); err != nil { + return err + } + + ret.Init, err = decodeElementInitValueVector(r) + if err != nil { + return err + } + + ret.Mode = wasm.ElementModeActive + ret.Type = wasm.RefTypeFuncref + return nil + case elementSegmentPrefixDeclarativeFuncrefValueVector: + // Prefix 3 requires funcref. + if err = ensureElementKindFuncRef(r); err != nil { + return err + } + ret.Init, err = decodeElementInitValueVector(r) + if err != nil { + return err + } + ret.Type = wasm.RefTypeFuncref + ret.Mode = wasm.ElementModeDeclarative + return nil + case elementSegmentPrefixActiveFuncrefConstExprVector: + err := decodeConstantExpression(r, enabledFeatures, &ret.OffsetExpr) + if err != nil { + return fmt.Errorf("read expr for offset: %w", err) + } + + ret.Init, err = decodeElementConstExprVector(r, wasm.RefTypeFuncref, enabledFeatures) + if err != nil { + return err + } + ret.Mode = wasm.ElementModeActive + ret.Type = wasm.RefTypeFuncref + return nil + case elementSegmentPrefixPassiveConstExprVector: + ret.Type, err = decodeElementRefType(r) + if err != nil { + return err + } + ret.Init, err = decodeElementConstExprVector(r, ret.Type, enabledFeatures) + if err != nil { + return err + } + ret.Mode = wasm.ElementModePassive + return nil + case elementSegmentPrefixActiveConstExprVector: + ret.TableIndex, _, err = leb128.DecodeUint32(r) + if err != nil { + return fmt.Errorf("get size of vector: %w", err) + } + + if ret.TableIndex != 0 { + if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil { + return fmt.Errorf("table index must be zero but was %d: %w", ret.TableIndex, err) + } + } + err := decodeConstantExpression(r, enabledFeatures, &ret.OffsetExpr) + if err != nil { + return fmt.Errorf("read expr for offset: %w", err) + } + + ret.Type, err = decodeElementRefType(r) + if err != nil { + return err + } + + ret.Init, err = decodeElementConstExprVector(r, ret.Type, enabledFeatures) + if err != nil { + return err + } + + ret.Mode = wasm.ElementModeActive + return nil + case elementSegmentPrefixDeclarativeConstExprVector: + ret.Type, err = decodeElementRefType(r) + if err != nil { + return err + } + ret.Init, err = decodeElementConstExprVector(r, ret.Type, enabledFeatures) + if err != nil { + return err + } + + ret.Mode = wasm.ElementModeDeclarative + return nil + default: + return fmt.Errorf("invalid element segment prefix: 0x%x", prefix) + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/errors.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/errors.go new file mode 100644 index 00000000000..b9125b0387d --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/errors.go @@ -0,0 +1,11 @@ +package binary + +import "errors" + +var ( + ErrInvalidByte = errors.New("invalid byte") + ErrInvalidMagicNumber = errors.New("invalid magic number") + ErrInvalidVersion = errors.New("invalid version header") + ErrInvalidSectionID = errors.New("invalid section id") + ErrCustomSectionNotFound = errors.New("custom section not found") +) diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/export.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/export.go new file mode 100644 index 00000000000..925e9c499fb --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/export.go @@ -0,0 +1,32 @@ +package binary + +import ( + "bytes" + "fmt" + + "github.com/tetratelabs/wazero/internal/leb128" + "github.com/tetratelabs/wazero/internal/wasm" +) + +func decodeExport(r *bytes.Reader, ret *wasm.Export) (err error) { + if ret.Name, _, err = decodeUTF8(r, "export name"); err != nil { + return + } + + b, err := r.ReadByte() + if err != nil { + err = fmt.Errorf("error decoding export kind: %w", err) + return + } + + ret.Type = b + switch ret.Type { + case wasm.ExternTypeFunc, wasm.ExternTypeTable, wasm.ExternTypeMemory, wasm.ExternTypeGlobal: + if ret.Index, _, err = leb128.DecodeUint32(r); err != nil { + err = fmt.Errorf("error decoding export index: %w", err) + } + default: + err = fmt.Errorf("%w: invalid byte for exportdesc: %#x", ErrInvalidByte, b) + } + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/function.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/function.go new file mode 100644 index 00000000000..bb9e2b649e9 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/function.go @@ -0,0 +1,56 @@ +package binary + +import ( + "bytes" + "fmt" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/leb128" + "github.com/tetratelabs/wazero/internal/wasm" +) + +func decodeFunctionType(enabledFeatures api.CoreFeatures, r *bytes.Reader, ret *wasm.FunctionType) (err error) { + b, err := r.ReadByte() + if err != nil { + return fmt.Errorf("read leading byte: %w", err) + } + + if b != 0x60 { + return fmt.Errorf("%w: %#x != 0x60", ErrInvalidByte, b) + } + + paramCount, _, err := leb128.DecodeUint32(r) + if err != nil { + return fmt.Errorf("could not read parameter count: %w", err) + } + + paramTypes, err := decodeValueTypes(r, paramCount) + if err != nil { + return fmt.Errorf("could not read parameter types: %w", err) + } + + resultCount, _, err := leb128.DecodeUint32(r) + if err != nil { + return fmt.Errorf("could not read result count: %w", err) + } + + // Guard >1.0 feature multi-value + if resultCount > 1 { + if err = enabledFeatures.RequireEnabled(api.CoreFeatureMultiValue); err != nil { + return fmt.Errorf("multiple result types invalid as %v", err) + } + } + + resultTypes, err := decodeValueTypes(r, resultCount) + if err != nil { + return fmt.Errorf("could not read result types: %w", err) + } + + ret.Params = paramTypes + ret.Results = resultTypes + + // cache the key for the function type + _ = ret.String() + + return nil +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/global.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/global.go new file mode 100644 index 00000000000..4e1c16fda4c --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/global.go @@ -0,0 +1,50 @@ +package binary + +import ( + "bytes" + "fmt" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/wasm" +) + +// decodeGlobal returns the api.Global decoded with the WebAssembly 1.0 (20191205) Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-global +func decodeGlobal(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.Global) (err error) { + ret.Type, err = decodeGlobalType(r) + if err != nil { + return err + } + + err = decodeConstantExpression(r, enabledFeatures, &ret.Init) + return +} + +// decodeGlobalType returns the wasm.GlobalType decoded with the WebAssembly 1.0 (20191205) Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-globaltype +func decodeGlobalType(r *bytes.Reader) (wasm.GlobalType, error) { + vt, err := decodeValueTypes(r, 1) + if err != nil { + return wasm.GlobalType{}, fmt.Errorf("read value type: %w", err) + } + + ret := wasm.GlobalType{ + ValType: vt[0], + } + + b, err := r.ReadByte() + if err != nil { + return wasm.GlobalType{}, fmt.Errorf("read mutablity: %w", err) + } + + switch mut := b; mut { + case 0x00: // not mutable + case 0x01: // mutable + ret.Mutable = true + default: + return wasm.GlobalType{}, fmt.Errorf("%w for mutability: %#x != 0x00 or 0x01", ErrInvalidByte, mut) + } + return ret, nil +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/header.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/header.go new file mode 100644 index 00000000000..29ba1b599e4 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/header.go @@ -0,0 +1,9 @@ +package binary + +// Magic is the 4 byte preamble (literally "\0asm") of the binary format +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-magic +var Magic = []byte{0x00, 0x61, 0x73, 0x6D} + +// version is format version and doesn't change between known specification versions +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-version +var version = []byte{0x01, 0x00, 0x00, 0x00} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/import.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/import.go new file mode 100644 index 00000000000..39d310c5572 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/import.go @@ -0,0 +1,52 @@ +package binary + +import ( + "bytes" + "fmt" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/leb128" + "github.com/tetratelabs/wazero/internal/wasm" +) + +func decodeImport( + r *bytes.Reader, + idx uint32, + memorySizer memorySizer, + memoryLimitPages uint32, + enabledFeatures api.CoreFeatures, + ret *wasm.Import, +) (err error) { + if ret.Module, _, err = decodeUTF8(r, "import module"); err != nil { + err = fmt.Errorf("import[%d] error decoding module: %w", idx, err) + return + } + + if ret.Name, _, err = decodeUTF8(r, "import name"); err != nil { + err = fmt.Errorf("import[%d] error decoding name: %w", idx, err) + return + } + + b, err := r.ReadByte() + if err != nil { + err = fmt.Errorf("import[%d] error decoding type: %w", idx, err) + return + } + ret.Type = b + switch ret.Type { + case wasm.ExternTypeFunc: + ret.DescFunc, _, err = leb128.DecodeUint32(r) + case wasm.ExternTypeTable: + err = decodeTable(r, enabledFeatures, &ret.DescTable) + case wasm.ExternTypeMemory: + ret.DescMem, err = decodeMemory(r, enabledFeatures, memorySizer, memoryLimitPages) + case wasm.ExternTypeGlobal: + ret.DescGlobal, err = decodeGlobalType(r) + default: + err = fmt.Errorf("%w: invalid byte for importdesc: %#x", ErrInvalidByte, b) + } + if err != nil { + err = fmt.Errorf("import[%d] %s[%s.%s]: %w", idx, wasm.ExternTypeName(ret.Type), ret.Module, ret.Name, err) + } + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/limits.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/limits.go new file mode 100644 index 00000000000..ff2d73b5fd4 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/limits.go @@ -0,0 +1,47 @@ +package binary + +import ( + "bytes" + "fmt" + + "github.com/tetratelabs/wazero/internal/leb128" +) + +// decodeLimitsType returns the `limitsType` (min, max) decoded with the WebAssembly 1.0 (20191205) Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#limits%E2%91%A6 +// +// Extended in threads proposal: https://webassembly.github.io/threads/core/binary/types.html#limits +func decodeLimitsType(r *bytes.Reader) (min uint32, max *uint32, shared bool, err error) { + var flag byte + if flag, err = r.ReadByte(); err != nil { + err = fmt.Errorf("read leading byte: %v", err) + return + } + + switch flag { + case 0x00, 0x02: + min, _, err = leb128.DecodeUint32(r) + if err != nil { + err = fmt.Errorf("read min of limit: %v", err) + } + case 0x01, 0x03: + min, _, err = leb128.DecodeUint32(r) + if err != nil { + err = fmt.Errorf("read min of limit: %v", err) + return + } + var m uint32 + if m, _, err = leb128.DecodeUint32(r); err != nil { + err = fmt.Errorf("read max of limit: %v", err) + } else { + max = &m + } + default: + err = fmt.Errorf("%v for limits: %#x not in (0x00, 0x01, 0x02, 0x03)", ErrInvalidByte, flag) + } + + shared = flag == 0x02 || flag == 0x03 + + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/memory.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/memory.go new file mode 100644 index 00000000000..e1b1751238d --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/memory.go @@ -0,0 +1,42 @@ +package binary + +import ( + "bytes" + "fmt" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental" + "github.com/tetratelabs/wazero/internal/wasm" +) + +// decodeMemory returns the api.Memory decoded with the WebAssembly 1.0 (20191205) Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-memory +func decodeMemory( + r *bytes.Reader, + enabledFeatures api.CoreFeatures, + memorySizer func(minPages uint32, maxPages *uint32) (min, capacity, max uint32), + memoryLimitPages uint32, +) (*wasm.Memory, error) { + min, maxP, shared, err := decodeLimitsType(r) + if err != nil { + return nil, err + } + + if shared { + if !enabledFeatures.IsEnabled(experimental.CoreFeaturesThreads) { + return nil, fmt.Errorf("shared memory requested but threads feature not enabled") + } + + // This restriction may be lifted in the future. + // https://webassembly.github.io/threads/core/binary/types.html#memory-types + if maxP == nil { + return nil, fmt.Errorf("shared memory requires a maximum size to be specified") + } + } + + min, capacity, max := memorySizer(min, maxP) + mem := &wasm.Memory{Min: min, Cap: capacity, Max: max, IsMaxEncoded: maxP != nil, IsShared: shared} + + return mem, mem.Validate(memoryLimitPages) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/names.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/names.go new file mode 100644 index 00000000000..56fb96dc82c --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/names.go @@ -0,0 +1,151 @@ +package binary + +import ( + "bytes" + "fmt" + "io" + + "github.com/tetratelabs/wazero/internal/leb128" + "github.com/tetratelabs/wazero/internal/wasm" +) + +const ( + // subsectionIDModuleName contains only the module name. + subsectionIDModuleName = uint8(0) + // subsectionIDFunctionNames is a map of indices to function names, in ascending order by function index + subsectionIDFunctionNames = uint8(1) + // subsectionIDLocalNames contain a map of function indices to a map of local indices to their names, in ascending + // order by function and local index + subsectionIDLocalNames = uint8(2) +) + +// decodeNameSection deserializes the data associated with the "name" key in SectionIDCustom according to the +// standard: +// +// * ModuleName decode from subsection 0 +// * FunctionNames decode from subsection 1 +// * LocalNames decode from subsection 2 +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namesec +func decodeNameSection(r *bytes.Reader, limit uint64) (result *wasm.NameSection, err error) { + // TODO: add leb128 functions that work on []byte and offset. While using a reader allows us to reuse reader-based + // leb128 functions, it is less efficient, causes untestable code and in some cases more complex vs plain []byte. + result = &wasm.NameSection{} + + // subsectionID is decoded if known, and skipped if not + var subsectionID uint8 + // subsectionSize is the length to skip when the subsectionID is unknown + var subsectionSize uint32 + var bytesRead uint64 + for limit > 0 { + if subsectionID, err = r.ReadByte(); err != nil { + if err == io.EOF { + return result, nil + } + // TODO: untestable as this can't fail for a reason beside EOF reading a byte from a buffer + return nil, fmt.Errorf("failed to read a subsection ID: %w", err) + } + limit-- + + if subsectionSize, bytesRead, err = leb128.DecodeUint32(r); err != nil { + return nil, fmt.Errorf("failed to read the size of subsection[%d]: %w", subsectionID, err) + } + limit -= bytesRead + + switch subsectionID { + case subsectionIDModuleName: + if result.ModuleName, _, err = decodeUTF8(r, "module name"); err != nil { + return nil, err + } + case subsectionIDFunctionNames: + if result.FunctionNames, err = decodeFunctionNames(r); err != nil { + return nil, err + } + case subsectionIDLocalNames: + if result.LocalNames, err = decodeLocalNames(r); err != nil { + return nil, err + } + default: // Skip other subsections. + // Note: Not Seek because it doesn't err when given an offset past EOF. Rather, it leads to undefined state. + if _, err = io.CopyN(io.Discard, r, int64(subsectionSize)); err != nil { + return nil, fmt.Errorf("failed to skip subsection[%d]: %w", subsectionID, err) + } + } + limit -= uint64(subsectionSize) + } + return +} + +func decodeFunctionNames(r *bytes.Reader) (wasm.NameMap, error) { + functionCount, err := decodeFunctionCount(r, subsectionIDFunctionNames) + if err != nil { + return nil, err + } + + result := make(wasm.NameMap, functionCount) + for i := uint32(0); i < functionCount; i++ { + functionIndex, err := decodeFunctionIndex(r, subsectionIDFunctionNames) + if err != nil { + return nil, err + } + + name, _, err := decodeUTF8(r, "function[%d] name", functionIndex) + if err != nil { + return nil, err + } + result[i] = wasm.NameAssoc{Index: functionIndex, Name: name} + } + return result, nil +} + +func decodeLocalNames(r *bytes.Reader) (wasm.IndirectNameMap, error) { + functionCount, err := decodeFunctionCount(r, subsectionIDLocalNames) + if err != nil { + return nil, err + } + + result := make(wasm.IndirectNameMap, functionCount) + for i := uint32(0); i < functionCount; i++ { + functionIndex, err := decodeFunctionIndex(r, subsectionIDLocalNames) + if err != nil { + return nil, err + } + + localCount, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("failed to read the local count for function[%d]: %w", functionIndex, err) + } + + locals := make(wasm.NameMap, localCount) + for j := uint32(0); j < localCount; j++ { + localIndex, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("failed to read a local index of function[%d]: %w", functionIndex, err) + } + + name, _, err := decodeUTF8(r, "function[%d] local[%d] name", functionIndex, localIndex) + if err != nil { + return nil, err + } + locals[j] = wasm.NameAssoc{Index: localIndex, Name: name} + } + result[i] = wasm.NameMapAssoc{Index: functionIndex, NameMap: locals} + } + return result, nil +} + +func decodeFunctionIndex(r *bytes.Reader, subsectionID uint8) (uint32, error) { + functionIndex, _, err := leb128.DecodeUint32(r) + if err != nil { + return 0, fmt.Errorf("failed to read a function index in subsection[%d]: %w", subsectionID, err) + } + return functionIndex, nil +} + +func decodeFunctionCount(r *bytes.Reader, subsectionID uint8) (uint32, error) { + functionCount, _, err := leb128.DecodeUint32(r) + if err != nil { + return 0, fmt.Errorf("failed to read the function count of subsection[%d]: %w", subsectionID, err) + } + return functionCount, nil +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/section.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/section.go new file mode 100644 index 00000000000..622ee5923e8 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/section.go @@ -0,0 +1,226 @@ +package binary + +import ( + "bytes" + "fmt" + "io" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/leb128" + "github.com/tetratelabs/wazero/internal/wasm" +) + +func decodeTypeSection(enabledFeatures api.CoreFeatures, r *bytes.Reader) ([]wasm.FunctionType, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get size of vector: %w", err) + } + + result := make([]wasm.FunctionType, vs) + for i := uint32(0); i < vs; i++ { + if err = decodeFunctionType(enabledFeatures, r, &result[i]); err != nil { + return nil, fmt.Errorf("read %d-th type: %v", i, err) + } + } + return result, nil +} + +// decodeImportSection decodes the decoded import segments plus the count per wasm.ExternType. +func decodeImportSection( + r *bytes.Reader, + memorySizer memorySizer, + memoryLimitPages uint32, + enabledFeatures api.CoreFeatures, +) (result []wasm.Import, + perModule map[string][]*wasm.Import, + funcCount, globalCount, memoryCount, tableCount wasm.Index, err error, +) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + err = fmt.Errorf("get size of vector: %w", err) + return + } + + perModule = make(map[string][]*wasm.Import) + result = make([]wasm.Import, vs) + for i := uint32(0); i < vs; i++ { + imp := &result[i] + if err = decodeImport(r, i, memorySizer, memoryLimitPages, enabledFeatures, imp); err != nil { + return + } + switch imp.Type { + case wasm.ExternTypeFunc: + imp.IndexPerType = funcCount + funcCount++ + case wasm.ExternTypeGlobal: + imp.IndexPerType = globalCount + globalCount++ + case wasm.ExternTypeMemory: + imp.IndexPerType = memoryCount + memoryCount++ + case wasm.ExternTypeTable: + imp.IndexPerType = tableCount + tableCount++ + } + perModule[imp.Module] = append(perModule[imp.Module], imp) + } + return +} + +func decodeFunctionSection(r *bytes.Reader) ([]uint32, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get size of vector: %w", err) + } + + result := make([]uint32, vs) + for i := uint32(0); i < vs; i++ { + if result[i], _, err = leb128.DecodeUint32(r); err != nil { + return nil, fmt.Errorf("get type index: %w", err) + } + } + return result, err +} + +func decodeTableSection(r *bytes.Reader, enabledFeatures api.CoreFeatures) ([]wasm.Table, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("error reading size") + } + if vs > 1 { + if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil { + return nil, fmt.Errorf("at most one table allowed in module as %w", err) + } + } + + ret := make([]wasm.Table, vs) + for i := range ret { + err = decodeTable(r, enabledFeatures, &ret[i]) + if err != nil { + return nil, err + } + } + return ret, nil +} + +func decodeMemorySection( + r *bytes.Reader, + enabledFeatures api.CoreFeatures, + memorySizer memorySizer, + memoryLimitPages uint32, +) (*wasm.Memory, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("error reading size") + } + if vs > 1 { + return nil, fmt.Errorf("at most one memory allowed in module, but read %d", vs) + } else if vs == 0 { + // memory count can be zero. + return nil, nil + } + + return decodeMemory(r, enabledFeatures, memorySizer, memoryLimitPages) +} + +func decodeGlobalSection(r *bytes.Reader, enabledFeatures api.CoreFeatures) ([]wasm.Global, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get size of vector: %w", err) + } + + result := make([]wasm.Global, vs) + for i := uint32(0); i < vs; i++ { + if err = decodeGlobal(r, enabledFeatures, &result[i]); err != nil { + return nil, fmt.Errorf("global[%d]: %w", i, err) + } + } + return result, nil +} + +func decodeExportSection(r *bytes.Reader) ([]wasm.Export, map[string]*wasm.Export, error) { + vs, _, sizeErr := leb128.DecodeUint32(r) + if sizeErr != nil { + return nil, nil, fmt.Errorf("get size of vector: %v", sizeErr) + } + + exportMap := make(map[string]*wasm.Export, vs) + exportSection := make([]wasm.Export, vs) + for i := wasm.Index(0); i < vs; i++ { + export := &exportSection[i] + err := decodeExport(r, export) + if err != nil { + return nil, nil, fmt.Errorf("read export: %w", err) + } + if _, ok := exportMap[export.Name]; ok { + return nil, nil, fmt.Errorf("export[%d] duplicates name %q", i, export.Name) + } else { + exportMap[export.Name] = export + } + } + return exportSection, exportMap, nil +} + +func decodeStartSection(r *bytes.Reader) (*wasm.Index, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get function index: %w", err) + } + return &vs, nil +} + +func decodeElementSection(r *bytes.Reader, enabledFeatures api.CoreFeatures) ([]wasm.ElementSegment, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get size of vector: %w", err) + } + + result := make([]wasm.ElementSegment, vs) + for i := uint32(0); i < vs; i++ { + if err = decodeElementSegment(r, enabledFeatures, &result[i]); err != nil { + return nil, fmt.Errorf("read element: %w", err) + } + } + return result, nil +} + +func decodeCodeSection(r *bytes.Reader) ([]wasm.Code, error) { + codeSectionStart := uint64(r.Len()) + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get size of vector: %w", err) + } + + result := make([]wasm.Code, vs) + for i := uint32(0); i < vs; i++ { + err = decodeCode(r, codeSectionStart, &result[i]) + if err != nil { + return nil, fmt.Errorf("read %d-th code segment: %v", i, err) + } + } + return result, nil +} + +func decodeDataSection(r *bytes.Reader, enabledFeatures api.CoreFeatures) ([]wasm.DataSegment, error) { + vs, _, err := leb128.DecodeUint32(r) + if err != nil { + return nil, fmt.Errorf("get size of vector: %w", err) + } + + result := make([]wasm.DataSegment, vs) + for i := uint32(0); i < vs; i++ { + if err = decodeDataSegment(r, enabledFeatures, &result[i]); err != nil { + return nil, fmt.Errorf("read data segment: %w", err) + } + } + return result, nil +} + +func decodeDataCountSection(r *bytes.Reader) (count *uint32, err error) { + v, _, err := leb128.DecodeUint32(r) + if err != nil && err != io.EOF { + // data count is optional, so EOF is fine. + return nil, err + } + return &v, nil +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/table.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/table.go new file mode 100644 index 00000000000..353ec75662e --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/table.go @@ -0,0 +1,43 @@ +package binary + +import ( + "bytes" + "fmt" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/wasm" +) + +// decodeTable returns the wasm.Table decoded with the WebAssembly 1.0 (20191205) Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-table +func decodeTable(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.Table) (err error) { + ret.Type, err = r.ReadByte() + if err != nil { + return fmt.Errorf("read leading byte: %v", err) + } + + if ret.Type != wasm.RefTypeFuncref { + if err = enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil { + return fmt.Errorf("table type funcref is invalid: %w", err) + } + } + + var shared bool + ret.Min, ret.Max, shared, err = decodeLimitsType(r) + if err != nil { + return fmt.Errorf("read limits: %v", err) + } + if ret.Min > wasm.MaximumFunctionIndex { + return fmt.Errorf("table min must be at most %d", wasm.MaximumFunctionIndex) + } + if ret.Max != nil { + if *ret.Max < ret.Min { + return fmt.Errorf("table size minimum must not be greater than maximum") + } + } + if shared { + return fmt.Errorf("tables cannot be marked as shared") + } + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/value.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/value.go new file mode 100644 index 00000000000..dfc4417edc1 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/binary/value.go @@ -0,0 +1,59 @@ +package binary + +import ( + "bytes" + "fmt" + "io" + "unicode/utf8" + "unsafe" + + "github.com/tetratelabs/wazero/internal/leb128" + "github.com/tetratelabs/wazero/internal/wasm" +) + +func decodeValueTypes(r *bytes.Reader, num uint32) ([]wasm.ValueType, error) { + if num == 0 { + return nil, nil + } + + ret := make([]wasm.ValueType, num) + _, err := io.ReadFull(r, ret) + if err != nil { + return nil, err + } + + for _, v := range ret { + switch v { + case wasm.ValueTypeI32, wasm.ValueTypeF32, wasm.ValueTypeI64, wasm.ValueTypeF64, + wasm.ValueTypeExternref, wasm.ValueTypeFuncref, wasm.ValueTypeV128: + default: + return nil, fmt.Errorf("invalid value type: %d", v) + } + } + return ret, nil +} + +// decodeUTF8 decodes a size prefixed string from the reader, returning it and the count of bytes read. +// contextFormat and contextArgs apply an error format when present +func decodeUTF8(r *bytes.Reader, contextFormat string, contextArgs ...interface{}) (string, uint32, error) { + size, sizeOfSize, err := leb128.DecodeUint32(r) + if err != nil { + return "", 0, fmt.Errorf("failed to read %s size: %w", fmt.Sprintf(contextFormat, contextArgs...), err) + } + + if size == 0 { + return "", uint32(sizeOfSize), nil + } + + buf := make([]byte, size) + if _, err = io.ReadFull(r, buf); err != nil { + return "", 0, fmt.Errorf("failed to read %s: %w", fmt.Sprintf(contextFormat, contextArgs...), err) + } + + if !utf8.Valid(buf) { + return "", 0, fmt.Errorf("%s is not valid UTF-8", fmt.Sprintf(contextFormat, contextArgs...)) + } + + ret := unsafe.String(&buf[0], int(size)) + return ret, size + uint32(sizeOfSize), nil +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/counts.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/counts.go new file mode 100644 index 00000000000..685a409419b --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/counts.go @@ -0,0 +1,51 @@ +package wasm + +import "fmt" + +// SectionElementCount returns the count of elements in a given section ID +// +// For example... +// * SectionIDType returns the count of FunctionType +// * SectionIDCustom returns the count of CustomSections plus one if NameSection is present +// * SectionIDHostFunction returns the count of HostFunctionSection +// * SectionIDExport returns the count of unique export names +func (m *Module) SectionElementCount(sectionID SectionID) uint32 { // element as in vector elements! + switch sectionID { + case SectionIDCustom: + numCustomSections := uint32(len(m.CustomSections)) + if m.NameSection != nil { + numCustomSections++ + } + return numCustomSections + case SectionIDType: + return uint32(len(m.TypeSection)) + case SectionIDImport: + return uint32(len(m.ImportSection)) + case SectionIDFunction: + return uint32(len(m.FunctionSection)) + case SectionIDTable: + return uint32(len(m.TableSection)) + case SectionIDMemory: + if m.MemorySection != nil { + return 1 + } + return 0 + case SectionIDGlobal: + return uint32(len(m.GlobalSection)) + case SectionIDExport: + return uint32(len(m.ExportSection)) + case SectionIDStart: + if m.StartSection != nil { + return 1 + } + return 0 + case SectionIDElement: + return uint32(len(m.ElementSection)) + case SectionIDCode: + return uint32(len(m.CodeSection)) + case SectionIDData: + return uint32(len(m.DataSection)) + default: + panic(fmt.Errorf("BUG: unknown section: %d", sectionID)) + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/engine.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/engine.go new file mode 100644 index 00000000000..8c387e9e12f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/engine.go @@ -0,0 +1,76 @@ +package wasm + +import ( + "context" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental" +) + +// Engine is a Store-scoped mechanism to compile functions declared or imported by a module. +// This is a top-level type implemented by an interpreter or compiler. +type Engine interface { + // Close closes this engine, and releases all the compiled cache. + Close() (err error) + + // CompileModule implements the same method as documented on wasm.Engine. + CompileModule(ctx context.Context, module *Module, listeners []experimental.FunctionListener, ensureTermination bool) error + + // CompiledModuleCount is exported for testing, to track the size of the compilation cache. + CompiledModuleCount() uint32 + + // DeleteCompiledModule releases compilation caches for the given module (source). + // Note: it is safe to call this function for a module from which module instances are instantiated even when these + // module instances have outstanding calls. + DeleteCompiledModule(module *Module) + + // NewModuleEngine compiles down the function instances in a module, and returns ModuleEngine for the module. + // + // * module is the source module from which moduleFunctions are instantiated. This is used for caching. + // * instance is the *ModuleInstance which is created from `module`. + // + // Note: Input parameters must be pre-validated with wasm.Module Validate, to ensure no fields are invalid + // due to reasons such as out-of-bounds. + NewModuleEngine(module *Module, instance *ModuleInstance) (ModuleEngine, error) +} + +// ModuleEngine implements function calls for a given module. +type ModuleEngine interface { + // DoneInstantiation is called at the end of the instantiation of the module. + DoneInstantiation() + + // NewFunction returns an api.Function for the given function pointed by the given Index. + NewFunction(index Index) api.Function + + // ResolveImportedFunction is used to add imported functions needed to make this ModuleEngine fully functional. + // - `index` is the function Index of this imported function. + // - `descFunc` is the type Index in Module.TypeSection of this imported function. It corresponds to Import.DescFunc. + // - `indexInImportedModule` is the function Index of the imported function in the imported module. + // - `importedModuleEngine` is the ModuleEngine for the imported ModuleInstance. + ResolveImportedFunction(index, descFunc, indexInImportedModule Index, importedModuleEngine ModuleEngine) + + // ResolveImportedMemory is called when this module imports a memory from another module. + ResolveImportedMemory(importedModuleEngine ModuleEngine) + + // LookupFunction returns the FunctionModule and the Index of the function in the returned ModuleInstance at the given offset in the table. + LookupFunction(t *TableInstance, typeId FunctionTypeID, tableOffset Index) (*ModuleInstance, Index) + + // GetGlobalValue returns the value of the global variable at the given Index. + // Only called when OwnsGlobals() returns true, and must not be called for imported globals + GetGlobalValue(idx Index) (lo, hi uint64) + + // SetGlobalValue sets the value of the global variable at the given Index. + // Only called when OwnsGlobals() returns true, and must not be called for imported globals + SetGlobalValue(idx Index, lo, hi uint64) + + // OwnsGlobals returns true if this ModuleEngine owns the global variables. If true, wasm.GlobalInstance's Val,ValHi should + // not be accessed directly. + OwnsGlobals() bool + + // FunctionInstanceReference returns Reference for the given Index for a FunctionInstance. The returned values are used by + // the initialization via ElementSegment. + FunctionInstanceReference(funcIndex Index) Reference + + // MemoryGrown notifies the engine that the memory has grown. + MemoryGrown() +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/func_validation.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/func_validation.go new file mode 100644 index 00000000000..b0e7f7cfbce --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/func_validation.go @@ -0,0 +1,2378 @@ +package wasm + +import ( + "bytes" + "errors" + "fmt" + "slices" + "strconv" + "strings" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental" + "github.com/tetratelabs/wazero/internal/leb128" +) + +// The wazero specific limitation described at RATIONALE.md. +const maximumValuesOnStack = 1 << 27 + +// validateFunction validates the instruction sequence of a function. +// following the specification https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#instructions%E2%91%A2. +// +// * idx is the index in the FunctionSection +// * functions are the function index, which is prefixed by imports. The value is the TypeSection index. +// * globals are the global index, which is prefixed by imports. +// * memory is the potentially imported memory and can be nil. +// * table is the potentially imported table and can be nil. +// * declaredFunctionIndexes is the set of function indexes declared by declarative element segments which can be acceed by OpcodeRefFunc instruction. +// +// Returns an error if the instruction sequence is not valid, +// or potentially it can exceed the maximum number of values on the stack. +func (m *Module) validateFunction(sts *stacks, enabledFeatures api.CoreFeatures, idx Index, functions []Index, + globals []GlobalType, memory *Memory, tables []Table, declaredFunctionIndexes map[Index]struct{}, br *bytes.Reader, +) error { + return m.validateFunctionWithMaxStackValues(sts, enabledFeatures, idx, functions, globals, memory, tables, maximumValuesOnStack, declaredFunctionIndexes, br) +} + +func readMemArg(pc uint64, body []byte) (align, offset uint32, read uint64, err error) { + align, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + err = fmt.Errorf("read memory align: %v", err) + return + } + if align >= 32 { + // Prevent 1< 32/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI32) + case OpcodeF32Load: + if 1< 32/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeF32) + case OpcodeI32Store: + if 1< 32/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + case OpcodeF32Store: + if 1< 32/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + case OpcodeI64Load: + if 1< 64/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI64) + case OpcodeF64Load: + if 1< 64/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeF64) + case OpcodeI64Store: + if 1< 64/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + case OpcodeF64Store: + if 1< 64/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + case OpcodeI32Load8S: + if 1< 1 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI32) + case OpcodeI32Load8U: + if 1< 1 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI32) + case OpcodeI64Load8S, OpcodeI64Load8U: + if 1< 1 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI64) + case OpcodeI32Store8: + if 1< 1 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + case OpcodeI64Store8: + if 1< 1 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + case OpcodeI32Load16S, OpcodeI32Load16U: + if 1< 16/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI32) + case OpcodeI64Load16S, OpcodeI64Load16U: + if 1< 16/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI64) + case OpcodeI32Store16: + if 1< 16/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + case OpcodeI64Store16: + if 1< 16/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + case OpcodeI64Load32S, OpcodeI64Load32U: + if 1< 32/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI64) + case OpcodeI64Store32: + if 1< 32/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + } + } else if OpcodeMemorySize <= op && op <= OpcodeMemoryGrow { + if memory == nil { + return fmt.Errorf("memory must exist for %s", InstructionName(op)) + } + pc++ + val, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("read immediate: %v", err) + } + if val != 0 || num != 1 { + return fmt.Errorf("memory instruction reserved bytes not zero with 1 byte") + } + switch Opcode(op) { + case OpcodeMemoryGrow: + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI32) + case OpcodeMemorySize: + valueTypeStack.push(ValueTypeI32) + } + pc += num - 1 + } else if OpcodeI32Const <= op && op <= OpcodeF64Const { + pc++ + switch Opcode(op) { + case OpcodeI32Const: + _, num, err := leb128.LoadInt32(body[pc:]) + if err != nil { + return fmt.Errorf("read i32 immediate: %s", err) + } + pc += num - 1 + valueTypeStack.push(ValueTypeI32) + case OpcodeI64Const: + _, num, err := leb128.LoadInt64(body[pc:]) + if err != nil { + return fmt.Errorf("read i64 immediate: %v", err) + } + valueTypeStack.push(ValueTypeI64) + pc += num - 1 + case OpcodeF32Const: + valueTypeStack.push(ValueTypeF32) + pc += 3 + case OpcodeF64Const: + valueTypeStack.push(ValueTypeF64) + pc += 7 + } + } else if OpcodeLocalGet <= op && op <= OpcodeGlobalSet { + pc++ + index, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("read immediate: %v", err) + } + pc += num - 1 + switch op { + case OpcodeLocalGet: + inputLen := uint32(len(functionType.Params)) + if l := uint32(len(localTypes)) + inputLen; index >= l { + return fmt.Errorf("invalid local index for %s %d >= %d(=len(locals)+len(parameters))", + OpcodeLocalGetName, index, l) + } + if index < inputLen { + valueTypeStack.push(functionType.Params[index]) + } else { + valueTypeStack.push(localTypes[index-inputLen]) + } + case OpcodeLocalSet: + inputLen := uint32(len(functionType.Params)) + if l := uint32(len(localTypes)) + inputLen; index >= l { + return fmt.Errorf("invalid local index for %s %d >= %d(=len(locals)+len(parameters))", + OpcodeLocalSetName, index, l) + } + var expType ValueType + if index < inputLen { + expType = functionType.Params[index] + } else { + expType = localTypes[index-inputLen] + } + if err := valueTypeStack.popAndVerifyType(expType); err != nil { + return err + } + case OpcodeLocalTee: + inputLen := uint32(len(functionType.Params)) + if l := uint32(len(localTypes)) + inputLen; index >= l { + return fmt.Errorf("invalid local index for %s %d >= %d(=len(locals)+len(parameters))", + OpcodeLocalTeeName, index, l) + } + var expType ValueType + if index < inputLen { + expType = functionType.Params[index] + } else { + expType = localTypes[index-inputLen] + } + if err := valueTypeStack.popAndVerifyType(expType); err != nil { + return err + } + valueTypeStack.push(expType) + case OpcodeGlobalGet: + if index >= uint32(len(globals)) { + return fmt.Errorf("invalid index for %s", OpcodeGlobalGetName) + } + valueTypeStack.push(globals[index].ValType) + case OpcodeGlobalSet: + if index >= uint32(len(globals)) { + return fmt.Errorf("invalid global index") + } else if !globals[index].Mutable { + return fmt.Errorf("%s when not mutable", OpcodeGlobalSetName) + } else if err := valueTypeStack.popAndVerifyType( + globals[index].ValType); err != nil { + return err + } + } + } else if op == OpcodeBr { + pc++ + index, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("read immediate: %v", err) + } else if int(index) >= len(controlBlockStack.stack) { + return fmt.Errorf("invalid %s operation: index out of range", OpcodeBrName) + } + pc += num - 1 + // Check type soundness. + target := &controlBlockStack.stack[len(controlBlockStack.stack)-int(index)-1] + var targetResultType []ValueType + if target.op == OpcodeLoop { + targetResultType = target.blockType.Params + } else { + targetResultType = target.blockType.Results + } + if err = valueTypeStack.popResults(op, targetResultType, false); err != nil { + return err + } + // br instruction is stack-polymorphic. + valueTypeStack.unreachable() + } else if op == OpcodeBrIf { + pc++ + index, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("read immediate: %v", err) + } else if int(index) >= len(controlBlockStack.stack) { + return fmt.Errorf( + "invalid ln param given for %s: index=%d with %d for the current label stack length", + OpcodeBrIfName, index, len(controlBlockStack.stack)) + } + pc += num - 1 + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the required operand for %s", OpcodeBrIfName) + } + // Check type soundness. + target := &controlBlockStack.stack[len(controlBlockStack.stack)-int(index)-1] + var targetResultType []ValueType + if target.op == OpcodeLoop { + targetResultType = target.blockType.Params + } else { + targetResultType = target.blockType.Results + } + if err := valueTypeStack.popResults(op, targetResultType, false); err != nil { + return err + } + // Push back the result + for _, t := range targetResultType { + valueTypeStack.push(t) + } + } else if op == OpcodeBrTable { + pc++ + br.Reset(body[pc:]) + nl, num, err := leb128.DecodeUint32(br) + if err != nil { + return fmt.Errorf("read immediate: %w", err) + } + + sts.ls = sts.ls[:0] + for i := uint32(0); i < nl; i++ { + l, n, err := leb128.DecodeUint32(br) + if err != nil { + return fmt.Errorf("read immediate: %w", err) + } + num += n + sts.ls = append(sts.ls, l) + } + ln, n, err := leb128.DecodeUint32(br) + if err != nil { + return fmt.Errorf("read immediate: %w", err) + } else if int(ln) >= len(controlBlockStack.stack) { + return fmt.Errorf( + "invalid ln param given for %s: ln=%d with %d for the current label stack length", + OpcodeBrTableName, ln, len(controlBlockStack.stack)) + } + pc += n + num - 1 + // Check type soundness. + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the required operand for %s", OpcodeBrTableName) + } + lnLabel := &controlBlockStack.stack[len(controlBlockStack.stack)-1-int(ln)] + var defaultLabelType []ValueType + // Below, we might modify the slice in case of unreachable. Therefore, + // we have to copy the content of block result types, otherwise the original + // function type might result in invalid value types if the block is the outermost label + // which equals the function's type. + if lnLabel.op != OpcodeLoop { // Loop operation doesn't require results since the continuation is the beginning of the loop. + defaultLabelType = slices.Clone(lnLabel.blockType.Results) + } else { + defaultLabelType = slices.Clone(lnLabel.blockType.Params) + } + + if enabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes) { + // As of reference-types proposal, br_table on unreachable state + // can choose unknown types for expected parameter types for each label. + // https://github.com/WebAssembly/reference-types/pull/116 + for i := range defaultLabelType { + index := len(defaultLabelType) - 1 - i + exp := defaultLabelType[index] + actual, err := valueTypeStack.pop() + if err != nil { + return err + } + if actual == valueTypeUnknown { + // Re-assign the expected type to unknown. + defaultLabelType[index] = valueTypeUnknown + } else if actual != exp { + return typeMismatchError(true, OpcodeBrTableName, actual, exp, i) + } + } + } else { + if err = valueTypeStack.popResults(op, defaultLabelType, false); err != nil { + return err + } + } + + for _, l := range sts.ls { + if int(l) >= len(controlBlockStack.stack) { + return fmt.Errorf("invalid l param given for %s", OpcodeBrTableName) + } + label := &controlBlockStack.stack[len(controlBlockStack.stack)-1-int(l)] + var tableLabelType []ValueType + if label.op != OpcodeLoop { + tableLabelType = label.blockType.Results + } else { + tableLabelType = label.blockType.Params + } + if len(defaultLabelType) != len(tableLabelType) { + return fmt.Errorf("inconsistent block type length for %s at %d; %v (ln=%d) != %v (l=%d)", OpcodeBrTableName, l, defaultLabelType, ln, tableLabelType, l) + } + for i := range defaultLabelType { + if defaultLabelType[i] != valueTypeUnknown && defaultLabelType[i] != tableLabelType[i] { + return fmt.Errorf("incosistent block type for %s at %d", OpcodeBrTableName, l) + } + } + } + + // br_table instruction is stack-polymorphic. + valueTypeStack.unreachable() + } else if op == OpcodeCall || op == OpcodeTailCallReturnCall { + pc++ + index, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("read immediate: %v", err) + } + pc += num - 1 + if int(index) >= len(functions) { + return fmt.Errorf("invalid function index") + } + + var opcodeName string + if op == OpcodeCall { + opcodeName = OpcodeCallName + } else { + opcodeName = OpcodeTailCallReturnCallName + } + + funcType := &m.TypeSection[functions[index]] + for i := 0; i < len(funcType.Params); i++ { + if err := valueTypeStack.popAndVerifyType(funcType.Params[len(funcType.Params)-1-i]); err != nil { + return fmt.Errorf("type mismatch on %s operation param type: %v", opcodeName, err) + } + } + for _, exp := range funcType.Results { + valueTypeStack.push(exp) + } + if op == OpcodeTailCallReturnCall { + if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesTailCall); err != nil { + return fmt.Errorf("%s invalid as %v", OpcodeTailCallReturnCallName, err) + } + // Same formatting as OpcodeEnd on the outer-most block + if err := valueTypeStack.requireStackValues(false, "", functionType.Results, false); err != nil { + return err + } + // behaves as a jump. + valueTypeStack.unreachable() + } + } else if op == OpcodeCallIndirect || op == OpcodeTailCallReturnCallIndirect { + pc++ + typeIndex, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("read immediate: %v", err) + } + pc += num + + var opcodeName string + if op == OpcodeCallIndirect { + opcodeName = OpcodeCallIndirectName + } else { + opcodeName = OpcodeTailCallReturnCallIndirectName + } + + if int(typeIndex) >= len(m.TypeSection) { + return fmt.Errorf("invalid type index at %s: %d", opcodeName, typeIndex) + } + + tableIndex, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("read table index: %v", err) + } + pc += num - 1 + if tableIndex != 0 { + if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil { + return fmt.Errorf("table index must be zero but was %d: %w", tableIndex, err) + } + } + + if tableIndex >= uint32(len(tables)) { + return fmt.Errorf("unknown table index: %d", tableIndex) + } + + table := tables[tableIndex] + if table.Type != RefTypeFuncref { + return fmt.Errorf("table is not funcref type but was %s for %s", RefTypeName(table.Type), opcodeName) + } + + if err = valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the offset in table for %s", opcodeName) + } + funcType := &m.TypeSection[typeIndex] + for i := 0; i < len(funcType.Params); i++ { + if err = valueTypeStack.popAndVerifyType(funcType.Params[len(funcType.Params)-1-i]); err != nil { + return fmt.Errorf("type mismatch on %s operation input type", opcodeName) + } + } + for _, exp := range funcType.Results { + valueTypeStack.push(exp) + } + + if op == OpcodeTailCallReturnCallIndirect { + if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesTailCall); err != nil { + return fmt.Errorf("%s invalid as %v", OpcodeTailCallReturnCallIndirectName, err) + } + // Same formatting as OpcodeEnd on the outer-most block + if err := valueTypeStack.requireStackValues(false, "", functionType.Results, false); err != nil { + return err + } + // behaves as a jump. + valueTypeStack.unreachable() + } + } else if OpcodeI32Eqz <= op && op <= OpcodeI64Extend32S { + switch op { + case OpcodeI32Eqz: + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI32EqzName, err) + } + valueTypeStack.push(ValueTypeI32) + case OpcodeI32Eq, OpcodeI32Ne, OpcodeI32LtS, + OpcodeI32LtU, OpcodeI32GtS, OpcodeI32GtU, OpcodeI32LeS, + OpcodeI32LeU, OpcodeI32GeS, OpcodeI32GeU: + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the 1st i32 operand for %s: %v", InstructionName(op), err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the 2nd i32 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeI32) + case OpcodeI64Eqz: + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI64EqzName, err) + } + valueTypeStack.push(ValueTypeI32) + case OpcodeI64Eq, OpcodeI64Ne, OpcodeI64LtS, + OpcodeI64LtU, OpcodeI64GtS, OpcodeI64GtU, + OpcodeI64LeS, OpcodeI64LeU, OpcodeI64GeS, OpcodeI64GeU: + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return fmt.Errorf("cannot pop the 1st i64 operand for %s: %v", InstructionName(op), err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return fmt.Errorf("cannot pop the 2nd i64 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeI32) + case OpcodeF32Eq, OpcodeF32Ne, OpcodeF32Lt, OpcodeF32Gt, OpcodeF32Le, OpcodeF32Ge: + if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil { + return fmt.Errorf("cannot pop the 1st f32 operand for %s: %v", InstructionName(op), err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil { + return fmt.Errorf("cannot pop the 2nd f32 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeI32) + case OpcodeF64Eq, OpcodeF64Ne, OpcodeF64Lt, OpcodeF64Gt, OpcodeF64Le, OpcodeF64Ge: + if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil { + return fmt.Errorf("cannot pop the 1st f64 operand for %s: %v", InstructionName(op), err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil { + return fmt.Errorf("cannot pop the 2nd f64 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeI32) + case OpcodeI32Clz, OpcodeI32Ctz, OpcodeI32Popcnt: + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the i32 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeI32) + case OpcodeI32Add, OpcodeI32Sub, OpcodeI32Mul, OpcodeI32DivS, + OpcodeI32DivU, OpcodeI32RemS, OpcodeI32RemU, OpcodeI32And, + OpcodeI32Or, OpcodeI32Xor, OpcodeI32Shl, OpcodeI32ShrS, + OpcodeI32ShrU, OpcodeI32Rotl, OpcodeI32Rotr: + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the 1st operand for %s: %v", InstructionName(op), err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the 2nd operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeI32) + case OpcodeI64Clz, OpcodeI64Ctz, OpcodeI64Popcnt: + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return fmt.Errorf("cannot pop the i64 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeI64) + case OpcodeI64Add, OpcodeI64Sub, OpcodeI64Mul, OpcodeI64DivS, + OpcodeI64DivU, OpcodeI64RemS, OpcodeI64RemU, OpcodeI64And, + OpcodeI64Or, OpcodeI64Xor, OpcodeI64Shl, OpcodeI64ShrS, + OpcodeI64ShrU, OpcodeI64Rotl, OpcodeI64Rotr: + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return fmt.Errorf("cannot pop the 1st i64 operand for %s: %v", InstructionName(op), err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return fmt.Errorf("cannot pop the 2nd i64 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeI64) + case OpcodeF32Abs, OpcodeF32Neg, OpcodeF32Ceil, + OpcodeF32Floor, OpcodeF32Trunc, OpcodeF32Nearest, + OpcodeF32Sqrt: + if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil { + return fmt.Errorf("cannot pop the 1st f32 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeF32) + case OpcodeF32Add, OpcodeF32Sub, OpcodeF32Mul, + OpcodeF32Div, OpcodeF32Min, OpcodeF32Max, + OpcodeF32Copysign: + if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil { + return fmt.Errorf("cannot pop the 1st f32 operand for %s: %v", InstructionName(op), err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil { + return fmt.Errorf("cannot pop the 2nd f32 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeF32) + case OpcodeF64Abs, OpcodeF64Neg, OpcodeF64Ceil, + OpcodeF64Floor, OpcodeF64Trunc, OpcodeF64Nearest, + OpcodeF64Sqrt: + if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil { + return fmt.Errorf("cannot pop the 1st f64 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeF64) + case OpcodeF64Add, OpcodeF64Sub, OpcodeF64Mul, + OpcodeF64Div, OpcodeF64Min, OpcodeF64Max, + OpcodeF64Copysign: + if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil { + return fmt.Errorf("cannot pop the 1st f64 operand for %s: %v", InstructionName(op), err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil { + return fmt.Errorf("cannot pop the 2nd f64 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeF64) + case OpcodeI32WrapI64: + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI32WrapI64Name, err) + } + valueTypeStack.push(ValueTypeI32) + case OpcodeI32TruncF32S, OpcodeI32TruncF32U: + if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil { + return fmt.Errorf("cannot pop the f32 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeI32) + case OpcodeI32TruncF64S, OpcodeI32TruncF64U: + if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil { + return fmt.Errorf("cannot pop the f64 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeI32) + case OpcodeI64ExtendI32S, OpcodeI64ExtendI32U: + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the i32 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeI64) + case OpcodeI64TruncF32S, OpcodeI64TruncF32U: + if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil { + return fmt.Errorf("cannot pop the f32 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeI64) + case OpcodeI64TruncF64S, OpcodeI64TruncF64U: + if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil { + return fmt.Errorf("cannot pop the f64 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeI64) + case OpcodeF32ConvertI32S, OpcodeF32ConvertI32U: + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the i32 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeF32) + case OpcodeF32ConvertI64S, OpcodeF32ConvertI64U: + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return fmt.Errorf("cannot pop the i64 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeF32) + case OpcodeF32DemoteF64: + if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeF32DemoteF64Name, err) + } + valueTypeStack.push(ValueTypeF32) + case OpcodeF64ConvertI32S, OpcodeF64ConvertI32U: + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the i32 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeF64) + case OpcodeF64ConvertI64S, OpcodeF64ConvertI64U: + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return fmt.Errorf("cannot pop the i64 operand for %s: %v", InstructionName(op), err) + } + valueTypeStack.push(ValueTypeF64) + case OpcodeF64PromoteF32: + if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeF64PromoteF32Name, err) + } + valueTypeStack.push(ValueTypeF64) + case OpcodeI32ReinterpretF32: + if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI32ReinterpretF32Name, err) + } + valueTypeStack.push(ValueTypeI32) + case OpcodeI64ReinterpretF64: + if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI64ReinterpretF64Name, err) + } + valueTypeStack.push(ValueTypeI64) + case OpcodeF32ReinterpretI32: + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeF32ReinterpretI32Name, err) + } + valueTypeStack.push(ValueTypeF32) + case OpcodeF64ReinterpretI64: + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeF64ReinterpretI64Name, err) + } + valueTypeStack.push(ValueTypeF64) + case OpcodeI32Extend8S, OpcodeI32Extend16S: + if err := enabledFeatures.RequireEnabled(api.CoreFeatureSignExtensionOps); err != nil { + return fmt.Errorf("%s invalid as %v", instructionNames[op], err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", instructionNames[op], err) + } + valueTypeStack.push(ValueTypeI32) + case OpcodeI64Extend8S, OpcodeI64Extend16S, OpcodeI64Extend32S: + if err := enabledFeatures.RequireEnabled(api.CoreFeatureSignExtensionOps); err != nil { + return fmt.Errorf("%s invalid as %v", instructionNames[op], err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", instructionNames[op], err) + } + valueTypeStack.push(ValueTypeI64) + default: + return fmt.Errorf("invalid numeric instruction 0x%x", op) + } + } else if op >= OpcodeRefNull && op <= OpcodeRefFunc { + if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil { + return fmt.Errorf("%s invalid as %v", instructionNames[op], err) + } + switch op { + case OpcodeRefNull: + pc++ + switch reftype := body[pc]; reftype { + case ValueTypeExternref: + valueTypeStack.push(ValueTypeExternref) + case ValueTypeFuncref: + valueTypeStack.push(ValueTypeFuncref) + default: + return fmt.Errorf("unknown type for ref.null: 0x%x", reftype) + } + case OpcodeRefIsNull: + tp, err := valueTypeStack.pop() + if err != nil { + return fmt.Errorf("cannot pop the operand for ref.is_null: %v", err) + } else if !isReferenceValueType(tp) && tp != valueTypeUnknown { + return fmt.Errorf("type mismatch: expected reference type but was %s", ValueTypeName(tp)) + } + valueTypeStack.push(ValueTypeI32) + case OpcodeRefFunc: + pc++ + index, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("failed to read function index for ref.func: %v", err) + } + if _, ok := declaredFunctionIndexes[index]; !ok { + return fmt.Errorf("undeclared function index %d for ref.func", index) + } + pc += num - 1 + valueTypeStack.push(ValueTypeFuncref) + } + } else if op == OpcodeTableGet || op == OpcodeTableSet { + if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil { + return fmt.Errorf("%s is invalid as %v", InstructionName(op), err) + } + pc++ + tableIndex, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("read immediate: %v", err) + } + if tableIndex >= uint32(len(tables)) { + return fmt.Errorf("table of index %d not found", tableIndex) + } + + refType := tables[tableIndex].Type + if op == OpcodeTableGet { + if err := valueTypeStack.popAndVerifyType(api.ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the operand for table.get: %v", err) + } + valueTypeStack.push(refType) + } else { + if err := valueTypeStack.popAndVerifyType(refType); err != nil { + return fmt.Errorf("cannot pop the operand for table.set: %v", err) + } + if err := valueTypeStack.popAndVerifyType(api.ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the operand for table.set: %v", err) + } + } + pc += num - 1 + } else if op == OpcodeMiscPrefix { + pc++ + // A misc opcode is encoded as an unsigned variable 32-bit integer. + miscOp32, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("failed to read misc opcode: %v", err) + } + pc += num - 1 + miscOpcode := byte(miscOp32) + // If the misc opcode is beyond byte range, it is highly likely this is an invalid binary, or + // it is due to the new opcode from a new proposal. In the latter case, we have to + // change the alias type of OpcodeMisc (which is currently byte) to uint32. + if uint32(byte(miscOp32)) != miscOp32 { + return fmt.Errorf("invalid misc opcode: %#x", miscOp32) + } + if miscOpcode >= OpcodeMiscI32TruncSatF32S && miscOpcode <= OpcodeMiscI64TruncSatF64U { + if err := enabledFeatures.RequireEnabled(api.CoreFeatureNonTrappingFloatToIntConversion); err != nil { + return fmt.Errorf("%s invalid as %v", miscInstructionNames[miscOpcode], err) + } + var inType, outType ValueType + switch miscOpcode { + case OpcodeMiscI32TruncSatF32S, OpcodeMiscI32TruncSatF32U: + inType, outType = ValueTypeF32, ValueTypeI32 + case OpcodeMiscI32TruncSatF64S, OpcodeMiscI32TruncSatF64U: + inType, outType = ValueTypeF64, ValueTypeI32 + case OpcodeMiscI64TruncSatF32S, OpcodeMiscI64TruncSatF32U: + inType, outType = ValueTypeF32, ValueTypeI64 + case OpcodeMiscI64TruncSatF64S, OpcodeMiscI64TruncSatF64U: + inType, outType = ValueTypeF64, ValueTypeI64 + } + if err := valueTypeStack.popAndVerifyType(inType); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", miscInstructionNames[miscOpcode], err) + } + valueTypeStack.push(outType) + } else if miscOpcode >= OpcodeMiscMemoryInit && miscOpcode <= OpcodeMiscTableCopy { + if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil { + return fmt.Errorf("%s invalid as %v", miscInstructionNames[miscOpcode], err) + } + var params []ValueType + // Handle opcodes added in bulk-memory-operations/WebAssembly 2.0. + switch miscOpcode { + case OpcodeMiscDataDrop: + if m.DataCountSection == nil { + return fmt.Errorf("%s requires data count section", MiscInstructionName(miscOpcode)) + } + + // We need to read the index to the data section. + pc++ + index, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("failed to read data segment index for %s: %v", MiscInstructionName(miscOpcode), err) + } + if int(index) >= len(m.DataSection) { + return fmt.Errorf("index %d out of range of data section(len=%d)", index, len(m.DataSection)) + } + pc += num - 1 + case OpcodeMiscMemoryInit, OpcodeMiscMemoryCopy, OpcodeMiscMemoryFill: + if memory == nil { + return fmt.Errorf("memory must exist for %s", MiscInstructionName(miscOpcode)) + } + params = []ValueType{ValueTypeI32, ValueTypeI32, ValueTypeI32} + + if miscOpcode == OpcodeMiscMemoryInit { + if m.DataCountSection == nil { + return fmt.Errorf("%s requires data count section", MiscInstructionName(miscOpcode)) + } + + // We need to read the index to the data section. + pc++ + index, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("failed to read data segment index for %s: %v", MiscInstructionName(miscOpcode), err) + } + if int(index) >= len(m.DataSection) { + return fmt.Errorf("index %d out of range of data section(len=%d)", index, len(m.DataSection)) + } + pc += num - 1 + } + + pc++ + val, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("failed to read memory index for %s: %v", MiscInstructionName(miscOpcode), err) + } + if val != 0 || num != 1 { + return fmt.Errorf("%s reserved byte must be zero encoded with 1 byte", MiscInstructionName(miscOpcode)) + } + if miscOpcode == OpcodeMiscMemoryCopy { + pc++ + // memory.copy needs two memory index which are reserved as zero. + val, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("failed to read memory index for %s: %v", MiscInstructionName(miscOpcode), err) + } + if val != 0 || num != 1 { + return fmt.Errorf("%s reserved byte must be zero encoded with 1 byte", MiscInstructionName(miscOpcode)) + } + } + + case OpcodeMiscTableInit: + params = []ValueType{ValueTypeI32, ValueTypeI32, ValueTypeI32} + pc++ + elementIndex, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("failed to read element segment index for %s: %v", MiscInstructionName(miscOpcode), err) + } + if int(elementIndex) >= len(m.ElementSection) { + return fmt.Errorf("index %d out of range of element section(len=%d)", elementIndex, len(m.ElementSection)) + } + pc += num + + tableIndex, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("failed to read source table index for %s: %v", MiscInstructionName(miscOpcode), err) + } + if tableIndex != 0 { + if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil { + return fmt.Errorf("source table index must be zero for %s as %v", MiscInstructionName(miscOpcode), err) + } + } + if tableIndex >= uint32(len(tables)) { + return fmt.Errorf("table of index %d not found", tableIndex) + } + + if m.ElementSection[elementIndex].Type != tables[tableIndex].Type { + return fmt.Errorf("type mismatch for table.init: element type %s does not match table type %s", + RefTypeName(m.ElementSection[elementIndex].Type), + RefTypeName(tables[tableIndex].Type), + ) + } + pc += num - 1 + case OpcodeMiscElemDrop: + pc++ + elementIndex, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("failed to read element segment index for %s: %v", MiscInstructionName(miscOpcode), err) + } else if int(elementIndex) >= len(m.ElementSection) { + return fmt.Errorf("index %d out of range of element section(len=%d)", elementIndex, len(m.ElementSection)) + } + pc += num - 1 + case OpcodeMiscTableCopy: + params = []ValueType{ValueTypeI32, ValueTypeI32, ValueTypeI32} + pc++ + + dstTableIndex, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("failed to read destination table index for %s: %v", MiscInstructionName(miscOpcode), err) + } + if dstTableIndex != 0 { + if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil { + return fmt.Errorf("destination table index must be zero for %s as %v", MiscInstructionName(miscOpcode), err) + } + } + if dstTableIndex >= uint32(len(tables)) { + return fmt.Errorf("table of index %d not found", dstTableIndex) + } + pc += num + + srcTableIndex, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("failed to read source table index for %s: %v", MiscInstructionName(miscOpcode), err) + } + if srcTableIndex != 0 { + if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil { + return fmt.Errorf("source table index must be zero for %s as %v", MiscInstructionName(miscOpcode), err) + } + } + if srcTableIndex >= uint32(len(tables)) { + return fmt.Errorf("table of index %d not found", srcTableIndex) + } + + if tables[srcTableIndex].Type != tables[dstTableIndex].Type { + return fmt.Errorf("table type mismatch for table.copy: %s (src) != %s (dst)", + RefTypeName(tables[srcTableIndex].Type), RefTypeName(tables[dstTableIndex].Type)) + } + + pc += num - 1 + } + for _, p := range params { + if err := valueTypeStack.popAndVerifyType(p); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", miscInstructionNames[miscOpcode], err) + } + } + } else if miscOpcode >= OpcodeMiscTableGrow && miscOpcode <= OpcodeMiscTableFill { + if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil { + return fmt.Errorf("%s invalid as %v", miscInstructionNames[miscOpcode], err) + } + + pc++ + tableIndex, num, err := leb128.LoadUint32(body[pc:]) + if err != nil { + return fmt.Errorf("failed to read table index for %s: %v", MiscInstructionName(miscOpcode), err) + } + if tableIndex >= uint32(len(tables)) { + return fmt.Errorf("table of index %d not found", tableIndex) + } + pc += num - 1 + + var params, results []ValueType + reftype := tables[tableIndex].Type + if miscOpcode == OpcodeMiscTableGrow { + params = []ValueType{ValueTypeI32, reftype} + results = []ValueType{ValueTypeI32} + } else if miscOpcode == OpcodeMiscTableSize { + results = []ValueType{ValueTypeI32} + } else if miscOpcode == OpcodeMiscTableFill { + params = []ValueType{ValueTypeI32, reftype, ValueTypeI32} + } + + for _, p := range params { + if err := valueTypeStack.popAndVerifyType(p); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", miscInstructionNames[miscOpcode], err) + } + } + for _, r := range results { + valueTypeStack.push(r) + } + } else { + return fmt.Errorf("unknown misc opcode %#x", miscOpcode) + } + } else if op == OpcodeVecPrefix { + pc++ + // Vector instructions come with two bytes where the first byte is always OpcodeVecPrefix, + // and the second byte determines the actual instruction. + vecOpcode := body[pc] + if err := enabledFeatures.RequireEnabled(api.CoreFeatureSIMD); err != nil { + return fmt.Errorf("%s invalid as %v", vectorInstructionName[vecOpcode], err) + } + + switch vecOpcode { + case OpcodeVecV128Const: + // Read 128-bit = 16 bytes constants + if int(pc+16) >= len(body) { + return fmt.Errorf("cannot read constant vector value for %s", vectorInstructionName[vecOpcode]) + } + pc += 16 + valueTypeStack.push(ValueTypeV128) + case OpcodeVecV128AnyTrue, OpcodeVecI8x16AllTrue, OpcodeVecI16x8AllTrue, OpcodeVecI32x4AllTrue, OpcodeVecI64x2AllTrue, + OpcodeVecI8x16BitMask, OpcodeVecI16x8BitMask, OpcodeVecI32x4BitMask, OpcodeVecI64x2BitMask: + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + valueTypeStack.push(ValueTypeI32) + case OpcodeVecV128Load, OpcodeVecV128Load8x8s, OpcodeVecV128Load8x8u, OpcodeVecV128Load16x4s, OpcodeVecV128Load16x4u, + OpcodeVecV128Load32x2s, OpcodeVecV128Load32x2u, OpcodeVecV128Load8Splat, OpcodeVecV128Load16Splat, + OpcodeVecV128Load32Splat, OpcodeVecV128Load64Splat, + OpcodeVecV128Load32zero, OpcodeVecV128Load64zero: + if memory == nil { + return fmt.Errorf("memory must exist for %s", VectorInstructionName(vecOpcode)) + } + pc++ + align, _, read, err := readMemArg(pc, body) + if err != nil { + return err + } + pc += read - 1 + var maxAlign uint32 + switch vecOpcode { + case OpcodeVecV128Load: + maxAlign = 128 / 8 + case OpcodeVecV128Load8x8s, OpcodeVecV128Load8x8u, OpcodeVecV128Load16x4s, OpcodeVecV128Load16x4u, + OpcodeVecV128Load32x2s, OpcodeVecV128Load32x2u: + maxAlign = 64 / 8 + case OpcodeVecV128Load8Splat: + maxAlign = 1 + case OpcodeVecV128Load16Splat: + maxAlign = 16 / 8 + case OpcodeVecV128Load32Splat: + maxAlign = 32 / 8 + case OpcodeVecV128Load64Splat: + maxAlign = 64 / 8 + case OpcodeVecV128Load32zero: + maxAlign = 32 / 8 + case OpcodeVecV128Load64zero: + maxAlign = 64 / 8 + } + + if 1< maxAlign { + return fmt.Errorf("invalid memory alignment %d for %s", align, VectorInstructionName(vecOpcode)) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", VectorInstructionName(vecOpcode), err) + } + valueTypeStack.push(ValueTypeV128) + case OpcodeVecV128Store: + if memory == nil { + return fmt.Errorf("memory must exist for %s", VectorInstructionName(vecOpcode)) + } + pc++ + align, _, read, err := readMemArg(pc, body) + if err != nil { + return err + } + pc += read - 1 + if 1< 128/8 { + return fmt.Errorf("invalid memory alignment %d for %s", align, OpcodeVecV128StoreName) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeVecV128StoreName, err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeVecV128StoreName, err) + } + case OpcodeVecV128Load8Lane, OpcodeVecV128Load16Lane, OpcodeVecV128Load32Lane, OpcodeVecV128Load64Lane: + if memory == nil { + return fmt.Errorf("memory must exist for %s", VectorInstructionName(vecOpcode)) + } + attr := vecLoadLanes[vecOpcode] + pc++ + align, _, read, err := readMemArg(pc, body) + if err != nil { + return err + } + if 1< attr.alignMax { + return fmt.Errorf("invalid memory alignment %d for %s", align, vectorInstructionName[vecOpcode]) + } + pc += read + if pc >= uint64(len(body)) { + return fmt.Errorf("lane for %s not found", OpcodeVecV128Load64LaneName) + } + lane := body[pc] + if lane >= attr.laneCeil { + return fmt.Errorf("invalid lane index %d >= %d for %s", lane, attr.laneCeil, vectorInstructionName[vecOpcode]) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + valueTypeStack.push(ValueTypeV128) + case OpcodeVecV128Store8Lane, OpcodeVecV128Store16Lane, OpcodeVecV128Store32Lane, OpcodeVecV128Store64Lane: + if memory == nil { + return fmt.Errorf("memory must exist for %s", VectorInstructionName(vecOpcode)) + } + attr := vecStoreLanes[vecOpcode] + pc++ + align, _, read, err := readMemArg(pc, body) + if err != nil { + return err + } + if 1< attr.alignMax { + return fmt.Errorf("invalid memory alignment %d for %s", align, vectorInstructionName[vecOpcode]) + } + pc += read + if pc >= uint64(len(body)) { + return fmt.Errorf("lane for %s not found", vectorInstructionName[vecOpcode]) + } + lane := body[pc] + if lane >= attr.laneCeil { + return fmt.Errorf("invalid lane index %d >= %d for %s", lane, attr.laneCeil, vectorInstructionName[vecOpcode]) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + case OpcodeVecI8x16ExtractLaneS, + OpcodeVecI8x16ExtractLaneU, + OpcodeVecI16x8ExtractLaneS, + OpcodeVecI16x8ExtractLaneU, + OpcodeVecI32x4ExtractLane, + OpcodeVecI64x2ExtractLane, + OpcodeVecF32x4ExtractLane, + OpcodeVecF64x2ExtractLane: + pc++ + if pc >= uint64(len(body)) { + return fmt.Errorf("lane for %s not found", vectorInstructionName[vecOpcode]) + } + attr := vecExtractLanes[vecOpcode] + lane := body[pc] + if lane >= attr.laneCeil { + return fmt.Errorf("invalid lane index %d >= %d for %s", lane, attr.laneCeil, vectorInstructionName[vecOpcode]) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + valueTypeStack.push(attr.resultType) + case OpcodeVecI8x16ReplaceLane, OpcodeVecI16x8ReplaceLane, OpcodeVecI32x4ReplaceLane, + OpcodeVecI64x2ReplaceLane, OpcodeVecF32x4ReplaceLane, OpcodeVecF64x2ReplaceLane: + pc++ + if pc >= uint64(len(body)) { + return fmt.Errorf("lane for %s not found", vectorInstructionName[vecOpcode]) + } + attr := vecReplaceLanes[vecOpcode] + lane := body[pc] + if lane >= attr.laneCeil { + return fmt.Errorf("invalid lane index %d >= %d for %s", lane, attr.laneCeil, vectorInstructionName[vecOpcode]) + } + if err := valueTypeStack.popAndVerifyType(attr.paramType); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + valueTypeStack.push(ValueTypeV128) + case OpcodeVecI8x16Splat, OpcodeVecI16x8Splat, OpcodeVecI32x4Splat, + OpcodeVecI64x2Splat, OpcodeVecF32x4Splat, OpcodeVecF64x2Splat: + tp := vecSplatValueTypes[vecOpcode] + if err := valueTypeStack.popAndVerifyType(tp); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + valueTypeStack.push(ValueTypeV128) + case OpcodeVecI8x16Swizzle, OpcodeVecV128And, OpcodeVecV128Or, OpcodeVecV128Xor, OpcodeVecV128AndNot: + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + valueTypeStack.push(ValueTypeV128) + case OpcodeVecV128Bitselect: + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + valueTypeStack.push(ValueTypeV128) + case OpcodeVecV128Not: + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + valueTypeStack.push(ValueTypeV128) + case OpcodeVecV128i8x16Shuffle: + pc++ + if pc+15 >= uint64(len(body)) { + return fmt.Errorf("16 lane indexes for %s not found", vectorInstructionName[vecOpcode]) + } + lanes := body[pc : pc+16] + for i, l := range lanes { + if l >= 32 { + return fmt.Errorf("invalid lane index[%d] %d >= %d for %s", i, l, 32, vectorInstructionName[vecOpcode]) + } + } + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + valueTypeStack.push(ValueTypeV128) + pc += 15 + case OpcodeVecI8x16Shl, OpcodeVecI8x16ShrS, OpcodeVecI8x16ShrU, + OpcodeVecI16x8Shl, OpcodeVecI16x8ShrS, OpcodeVecI16x8ShrU, + OpcodeVecI32x4Shl, OpcodeVecI32x4ShrS, OpcodeVecI32x4ShrU, + OpcodeVecI64x2Shl, OpcodeVecI64x2ShrS, OpcodeVecI64x2ShrU: + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + valueTypeStack.push(ValueTypeV128) + case OpcodeVecI8x16Eq, OpcodeVecI8x16Ne, OpcodeVecI8x16LtS, OpcodeVecI8x16LtU, OpcodeVecI8x16GtS, + OpcodeVecI8x16GtU, OpcodeVecI8x16LeS, OpcodeVecI8x16LeU, OpcodeVecI8x16GeS, OpcodeVecI8x16GeU, + OpcodeVecI16x8Eq, OpcodeVecI16x8Ne, OpcodeVecI16x8LtS, OpcodeVecI16x8LtU, OpcodeVecI16x8GtS, + OpcodeVecI16x8GtU, OpcodeVecI16x8LeS, OpcodeVecI16x8LeU, OpcodeVecI16x8GeS, OpcodeVecI16x8GeU, + OpcodeVecI32x4Eq, OpcodeVecI32x4Ne, OpcodeVecI32x4LtS, OpcodeVecI32x4LtU, OpcodeVecI32x4GtS, + OpcodeVecI32x4GtU, OpcodeVecI32x4LeS, OpcodeVecI32x4LeU, OpcodeVecI32x4GeS, OpcodeVecI32x4GeU, + OpcodeVecI64x2Eq, OpcodeVecI64x2Ne, OpcodeVecI64x2LtS, OpcodeVecI64x2GtS, OpcodeVecI64x2LeS, + OpcodeVecI64x2GeS, OpcodeVecF32x4Eq, OpcodeVecF32x4Ne, OpcodeVecF32x4Lt, OpcodeVecF32x4Gt, + OpcodeVecF32x4Le, OpcodeVecF32x4Ge, OpcodeVecF64x2Eq, OpcodeVecF64x2Ne, OpcodeVecF64x2Lt, + OpcodeVecF64x2Gt, OpcodeVecF64x2Le, OpcodeVecF64x2Ge, + OpcodeVecI32x4DotI16x8S, + OpcodeVecI8x16NarrowI16x8S, OpcodeVecI8x16NarrowI16x8U, OpcodeVecI16x8NarrowI32x4S, OpcodeVecI16x8NarrowI32x4U: + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + valueTypeStack.push(ValueTypeV128) + case OpcodeVecI8x16Neg, OpcodeVecI16x8Neg, OpcodeVecI32x4Neg, OpcodeVecI64x2Neg, OpcodeVecF32x4Neg, OpcodeVecF64x2Neg, + OpcodeVecF32x4Sqrt, OpcodeVecF64x2Sqrt, + OpcodeVecI8x16Abs, OpcodeVecI8x16Popcnt, OpcodeVecI16x8Abs, OpcodeVecI32x4Abs, OpcodeVecI64x2Abs, + OpcodeVecF32x4Abs, OpcodeVecF64x2Abs, + OpcodeVecF32x4Ceil, OpcodeVecF32x4Floor, OpcodeVecF32x4Trunc, OpcodeVecF32x4Nearest, + OpcodeVecF64x2Ceil, OpcodeVecF64x2Floor, OpcodeVecF64x2Trunc, OpcodeVecF64x2Nearest, + OpcodeVecI16x8ExtendLowI8x16S, OpcodeVecI16x8ExtendHighI8x16S, OpcodeVecI16x8ExtendLowI8x16U, OpcodeVecI16x8ExtendHighI8x16U, + OpcodeVecI32x4ExtendLowI16x8S, OpcodeVecI32x4ExtendHighI16x8S, OpcodeVecI32x4ExtendLowI16x8U, OpcodeVecI32x4ExtendHighI16x8U, + OpcodeVecI64x2ExtendLowI32x4S, OpcodeVecI64x2ExtendHighI32x4S, OpcodeVecI64x2ExtendLowI32x4U, OpcodeVecI64x2ExtendHighI32x4U, + OpcodeVecI16x8ExtaddPairwiseI8x16S, OpcodeVecI16x8ExtaddPairwiseI8x16U, + OpcodeVecI32x4ExtaddPairwiseI16x8S, OpcodeVecI32x4ExtaddPairwiseI16x8U, + OpcodeVecF64x2PromoteLowF32x4Zero, OpcodeVecF32x4DemoteF64x2Zero, + OpcodeVecF32x4ConvertI32x4S, OpcodeVecF32x4ConvertI32x4U, + OpcodeVecF64x2ConvertLowI32x4S, OpcodeVecF64x2ConvertLowI32x4U, + OpcodeVecI32x4TruncSatF32x4S, OpcodeVecI32x4TruncSatF32x4U, OpcodeVecI32x4TruncSatF64x2SZero, OpcodeVecI32x4TruncSatF64x2UZero: + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + valueTypeStack.push(ValueTypeV128) + + case OpcodeVecI8x16Add, OpcodeVecI8x16AddSatS, OpcodeVecI8x16AddSatU, OpcodeVecI8x16Sub, OpcodeVecI8x16SubSatS, OpcodeVecI8x16SubSatU, + OpcodeVecI16x8Add, OpcodeVecI16x8AddSatS, OpcodeVecI16x8AddSatU, OpcodeVecI16x8Sub, OpcodeVecI16x8SubSatS, OpcodeVecI16x8SubSatU, OpcodeVecI16x8Mul, + OpcodeVecI32x4Add, OpcodeVecI32x4Sub, OpcodeVecI32x4Mul, + OpcodeVecI64x2Add, OpcodeVecI64x2Sub, OpcodeVecI64x2Mul, + OpcodeVecF32x4Add, OpcodeVecF32x4Sub, OpcodeVecF32x4Mul, OpcodeVecF32x4Div, + OpcodeVecF64x2Add, OpcodeVecF64x2Sub, OpcodeVecF64x2Mul, OpcodeVecF64x2Div, + OpcodeVecI8x16MinS, OpcodeVecI8x16MinU, OpcodeVecI8x16MaxS, OpcodeVecI8x16MaxU, + OpcodeVecI8x16AvgrU, + OpcodeVecI16x8MinS, OpcodeVecI16x8MinU, OpcodeVecI16x8MaxS, OpcodeVecI16x8MaxU, + OpcodeVecI16x8AvgrU, + OpcodeVecI32x4MinS, OpcodeVecI32x4MinU, OpcodeVecI32x4MaxS, OpcodeVecI32x4MaxU, + OpcodeVecF32x4Min, OpcodeVecF32x4Max, OpcodeVecF64x2Min, OpcodeVecF64x2Max, + OpcodeVecF32x4Pmin, OpcodeVecF32x4Pmax, OpcodeVecF64x2Pmin, OpcodeVecF64x2Pmax, + OpcodeVecI16x8Q15mulrSatS, + OpcodeVecI16x8ExtMulLowI8x16S, OpcodeVecI16x8ExtMulHighI8x16S, OpcodeVecI16x8ExtMulLowI8x16U, OpcodeVecI16x8ExtMulHighI8x16U, + OpcodeVecI32x4ExtMulLowI16x8S, OpcodeVecI32x4ExtMulHighI16x8S, OpcodeVecI32x4ExtMulLowI16x8U, OpcodeVecI32x4ExtMulHighI16x8U, + OpcodeVecI64x2ExtMulLowI32x4S, OpcodeVecI64x2ExtMulHighI32x4S, OpcodeVecI64x2ExtMulLowI32x4U, OpcodeVecI64x2ExtMulHighI32x4U: + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err) + } + valueTypeStack.push(ValueTypeV128) + default: + return fmt.Errorf("unknown SIMD instruction %s", vectorInstructionName[vecOpcode]) + } + } else if op == OpcodeBlock { + br.Reset(body[pc+1:]) + bt, num, err := DecodeBlockType(m.TypeSection, br, enabledFeatures) + if err != nil { + return fmt.Errorf("read block: %w", err) + } + controlBlockStack.push(pc, 0, 0, bt, num, 0) + if err = valueTypeStack.popParams(op, bt.Params, false); err != nil { + return err + } + // Plus we have to push any block params again. + for _, p := range bt.Params { + valueTypeStack.push(p) + } + valueTypeStack.pushStackLimit(len(bt.Params)) + pc += num + } else if op == OpcodeAtomicPrefix { + pc++ + // Atomic instructions come with two bytes where the first byte is always OpcodeAtomicPrefix, + // and the second byte determines the actual instruction. + atomicOpcode := body[pc] + if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesThreads); err != nil { + return fmt.Errorf("%s invalid as %v", atomicInstructionName[atomicOpcode], err) + } + pc++ + + if atomicOpcode == OpcodeAtomicFence { + // No memory requirement and no arguments or return, however the immediate byte value must be 0. + imm := body[pc] + if imm != 0x0 { + return fmt.Errorf("invalid immediate value for %s", AtomicInstructionName(atomicOpcode)) + } + continue + } + + // All atomic operations except fence (checked above) require memory + if memory == nil { + return fmt.Errorf("memory must exist for %s", AtomicInstructionName(atomicOpcode)) + } + align, _, read, err := readMemArg(pc, body) + if err != nil { + return err + } + pc += read - 1 + switch atomicOpcode { + case OpcodeAtomicMemoryNotify: + if 1< 32/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI32) + case OpcodeAtomicMemoryWait32: + if 1< 32/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI32) + case OpcodeAtomicMemoryWait64: + if 1< 64/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI32) + case OpcodeAtomicI32Load: + if 1< 32/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI32) + case OpcodeAtomicI64Load: + if 1< 64/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI64) + case OpcodeAtomicI32Load8U: + if 1< 16/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI64) + case OpcodeAtomicI64Load32U: + if 1< 32/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI64) + case OpcodeAtomicI32Store: + if 1< 32/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + case OpcodeAtomicI64Store: + if 1< 64/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + case OpcodeAtomicI32Store8: + if 1< 1 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + case OpcodeAtomicI32Store16: + if 1< 16/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + case OpcodeAtomicI64Store8: + if 1< 1 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + case OpcodeAtomicI64Store16: + if 1< 16/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + case OpcodeAtomicI64Store32: + if 1< 32/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + case OpcodeAtomicI32RmwAdd, OpcodeAtomicI32RmwSub, OpcodeAtomicI32RmwAnd, OpcodeAtomicI32RmwOr, OpcodeAtomicI32RmwXor, OpcodeAtomicI32RmwXchg: + if 1< 32/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI32) + case OpcodeAtomicI32Rmw8AddU, OpcodeAtomicI32Rmw8SubU, OpcodeAtomicI32Rmw8AndU, OpcodeAtomicI32Rmw8OrU, OpcodeAtomicI32Rmw8XorU, OpcodeAtomicI32Rmw8XchgU: + if 1< 1 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI32) + case OpcodeAtomicI32Rmw16AddU, OpcodeAtomicI32Rmw16SubU, OpcodeAtomicI32Rmw16AndU, OpcodeAtomicI32Rmw16OrU, OpcodeAtomicI32Rmw16XorU, OpcodeAtomicI32Rmw16XchgU: + if 1< 16/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI32) + case OpcodeAtomicI64RmwAdd, OpcodeAtomicI64RmwSub, OpcodeAtomicI64RmwAnd, OpcodeAtomicI64RmwOr, OpcodeAtomicI64RmwXor, OpcodeAtomicI64RmwXchg: + if 1< 64/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI64) + case OpcodeAtomicI64Rmw8AddU, OpcodeAtomicI64Rmw8SubU, OpcodeAtomicI64Rmw8AndU, OpcodeAtomicI64Rmw8OrU, OpcodeAtomicI64Rmw8XorU, OpcodeAtomicI64Rmw8XchgU: + if 1< 1 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI64) + case OpcodeAtomicI64Rmw16AddU, OpcodeAtomicI64Rmw16SubU, OpcodeAtomicI64Rmw16AndU, OpcodeAtomicI64Rmw16OrU, OpcodeAtomicI64Rmw16XorU, OpcodeAtomicI64Rmw16XchgU: + if 1< 16/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI64) + case OpcodeAtomicI64Rmw32AddU, OpcodeAtomicI64Rmw32SubU, OpcodeAtomicI64Rmw32AndU, OpcodeAtomicI64Rmw32OrU, OpcodeAtomicI64Rmw32XorU, OpcodeAtomicI64Rmw32XchgU: + if 1< 32/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI64) + case OpcodeAtomicI32RmwCmpxchg: + if 1< 32/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI32) + case OpcodeAtomicI32Rmw8CmpxchgU: + if 1< 1 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI32) + case OpcodeAtomicI32Rmw16CmpxchgU: + if 1< 16/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI32) + case OpcodeAtomicI64RmwCmpxchg: + if 1< 64/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI64) + case OpcodeAtomicI64Rmw8CmpxchgU: + if 1< 1 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI64) + case OpcodeAtomicI64Rmw16CmpxchgU: + if 1< 16/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI64) + case OpcodeAtomicI64Rmw32CmpxchgU: + if 1< 32/8 { + return fmt.Errorf("invalid memory alignment") + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil { + return err + } + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return err + } + valueTypeStack.push(ValueTypeI64) + default: + return fmt.Errorf("invalid atomic opcode: 0x%x", atomicOpcode) + } + } else if op == OpcodeLoop { + br.Reset(body[pc+1:]) + bt, num, err := DecodeBlockType(m.TypeSection, br, enabledFeatures) + if err != nil { + return fmt.Errorf("read block: %w", err) + } + controlBlockStack.push(pc, 0, 0, bt, num, op) + if err = valueTypeStack.popParams(op, bt.Params, false); err != nil { + return err + } + // Plus we have to push any block params again. + for _, p := range bt.Params { + valueTypeStack.push(p) + } + valueTypeStack.pushStackLimit(len(bt.Params)) + pc += num + } else if op == OpcodeIf { + br.Reset(body[pc+1:]) + bt, num, err := DecodeBlockType(m.TypeSection, br, enabledFeatures) + if err != nil { + return fmt.Errorf("read block: %w", err) + } + controlBlockStack.push(pc, 0, 0, bt, num, op) + if err = valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("cannot pop the operand for 'if': %v", err) + } + if err = valueTypeStack.popParams(op, bt.Params, false); err != nil { + return err + } + // Plus we have to push any block params again. + for _, p := range bt.Params { + valueTypeStack.push(p) + } + valueTypeStack.pushStackLimit(len(bt.Params)) + pc += num + } else if op == OpcodeElse { + bl := &controlBlockStack.stack[len(controlBlockStack.stack)-1] + if bl.op != OpcodeIf { + return fmt.Errorf("else instruction must be used in if block: %#x", pc) + } + bl.op = OpcodeElse + bl.elseAt = pc + // Check the type soundness of the instructions *before* entering this else Op. + if err := valueTypeStack.popResults(OpcodeIf, bl.blockType.Results, true); err != nil { + return err + } + // Before entering instructions inside else, we pop all the values pushed by then block. + valueTypeStack.resetAtStackLimit() + // Plus we have to push any block params again. + for _, p := range bl.blockType.Params { + valueTypeStack.push(p) + } + } else if op == OpcodeEnd { + bl := controlBlockStack.pop() + bl.endAt = pc + + // OpcodeEnd can end a block or the function itself. Check to see what it is: + + ifMissingElse := bl.op == OpcodeIf && bl.elseAt <= bl.startAt + if ifMissingElse { + // If this is the end of block without else, the number of block's results and params must be same. + // Otherwise, the value stack would result in the inconsistent state at runtime. + if !bytes.Equal(bl.blockType.Results, bl.blockType.Params) { + return typeCountError(false, OpcodeElseName, bl.blockType.Params, bl.blockType.Results) + } + // -1 skips else, to handle if block without else properly. + bl.elseAt = bl.endAt - 1 + } + + // Determine the block context + ctx := "" // the outer-most block: the function return + if bl.op == OpcodeIf && !ifMissingElse && bl.elseAt > 0 { + ctx = OpcodeElseName + } else if bl.op != 0 { + ctx = InstructionName(bl.op) + } + + // Check return types match + if err := valueTypeStack.requireStackValues(false, ctx, bl.blockType.Results, true); err != nil { + return err + } + + // Put the result types at the end after resetting at the stack limit + // since we might have Any type between the limit and the current top. + valueTypeStack.resetAtStackLimit() + for _, exp := range bl.blockType.Results { + valueTypeStack.push(exp) + } + // We exit if/loop/block, so reset the constraints on the stack manipulation + // on values previously pushed by outer blocks. + valueTypeStack.popStackLimit() + } else if op == OpcodeReturn { + // Same formatting as OpcodeEnd on the outer-most block + if err := valueTypeStack.requireStackValues(false, "", functionType.Results, false); err != nil { + return err + } + // return instruction is stack-polymorphic. + valueTypeStack.unreachable() + } else if op == OpcodeDrop { + _, err := valueTypeStack.pop() + if err != nil { + return fmt.Errorf("invalid drop: %v", err) + } + } else if op == OpcodeSelect || op == OpcodeTypedSelect { + if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { + return fmt.Errorf("type mismatch on 3rd select operand: %v", err) + } + v1, err := valueTypeStack.pop() + if err != nil { + return fmt.Errorf("invalid select: %v", err) + } + v2, err := valueTypeStack.pop() + if err != nil { + return fmt.Errorf("invalid select: %v", err) + } + + if op == OpcodeTypedSelect { + if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil { + return fmt.Errorf("%s is invalid as %w", InstructionName(op), err) + } + pc++ + if numTypeImmeidates := body[pc]; numTypeImmeidates != 1 { + return fmt.Errorf("too many type immediates for %s", InstructionName(op)) + } + pc++ + tp := body[pc] + if tp != ValueTypeI32 && tp != ValueTypeI64 && tp != ValueTypeF32 && tp != ValueTypeF64 && + tp != api.ValueTypeExternref && tp != ValueTypeFuncref && tp != ValueTypeV128 { + return fmt.Errorf("invalid type %s for %s", ValueTypeName(tp), OpcodeTypedSelectName) + } + } else if isReferenceValueType(v1) || isReferenceValueType(v2) { + return fmt.Errorf("reference types cannot be used for non typed select instruction") + } + + if v1 != v2 && v1 != valueTypeUnknown && v2 != valueTypeUnknown { + return fmt.Errorf("type mismatch on 1st and 2nd select operands") + } + if v1 == valueTypeUnknown { + valueTypeStack.push(v2) + } else { + valueTypeStack.push(v1) + } + } else if op == OpcodeUnreachable { + // unreachable instruction is stack-polymorphic. + valueTypeStack.unreachable() + } else if op == OpcodeNop { + } else { + return fmt.Errorf("invalid instruction 0x%x", op) + } + } + + if len(controlBlockStack.stack) > 0 { + return fmt.Errorf("ill-nested block exists") + } + if valueTypeStack.maximumStackPointer > maxStackValues { + return fmt.Errorf("function may have %d stack values, which exceeds limit %d", valueTypeStack.maximumStackPointer, maxStackValues) + } + return nil +} + +var vecExtractLanes = [...]struct { + laneCeil byte + resultType ValueType +}{ + OpcodeVecI8x16ExtractLaneS: {laneCeil: 16, resultType: ValueTypeI32}, + OpcodeVecI8x16ExtractLaneU: {laneCeil: 16, resultType: ValueTypeI32}, + OpcodeVecI16x8ExtractLaneS: {laneCeil: 8, resultType: ValueTypeI32}, + OpcodeVecI16x8ExtractLaneU: {laneCeil: 8, resultType: ValueTypeI32}, + OpcodeVecI32x4ExtractLane: {laneCeil: 4, resultType: ValueTypeI32}, + OpcodeVecI64x2ExtractLane: {laneCeil: 2, resultType: ValueTypeI64}, + OpcodeVecF32x4ExtractLane: {laneCeil: 4, resultType: ValueTypeF32}, + OpcodeVecF64x2ExtractLane: {laneCeil: 2, resultType: ValueTypeF64}, +} + +var vecReplaceLanes = [...]struct { + laneCeil byte + paramType ValueType +}{ + OpcodeVecI8x16ReplaceLane: {laneCeil: 16, paramType: ValueTypeI32}, + OpcodeVecI16x8ReplaceLane: {laneCeil: 8, paramType: ValueTypeI32}, + OpcodeVecI32x4ReplaceLane: {laneCeil: 4, paramType: ValueTypeI32}, + OpcodeVecI64x2ReplaceLane: {laneCeil: 2, paramType: ValueTypeI64}, + OpcodeVecF32x4ReplaceLane: {laneCeil: 4, paramType: ValueTypeF32}, + OpcodeVecF64x2ReplaceLane: {laneCeil: 2, paramType: ValueTypeF64}, +} + +var vecStoreLanes = [...]struct { + alignMax uint32 + laneCeil byte +}{ + OpcodeVecV128Store64Lane: {alignMax: 64 / 8, laneCeil: 128 / 64}, + OpcodeVecV128Store32Lane: {alignMax: 32 / 8, laneCeil: 128 / 32}, + OpcodeVecV128Store16Lane: {alignMax: 16 / 8, laneCeil: 128 / 16}, + OpcodeVecV128Store8Lane: {alignMax: 1, laneCeil: 128 / 8}, +} + +var vecLoadLanes = [...]struct { + alignMax uint32 + laneCeil byte +}{ + OpcodeVecV128Load64Lane: {alignMax: 64 / 8, laneCeil: 128 / 64}, + OpcodeVecV128Load32Lane: {alignMax: 32 / 8, laneCeil: 128 / 32}, + OpcodeVecV128Load16Lane: {alignMax: 16 / 8, laneCeil: 128 / 16}, + OpcodeVecV128Load8Lane: {alignMax: 1, laneCeil: 128 / 8}, +} + +var vecSplatValueTypes = [...]ValueType{ + OpcodeVecI8x16Splat: ValueTypeI32, + OpcodeVecI16x8Splat: ValueTypeI32, + OpcodeVecI32x4Splat: ValueTypeI32, + OpcodeVecI64x2Splat: ValueTypeI64, + OpcodeVecF32x4Splat: ValueTypeF32, + OpcodeVecF64x2Splat: ValueTypeF64, +} + +type stacks struct { + vs valueTypeStack + cs controlBlockStack + // ls is the label slice that is reused for each br_table instruction. + ls []uint32 +} + +func (sts *stacks) reset(functionType *FunctionType) { + // Reset valueStack for reuse. + sts.vs.stack = sts.vs.stack[:0] + sts.vs.stackLimits = sts.vs.stackLimits[:0] + sts.vs.maximumStackPointer = 0 + sts.cs.stack = sts.cs.stack[:0] + sts.cs.stack = append(sts.cs.stack, controlBlock{blockType: functionType}) + sts.ls = sts.ls[:0] +} + +type controlBlockStack struct { + stack []controlBlock +} + +func (s *controlBlockStack) pop() *controlBlock { + tail := len(s.stack) - 1 + ret := &s.stack[tail] + s.stack = s.stack[:tail] + return ret +} + +func (s *controlBlockStack) push(startAt, elseAt, endAt uint64, blockType *FunctionType, blockTypeBytes uint64, op Opcode) { + s.stack = append(s.stack, controlBlock{ + startAt: startAt, + elseAt: elseAt, + endAt: endAt, + blockType: blockType, + blockTypeBytes: blockTypeBytes, + op: op, + }) +} + +type valueTypeStack struct { + stack []ValueType + stackLimits []int + maximumStackPointer int + // requireStackValuesTmp is used in requireStackValues function to reduce the allocation. + requireStackValuesTmp []ValueType +} + +// Only used in the analyzeFunction below. +const valueTypeUnknown = ValueType(0xFF) + +func (s *valueTypeStack) tryPop() (vt ValueType, limit int, ok bool) { + if len(s.stackLimits) > 0 { + limit = s.stackLimits[len(s.stackLimits)-1] + } + stackLen := len(s.stack) + if stackLen <= limit { + return + } else if stackLen == limit+1 && s.stack[limit] == valueTypeUnknown { + vt = valueTypeUnknown + ok = true + return + } else { + vt = s.stack[stackLen-1] + s.stack = s.stack[:stackLen-1] + ok = true + return + } +} + +func (s *valueTypeStack) pop() (ValueType, error) { + if vt, limit, ok := s.tryPop(); ok { + return vt, nil + } else { + return 0, fmt.Errorf("invalid operation: trying to pop at %d with limit %d", len(s.stack), limit) + } +} + +// popAndVerifyType returns an error if the stack value is unexpected. +func (s *valueTypeStack) popAndVerifyType(expected ValueType) error { + have, _, ok := s.tryPop() + if !ok { + return fmt.Errorf("%s missing", ValueTypeName(expected)) + } + if have != expected && have != valueTypeUnknown && expected != valueTypeUnknown { + return fmt.Errorf("type mismatch: expected %s, but was %s", ValueTypeName(expected), ValueTypeName(have)) + } + return nil +} + +func (s *valueTypeStack) push(v ValueType) { + s.stack = append(s.stack, v) + if sp := len(s.stack); sp > s.maximumStackPointer { + s.maximumStackPointer = sp + } +} + +func (s *valueTypeStack) unreachable() { + s.resetAtStackLimit() + s.stack = append(s.stack, valueTypeUnknown) +} + +func (s *valueTypeStack) resetAtStackLimit() { + if len(s.stackLimits) != 0 { + s.stack = s.stack[:s.stackLimits[len(s.stackLimits)-1]] + } else { + s.stack = s.stack[:0] + } +} + +func (s *valueTypeStack) popStackLimit() { + if len(s.stackLimits) != 0 { + s.stackLimits = s.stackLimits[:len(s.stackLimits)-1] + } +} + +// pushStackLimit pushes the control frame's bottom of the stack. +func (s *valueTypeStack) pushStackLimit(params int) { + limit := len(s.stack) - params + s.stackLimits = append(s.stackLimits, limit) +} + +func (s *valueTypeStack) popParams(oc Opcode, want []ValueType, checkAboveLimit bool) error { + return s.requireStackValues(true, InstructionName(oc), want, checkAboveLimit) +} + +func (s *valueTypeStack) popResults(oc Opcode, want []ValueType, checkAboveLimit bool) error { + return s.requireStackValues(false, InstructionName(oc), want, checkAboveLimit) +} + +func (s *valueTypeStack) requireStackValues( + isParam bool, + context string, + want []ValueType, + checkAboveLimit bool, +) error { + limit := 0 + if len(s.stackLimits) > 0 { + limit = s.stackLimits[len(s.stackLimits)-1] + } + // Iterate backwards as we are comparing the desired slice against stack value types. + countWanted := len(want) + + // First, check if there are enough values on the stack. + s.requireStackValuesTmp = s.requireStackValuesTmp[:0] + for i := countWanted - 1; i >= 0; i-- { + popped, _, ok := s.tryPop() + if !ok { + if len(s.requireStackValuesTmp) > len(want) { + return typeCountError(isParam, context, s.requireStackValuesTmp, want) + } + return typeCountError(isParam, context, s.requireStackValuesTmp, want) + } + s.requireStackValuesTmp = append(s.requireStackValuesTmp, popped) + } + + // Now, check if there are too many values. + if checkAboveLimit { + if !(limit == len(s.stack) || (limit+1 == len(s.stack) && s.stack[limit] == valueTypeUnknown)) { + return typeCountError(isParam, context, append(s.stack, want...), want) + } + } + + // Finally, check the types of the values: + for i, v := range s.requireStackValuesTmp { + nextWant := want[countWanted-i-1] // have is in reverse order (stack) + if v != nextWant && v != valueTypeUnknown && nextWant != valueTypeUnknown { + return typeMismatchError(isParam, context, v, nextWant, i) + } + } + return nil +} + +// typeMismatchError returns an error similar to go compiler's error on type mismatch. +func typeMismatchError(isParam bool, context string, have ValueType, want ValueType, i int) error { + var ret strings.Builder + ret.WriteString("cannot use ") + ret.WriteString(ValueTypeName(have)) + if context != "" { + ret.WriteString(" in ") + ret.WriteString(context) + ret.WriteString(" block") + } + if isParam { + ret.WriteString(" as param") + } else { + ret.WriteString(" as result") + } + ret.WriteString("[") + ret.WriteString(strconv.Itoa(i)) + ret.WriteString("] type ") + ret.WriteString(ValueTypeName(want)) + return errors.New(ret.String()) +} + +// typeCountError returns an error similar to go compiler's error on type count mismatch. +func typeCountError(isParam bool, context string, have []ValueType, want []ValueType) error { + var ret strings.Builder + if len(have) > len(want) { + ret.WriteString("too many ") + } else { + ret.WriteString("not enough ") + } + if isParam { + ret.WriteString("params") + } else { + ret.WriteString("results") + } + if context != "" { + if isParam { + ret.WriteString(" for ") + } else { + ret.WriteString(" in ") + } + ret.WriteString(context) + ret.WriteString(" block") + } + ret.WriteString("\n\thave (") + writeValueTypes(have, &ret) + ret.WriteString(")\n\twant (") + writeValueTypes(want, &ret) + ret.WriteByte(')') + return errors.New(ret.String()) +} + +func writeValueTypes(vts []ValueType, ret *strings.Builder) { + switch len(vts) { + case 0: + case 1: + ret.WriteString(ValueTypeName(vts[0])) + default: + ret.WriteString(ValueTypeName(vts[0])) + for _, vt := range vts[1:] { + ret.WriteString(", ") + ret.WriteString(ValueTypeName(vt)) + } + } +} + +func (s *valueTypeStack) String() string { + var typeStrs, limits []string + for _, v := range s.stack { + var str string + if v == valueTypeUnknown { + str = "unknown" + } else { + str = ValueTypeName(v) + } + typeStrs = append(typeStrs, str) + } + for _, d := range s.stackLimits { + limits = append(limits, fmt.Sprintf("%d", d)) + } + return fmt.Sprintf("{stack: [%s], limits: [%s]}", + strings.Join(typeStrs, ", "), strings.Join(limits, ",")) +} + +type controlBlock struct { + startAt, elseAt, endAt uint64 + blockType *FunctionType + blockTypeBytes uint64 + // op is zero when the outermost block + op Opcode +} + +// DecodeBlockType decodes the type index from a positive 33-bit signed integer. Negative numbers indicate up to one +// WebAssembly 1.0 (20191205) compatible result type. Positive numbers are decoded when `enabledFeatures` include +// CoreFeatureMultiValue and include an index in the Module.TypeSection. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-blocktype +// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/multi-value/Overview.md +func DecodeBlockType(types []FunctionType, r *bytes.Reader, enabledFeatures api.CoreFeatures) (*FunctionType, uint64, error) { + raw, num, err := leb128.DecodeInt33AsInt64(r) + if err != nil { + return nil, 0, fmt.Errorf("decode int33: %w", err) + } + + var ret *FunctionType + switch raw { + case -64: // 0x40 in original byte = nil + ret = blockType_v_v + case -1: // 0x7f in original byte = i32 + ret = blockType_v_i32 + case -2: // 0x7e in original byte = i64 + ret = blockType_v_i64 + case -3: // 0x7d in original byte = f32 + ret = blockType_v_f32 + case -4: // 0x7c in original byte = f64 + ret = blockType_v_f64 + case -5: // 0x7b in original byte = v128 + ret = blockType_v_v128 + case -16: // 0x70 in original byte = funcref + ret = blockType_v_funcref + case -17: // 0x6f in original byte = externref + ret = blockType_v_externref + default: + if err = enabledFeatures.RequireEnabled(api.CoreFeatureMultiValue); err != nil { + return nil, num, fmt.Errorf("block with function type return invalid as %v", err) + } + if raw < 0 || (raw >= int64(len(types))) { + return nil, 0, fmt.Errorf("type index out of range: %d", raw) + } + ret = &types[raw] + } + return ret, num, err +} + +// These block types are defined as globals in order to avoid allocations in DecodeBlockType. +var ( + blockType_v_v = &FunctionType{} + blockType_v_i32 = &FunctionType{Results: []ValueType{ValueTypeI32}, ResultNumInUint64: 1} + blockType_v_i64 = &FunctionType{Results: []ValueType{ValueTypeI64}, ResultNumInUint64: 1} + blockType_v_f32 = &FunctionType{Results: []ValueType{ValueTypeF32}, ResultNumInUint64: 1} + blockType_v_f64 = &FunctionType{Results: []ValueType{ValueTypeF64}, ResultNumInUint64: 1} + blockType_v_v128 = &FunctionType{Results: []ValueType{ValueTypeV128}, ResultNumInUint64: 2} + blockType_v_funcref = &FunctionType{Results: []ValueType{ValueTypeFuncref}, ResultNumInUint64: 1} + blockType_v_externref = &FunctionType{Results: []ValueType{ValueTypeExternref}, ResultNumInUint64: 1} +) + +// SplitCallStack returns the input stack resliced to the count of params and +// results, or errors if it isn't long enough for either. +func SplitCallStack(ft *FunctionType, stack []uint64) (params []uint64, results []uint64, err error) { + stackLen := len(stack) + if n := ft.ParamNumInUint64; n > stackLen { + return nil, nil, fmt.Errorf("need %d params, but stack size is %d", n, stackLen) + } else if n > 0 { + params = stack[:n] + } + if n := ft.ResultNumInUint64; n > stackLen { + return nil, nil, fmt.Errorf("need %d results, but stack size is %d", n, stackLen) + } else if n > 0 { + results = stack[:n] + } + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/function_definition.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/function_definition.go new file mode 100644 index 00000000000..c5f6e912176 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/function_definition.go @@ -0,0 +1,188 @@ +package wasm + +import ( + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/internalapi" + "github.com/tetratelabs/wazero/internal/wasmdebug" +) + +// ImportedFunctions returns the definitions of each imported function. +// +// Note: Unlike ExportedFunctions, there is no unique constraint on imports. +func (m *Module) ImportedFunctions() (ret []api.FunctionDefinition) { + for i := uint32(0); i < m.ImportFunctionCount; i++ { + ret = append(ret, m.FunctionDefinition(i)) + } + return +} + +// ExportedFunctions returns the definitions of each exported function. +func (m *Module) ExportedFunctions() map[string]api.FunctionDefinition { + ret := map[string]api.FunctionDefinition{} + for i := range m.ExportSection { + exp := &m.ExportSection[i] + if exp.Type == ExternTypeFunc { + d := m.FunctionDefinition(exp.Index) + ret[exp.Name] = d + } + } + return ret +} + +// FunctionDefinition returns the FunctionDefinition for the given `index`. +func (m *Module) FunctionDefinition(index Index) *FunctionDefinition { + // TODO: function initialization is lazy, but bulk. Make it per function. + m.buildFunctionDefinitions() + return &m.FunctionDefinitionSection[index] +} + +// buildFunctionDefinitions generates function metadata that can be parsed from +// the module. This must be called after all validation. +func (m *Module) buildFunctionDefinitions() { + m.functionDefinitionSectionInitOnce.Do(m.buildFunctionDefinitionsOnce) +} + +func (m *Module) buildFunctionDefinitionsOnce() { + var moduleName string + var functionNames NameMap + var localNames, resultNames IndirectNameMap + if m.NameSection != nil { + moduleName = m.NameSection.ModuleName + functionNames = m.NameSection.FunctionNames + localNames = m.NameSection.LocalNames + resultNames = m.NameSection.ResultNames + } + + importCount := m.ImportFunctionCount + m.FunctionDefinitionSection = make([]FunctionDefinition, importCount+uint32(len(m.FunctionSection))) + + importFuncIdx := Index(0) + for i := range m.ImportSection { + imp := &m.ImportSection[i] + if imp.Type != ExternTypeFunc { + continue + } + + def := &m.FunctionDefinitionSection[importFuncIdx] + def.importDesc = imp + def.index = importFuncIdx + def.Functype = &m.TypeSection[imp.DescFunc] + importFuncIdx++ + } + + for codeIndex, typeIndex := range m.FunctionSection { + code := &m.CodeSection[codeIndex] + idx := importFuncIdx + Index(codeIndex) + def := &m.FunctionDefinitionSection[idx] + def.index = idx + def.Functype = &m.TypeSection[typeIndex] + def.goFunc = code.GoFunc + } + + n, nLen := 0, len(functionNames) + for i := range m.FunctionDefinitionSection { + d := &m.FunctionDefinitionSection[i] + // The function name section begins with imports, but can be sparse. + // This keeps track of how far in the name section we've searched. + funcIdx := d.index + var funcName string + for ; n < nLen; n++ { + next := &functionNames[n] + if next.Index > funcIdx { + break // we have function names, but starting at a later index. + } else if next.Index == funcIdx { + funcName = next.Name + break + } + } + + d.moduleName = moduleName + d.name = funcName + d.Debugname = wasmdebug.FuncName(moduleName, funcName, funcIdx) + d.paramNames = paramNames(localNames, funcIdx, len(d.Functype.Params)) + d.resultNames = paramNames(resultNames, funcIdx, len(d.Functype.Results)) + + for i := range m.ExportSection { + e := &m.ExportSection[i] + if e.Type == ExternTypeFunc && e.Index == funcIdx { + d.exportNames = append(d.exportNames, e.Name) + } + } + } +} + +// FunctionDefinition implements api.FunctionDefinition +type FunctionDefinition struct { + internalapi.WazeroOnlyType + moduleName string + index Index + name string + // Debugname is exported for testing purpose. + Debugname string + goFunc interface{} + // Functype is exported for testing purpose. + Functype *FunctionType + importDesc *Import + exportNames []string + paramNames []string + resultNames []string +} + +// ModuleName implements the same method as documented on api.FunctionDefinition. +func (f *FunctionDefinition) ModuleName() string { + return f.moduleName +} + +// Index implements the same method as documented on api.FunctionDefinition. +func (f *FunctionDefinition) Index() uint32 { + return f.index +} + +// Name implements the same method as documented on api.FunctionDefinition. +func (f *FunctionDefinition) Name() string { + return f.name +} + +// DebugName implements the same method as documented on api.FunctionDefinition. +func (f *FunctionDefinition) DebugName() string { + return f.Debugname +} + +// Import implements the same method as documented on api.FunctionDefinition. +func (f *FunctionDefinition) Import() (moduleName, name string, isImport bool) { + if f.importDesc != nil { + importDesc := f.importDesc + moduleName, name, isImport = importDesc.Module, importDesc.Name, true + } + return +} + +// ExportNames implements the same method as documented on api.FunctionDefinition. +func (f *FunctionDefinition) ExportNames() []string { + return f.exportNames +} + +// GoFunction implements the same method as documented on api.FunctionDefinition. +func (f *FunctionDefinition) GoFunction() interface{} { + return f.goFunc +} + +// ParamTypes implements api.FunctionDefinition ParamTypes. +func (f *FunctionDefinition) ParamTypes() []ValueType { + return f.Functype.Params +} + +// ParamNames implements the same method as documented on api.FunctionDefinition. +func (f *FunctionDefinition) ParamNames() []string { + return f.paramNames +} + +// ResultTypes implements api.FunctionDefinition ResultTypes. +func (f *FunctionDefinition) ResultTypes() []ValueType { + return f.Functype.Results +} + +// ResultNames implements the same method as documented on api.FunctionDefinition. +func (f *FunctionDefinition) ResultNames() []string { + return f.resultNames +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/global.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/global.go new file mode 100644 index 00000000000..abaa2d1f992 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/global.go @@ -0,0 +1,55 @@ +package wasm + +import ( + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/internalapi" +) + +// constantGlobal wraps GlobalInstance to implement api.Global. +type constantGlobal struct { + internalapi.WazeroOnlyType + g *GlobalInstance +} + +// Type implements api.Global. +func (g constantGlobal) Type() api.ValueType { + return g.g.Type.ValType +} + +// Get implements api.Global. +func (g constantGlobal) Get() uint64 { + ret, _ := g.g.Value() + return ret +} + +// String implements api.Global. +func (g constantGlobal) String() string { + return g.g.String() +} + +// mutableGlobal extends constantGlobal to allow updates. +type mutableGlobal struct { + internalapi.WazeroOnlyType + g *GlobalInstance +} + +// Type implements api.Global. +func (g mutableGlobal) Type() api.ValueType { + return g.g.Type.ValType +} + +// Get implements api.Global. +func (g mutableGlobal) Get() uint64 { + ret, _ := g.g.Value() + return ret +} + +// String implements api.Global. +func (g mutableGlobal) String() string { + return g.g.String() +} + +// Set implements the same method as documented on api.MutableGlobal. +func (g mutableGlobal) Set(v uint64) { + g.g.SetValue(v, 0) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/gofunc.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/gofunc.go new file mode 100644 index 00000000000..9510c2588e7 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/gofunc.go @@ -0,0 +1,279 @@ +package wasm + +import ( + "bytes" + "context" + "errors" + "fmt" + "math" + "reflect" + + "github.com/tetratelabs/wazero/api" +) + +type paramsKind byte + +const ( + paramsKindNoContext paramsKind = iota + paramsKindContext + paramsKindContextModule +) + +// Below are reflection code to get the interface type used to parse functions and set values. + +var ( + moduleType = reflect.TypeOf((*api.Module)(nil)).Elem() + goContextType = reflect.TypeOf((*context.Context)(nil)).Elem() + errorType = reflect.TypeOf((*error)(nil)).Elem() +) + +// compile-time check to ensure reflectGoModuleFunction implements +// api.GoModuleFunction. +var _ api.GoModuleFunction = (*reflectGoModuleFunction)(nil) + +type reflectGoModuleFunction struct { + fn *reflect.Value + params, results []ValueType +} + +// Call implements the same method as documented on api.GoModuleFunction. +func (f *reflectGoModuleFunction) Call(ctx context.Context, mod api.Module, stack []uint64) { + callGoFunc(ctx, mod, f.fn, stack) +} + +// EqualTo is exposed for testing. +func (f *reflectGoModuleFunction) EqualTo(that interface{}) bool { + if f2, ok := that.(*reflectGoModuleFunction); !ok { + return false + } else { + // TODO compare reflect pointers + return bytes.Equal(f.params, f2.params) && bytes.Equal(f.results, f2.results) + } +} + +// compile-time check to ensure reflectGoFunction implements api.GoFunction. +var _ api.GoFunction = (*reflectGoFunction)(nil) + +type reflectGoFunction struct { + fn *reflect.Value + pk paramsKind + params, results []ValueType +} + +// EqualTo is exposed for testing. +func (f *reflectGoFunction) EqualTo(that interface{}) bool { + if f2, ok := that.(*reflectGoFunction); !ok { + return false + } else { + // TODO compare reflect pointers + return f.pk == f2.pk && + bytes.Equal(f.params, f2.params) && bytes.Equal(f.results, f2.results) + } +} + +// Call implements the same method as documented on api.GoFunction. +func (f *reflectGoFunction) Call(ctx context.Context, stack []uint64) { + if f.pk == paramsKindNoContext { + ctx = nil + } + callGoFunc(ctx, nil, f.fn, stack) +} + +// callGoFunc executes the reflective function by converting params to Go +// types. The results of the function call are converted back to api.ValueType. +func callGoFunc(ctx context.Context, mod api.Module, fn *reflect.Value, stack []uint64) { + tp := fn.Type() + + var in []reflect.Value + pLen := tp.NumIn() + if pLen != 0 { + in = make([]reflect.Value, pLen) + + i := 0 + if ctx != nil { + in[0] = newContextVal(ctx) + i++ + } + if mod != nil { + in[1] = newModuleVal(mod) + i++ + } + + for j := 0; i < pLen; i++ { + next := tp.In(i) + val := reflect.New(next).Elem() + k := next.Kind() + raw := stack[j] + j++ + + switch k { + case reflect.Float32: + val.SetFloat(float64(math.Float32frombits(uint32(raw)))) + case reflect.Float64: + val.SetFloat(math.Float64frombits(raw)) + case reflect.Uint32, reflect.Uint64, reflect.Uintptr: + val.SetUint(raw) + case reflect.Int32, reflect.Int64: + val.SetInt(int64(raw)) + default: + panic(fmt.Errorf("BUG: param[%d] has an invalid type: %v", i, k)) + } + in[i] = val + } + } + + // Execute the host function and push back the call result onto the stack. + for i, ret := range fn.Call(in) { + switch ret.Kind() { + case reflect.Float32: + stack[i] = uint64(math.Float32bits(float32(ret.Float()))) + case reflect.Float64: + stack[i] = math.Float64bits(ret.Float()) + case reflect.Uint32, reflect.Uint64, reflect.Uintptr: + stack[i] = ret.Uint() + case reflect.Int32, reflect.Int64: + stack[i] = uint64(ret.Int()) + default: + panic(fmt.Errorf("BUG: result[%d] has an invalid type: %v", i, ret.Kind())) + } + } +} + +func newContextVal(ctx context.Context) reflect.Value { + val := reflect.New(goContextType).Elem() + val.Set(reflect.ValueOf(ctx)) + return val +} + +func newModuleVal(m api.Module) reflect.Value { + val := reflect.New(moduleType).Elem() + val.Set(reflect.ValueOf(m)) + return val +} + +// MustParseGoReflectFuncCode parses Code from the go function or panics. +// +// Exposing this simplifies FunctionDefinition of host functions in built-in host +// modules and tests. +func MustParseGoReflectFuncCode(fn interface{}) Code { + _, _, code, err := parseGoReflectFunc(fn) + if err != nil { + panic(err) + } + return code +} + +func parseGoReflectFunc(fn interface{}) (params, results []ValueType, code Code, err error) { + fnV := reflect.ValueOf(fn) + p := fnV.Type() + + if fnV.Kind() != reflect.Func { + err = fmt.Errorf("kind != func: %s", fnV.Kind().String()) + return + } + + pk, kindErr := kind(p) + if kindErr != nil { + err = kindErr + return + } + + pOffset := 0 + switch pk { + case paramsKindNoContext: + case paramsKindContext: + pOffset = 1 + case paramsKindContextModule: + pOffset = 2 + } + + pCount := p.NumIn() - pOffset + if pCount > 0 { + params = make([]ValueType, pCount) + } + for i := 0; i < len(params); i++ { + pI := p.In(i + pOffset) + if t, ok := getTypeOf(pI.Kind()); ok { + params[i] = t + continue + } + + // Now, we will definitely err, decide which message is best + var arg0Type reflect.Type + if hc := pI.Implements(moduleType); hc { + arg0Type = moduleType + } else if gc := pI.Implements(goContextType); gc { + arg0Type = goContextType + } + + if arg0Type != nil { + err = fmt.Errorf("param[%d] is a %s, which may be defined only once as param[0]", i+pOffset, arg0Type) + } else { + err = fmt.Errorf("param[%d] is unsupported: %s", i+pOffset, pI.Kind()) + } + return + } + + rCount := p.NumOut() + if rCount > 0 { + results = make([]ValueType, rCount) + } + for i := 0; i < len(results); i++ { + rI := p.Out(i) + if t, ok := getTypeOf(rI.Kind()); ok { + results[i] = t + continue + } + + // Now, we will definitely err, decide which message is best + if rI.Implements(errorType) { + err = fmt.Errorf("result[%d] is an error, which is unsupported", i) + } else { + err = fmt.Errorf("result[%d] is unsupported: %s", i, rI.Kind()) + } + return + } + + code = Code{} + if pk == paramsKindContextModule { + code.GoFunc = &reflectGoModuleFunction{fn: &fnV, params: params, results: results} + } else { + code.GoFunc = &reflectGoFunction{pk: pk, fn: &fnV, params: params, results: results} + } + return +} + +func kind(p reflect.Type) (paramsKind, error) { + pCount := p.NumIn() + if pCount > 0 && p.In(0).Kind() == reflect.Interface { + p0 := p.In(0) + if p0.Implements(moduleType) { + return 0, errors.New("invalid signature: api.Module parameter must be preceded by context.Context") + } else if p0.Implements(goContextType) { + if pCount >= 2 && p.In(1).Implements(moduleType) { + return paramsKindContextModule, nil + } + return paramsKindContext, nil + } + } + // Without context param allows portability with reflective runtimes. + // This allows people to more easily port to wazero. + return paramsKindNoContext, nil +} + +func getTypeOf(kind reflect.Kind) (ValueType, bool) { + switch kind { + case reflect.Float64: + return ValueTypeF64, true + case reflect.Float32: + return ValueTypeF32, true + case reflect.Int32, reflect.Uint32: + return ValueTypeI32, true + case reflect.Int64, reflect.Uint64: + return ValueTypeI64, true + case reflect.Uintptr: + return ValueTypeExternref, true + default: + return 0x00, false + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/host.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/host.go new file mode 100644 index 00000000000..bca686d1df6 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/host.go @@ -0,0 +1,179 @@ +package wasm + +import ( + "errors" + "fmt" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/wasmdebug" +) + +type HostFuncExporter interface { + ExportHostFunc(*HostFunc) +} + +// HostFunc is a function with an inlined type, used for NewHostModule. +// Any corresponding FunctionType will be reused or added to the Module. +type HostFunc struct { + // ExportName is the only value returned by api.FunctionDefinition. + ExportName string + + // Name is equivalent to the same method on api.FunctionDefinition. + Name string + + // ParamTypes is equivalent to the same method on api.FunctionDefinition. + ParamTypes []ValueType + + // ParamNames is equivalent to the same method on api.FunctionDefinition. + ParamNames []string + + // ResultTypes is equivalent to the same method on api.FunctionDefinition. + ResultTypes []ValueType + + // ResultNames is equivalent to the same method on api.FunctionDefinition. + ResultNames []string + + // Code is the equivalent function in the SectionIDCode. + Code Code +} + +// WithGoModuleFunc returns a copy of the function, replacing its Code.GoFunc. +func (f *HostFunc) WithGoModuleFunc(fn api.GoModuleFunc) *HostFunc { + ret := *f + ret.Code.GoFunc = fn + return &ret +} + +// NewHostModule is defined internally for use in WASI tests and to keep the code size in the root directory small. +func NewHostModule( + moduleName string, + exportNames []string, + nameToHostFunc map[string]*HostFunc, + enabledFeatures api.CoreFeatures, +) (m *Module, err error) { + if moduleName != "" { + m = &Module{NameSection: &NameSection{ModuleName: moduleName}} + } else { + return nil, errors.New("a module name must not be empty") + } + + if exportCount := uint32(len(nameToHostFunc)); exportCount > 0 { + m.ExportSection = make([]Export, 0, exportCount) + m.Exports = make(map[string]*Export, exportCount) + if err = addFuncs(m, exportNames, nameToHostFunc, enabledFeatures); err != nil { + return + } + } + + m.IsHostModule = true + // Uses the address of *wasm.Module as the module ID so that host functions can have each state per compilation. + // Downside of this is that compilation cache on host functions (trampoline codes for Go functions and + // Wasm codes for Wasm-implemented host functions) are not available and compiles each time. On the other hand, + // compilation of host modules is not costly as it's merely small trampolines vs the real-world native Wasm binary. + // TODO: refactor engines so that we can properly cache compiled machine codes for host modules. + m.AssignModuleID([]byte(fmt.Sprintf("@@@@@@@@%p", m)), // @@@@@@@@ = any 8 bytes different from Wasm header. + nil, false) + return +} + +func addFuncs( + m *Module, + exportNames []string, + nameToHostFunc map[string]*HostFunc, + enabledFeatures api.CoreFeatures, +) (err error) { + if m.NameSection == nil { + m.NameSection = &NameSection{} + } + moduleName := m.NameSection.ModuleName + + for _, k := range exportNames { + hf := nameToHostFunc[k] + if hf.Name == "" { + hf.Name = k // default name to export name + } + switch hf.Code.GoFunc.(type) { + case api.GoModuleFunction, api.GoFunction: + continue // already parsed + } + + // Resolve the code using reflection + hf.ParamTypes, hf.ResultTypes, hf.Code, err = parseGoReflectFunc(hf.Code.GoFunc) + if err != nil { + return fmt.Errorf("func[%s.%s] %w", moduleName, k, err) + } + + // Assign names to the function, if they exist. + params := hf.ParamTypes + if paramNames := hf.ParamNames; paramNames != nil { + if paramNamesLen := len(paramNames); paramNamesLen != len(params) { + return fmt.Errorf("func[%s.%s] has %d params, but %d params names", moduleName, k, paramNamesLen, len(params)) + } + } + + results := hf.ResultTypes + if resultNames := hf.ResultNames; resultNames != nil { + if resultNamesLen := len(resultNames); resultNamesLen != len(results) { + return fmt.Errorf("func[%s.%s] has %d results, but %d results names", moduleName, k, resultNamesLen, len(results)) + } + } + } + + funcCount := uint32(len(exportNames)) + m.NameSection.FunctionNames = make([]NameAssoc, 0, funcCount) + m.FunctionSection = make([]Index, 0, funcCount) + m.CodeSection = make([]Code, 0, funcCount) + + idx := Index(0) + for _, name := range exportNames { + hf := nameToHostFunc[name] + debugName := wasmdebug.FuncName(moduleName, name, idx) + typeIdx, typeErr := m.maybeAddType(hf.ParamTypes, hf.ResultTypes, enabledFeatures) + if typeErr != nil { + return fmt.Errorf("func[%s] %v", debugName, typeErr) + } + m.FunctionSection = append(m.FunctionSection, typeIdx) + m.CodeSection = append(m.CodeSection, hf.Code) + + export := hf.ExportName + m.ExportSection = append(m.ExportSection, Export{Type: ExternTypeFunc, Name: export, Index: idx}) + m.Exports[export] = &m.ExportSection[len(m.ExportSection)-1] + m.NameSection.FunctionNames = append(m.NameSection.FunctionNames, NameAssoc{Index: idx, Name: hf.Name}) + + if len(hf.ParamNames) > 0 { + localNames := NameMapAssoc{Index: idx} + for i, n := range hf.ParamNames { + localNames.NameMap = append(localNames.NameMap, NameAssoc{Index: Index(i), Name: n}) + } + m.NameSection.LocalNames = append(m.NameSection.LocalNames, localNames) + } + if len(hf.ResultNames) > 0 { + resultNames := NameMapAssoc{Index: idx} + for i, n := range hf.ResultNames { + resultNames.NameMap = append(resultNames.NameMap, NameAssoc{Index: Index(i), Name: n}) + } + m.NameSection.ResultNames = append(m.NameSection.ResultNames, resultNames) + } + idx++ + } + return nil +} + +func (m *Module) maybeAddType(params, results []ValueType, enabledFeatures api.CoreFeatures) (Index, error) { + if len(results) > 1 { + // Guard >1.0 feature multi-value + if err := enabledFeatures.RequireEnabled(api.CoreFeatureMultiValue); err != nil { + return 0, fmt.Errorf("multiple result types invalid as %v", err) + } + } + for i := range m.TypeSection { + t := &m.TypeSection[i] + if t.EqualsSignature(params, results) { + return Index(i), nil + } + } + + result := m.SectionElementCount(SectionIDType) + m.TypeSection = append(m.TypeSection, FunctionType{Params: params, Results: results}) + return result, nil +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/instruction.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/instruction.go new file mode 100644 index 00000000000..7f76b1e5267 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/instruction.go @@ -0,0 +1,1891 @@ +package wasm + +// Opcode is the binary Opcode of an instruction. See also InstructionName +type Opcode = byte + +const ( + // OpcodeUnreachable causes an unconditional trap. + OpcodeUnreachable Opcode = 0x00 + // OpcodeNop does nothing + OpcodeNop Opcode = 0x01 + // OpcodeBlock brackets a sequence of instructions. A branch instruction on an if label breaks out to after its + // OpcodeEnd. + OpcodeBlock Opcode = 0x02 + // OpcodeLoop brackets a sequence of instructions. A branch instruction on a loop label will jump back to the + // beginning of its block. + OpcodeLoop Opcode = 0x03 + // OpcodeIf brackets a sequence of instructions. When the top of the stack evaluates to 1, the block is executed. + // Zero jumps to the optional OpcodeElse. A branch instruction on an if label breaks out to after its OpcodeEnd. + OpcodeIf Opcode = 0x04 + // OpcodeElse brackets a sequence of instructions enclosed by an OpcodeIf. A branch instruction on a then label + // breaks out to after the OpcodeEnd on the enclosing OpcodeIf. + OpcodeElse Opcode = 0x05 + // OpcodeEnd terminates a control instruction OpcodeBlock, OpcodeLoop or OpcodeIf. + OpcodeEnd Opcode = 0x0b + + // OpcodeBr is a stack-polymorphic opcode that performs an unconditional branch. How the stack is modified depends + // on whether the "br" is enclosed by a loop, and if CoreFeatureMultiValue is enabled. + // + // Here are the rules in pseudocode about how the stack is modified based on the "br" operand L (label): + // if L is loop: append(L.originalStackWithoutInputs, N-values popped from the stack) where N == L.inputs + // else: append(L.originalStackWithoutInputs, N-values popped from the stack) where N == L.results + // + // In WebAssembly 1.0 (20191205), N can be zero or one. When CoreFeatureMultiValue is enabled, N can be more than one, + // depending on the type use of the label L. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#-hrefsyntax-instr-controlmathsfbrl + OpcodeBr Opcode = 0x0c + + OpcodeBrIf Opcode = 0x0d + OpcodeBrTable Opcode = 0x0e + OpcodeReturn Opcode = 0x0f + OpcodeCall Opcode = 0x10 + OpcodeCallIndirect Opcode = 0x11 + + // parametric instructions + + OpcodeDrop Opcode = 0x1a + OpcodeSelect Opcode = 0x1b + OpcodeTypedSelect Opcode = 0x1c + + // variable instructions + + OpcodeLocalGet Opcode = 0x20 + OpcodeLocalSet Opcode = 0x21 + OpcodeLocalTee Opcode = 0x22 + OpcodeGlobalGet Opcode = 0x23 + OpcodeGlobalSet Opcode = 0x24 + + // Below are toggled with CoreFeatureReferenceTypes + + OpcodeTableGet Opcode = 0x25 + OpcodeTableSet Opcode = 0x26 + + // memory instructions + + OpcodeI32Load Opcode = 0x28 + OpcodeI64Load Opcode = 0x29 + OpcodeF32Load Opcode = 0x2a + OpcodeF64Load Opcode = 0x2b + OpcodeI32Load8S Opcode = 0x2c + OpcodeI32Load8U Opcode = 0x2d + OpcodeI32Load16S Opcode = 0x2e + OpcodeI32Load16U Opcode = 0x2f + OpcodeI64Load8S Opcode = 0x30 + OpcodeI64Load8U Opcode = 0x31 + OpcodeI64Load16S Opcode = 0x32 + OpcodeI64Load16U Opcode = 0x33 + OpcodeI64Load32S Opcode = 0x34 + OpcodeI64Load32U Opcode = 0x35 + OpcodeI32Store Opcode = 0x36 + OpcodeI64Store Opcode = 0x37 + OpcodeF32Store Opcode = 0x38 + OpcodeF64Store Opcode = 0x39 + OpcodeI32Store8 Opcode = 0x3a + OpcodeI32Store16 Opcode = 0x3b + OpcodeI64Store8 Opcode = 0x3c + OpcodeI64Store16 Opcode = 0x3d + OpcodeI64Store32 Opcode = 0x3e + OpcodeMemorySize Opcode = 0x3f + OpcodeMemoryGrow Opcode = 0x40 + + // const instructions + + OpcodeI32Const Opcode = 0x41 + OpcodeI64Const Opcode = 0x42 + OpcodeF32Const Opcode = 0x43 + OpcodeF64Const Opcode = 0x44 + + // numeric instructions + + OpcodeI32Eqz Opcode = 0x45 + OpcodeI32Eq Opcode = 0x46 + OpcodeI32Ne Opcode = 0x47 + OpcodeI32LtS Opcode = 0x48 + OpcodeI32LtU Opcode = 0x49 + OpcodeI32GtS Opcode = 0x4a + OpcodeI32GtU Opcode = 0x4b + OpcodeI32LeS Opcode = 0x4c + OpcodeI32LeU Opcode = 0x4d + OpcodeI32GeS Opcode = 0x4e + OpcodeI32GeU Opcode = 0x4f + + OpcodeI64Eqz Opcode = 0x50 + OpcodeI64Eq Opcode = 0x51 + OpcodeI64Ne Opcode = 0x52 + OpcodeI64LtS Opcode = 0x53 + OpcodeI64LtU Opcode = 0x54 + OpcodeI64GtS Opcode = 0x55 + OpcodeI64GtU Opcode = 0x56 + OpcodeI64LeS Opcode = 0x57 + OpcodeI64LeU Opcode = 0x58 + OpcodeI64GeS Opcode = 0x59 + OpcodeI64GeU Opcode = 0x5a + + OpcodeF32Eq Opcode = 0x5b + OpcodeF32Ne Opcode = 0x5c + OpcodeF32Lt Opcode = 0x5d + OpcodeF32Gt Opcode = 0x5e + OpcodeF32Le Opcode = 0x5f + OpcodeF32Ge Opcode = 0x60 + + OpcodeF64Eq Opcode = 0x61 + OpcodeF64Ne Opcode = 0x62 + OpcodeF64Lt Opcode = 0x63 + OpcodeF64Gt Opcode = 0x64 + OpcodeF64Le Opcode = 0x65 + OpcodeF64Ge Opcode = 0x66 + + OpcodeI32Clz Opcode = 0x67 + OpcodeI32Ctz Opcode = 0x68 + OpcodeI32Popcnt Opcode = 0x69 + OpcodeI32Add Opcode = 0x6a + OpcodeI32Sub Opcode = 0x6b + OpcodeI32Mul Opcode = 0x6c + OpcodeI32DivS Opcode = 0x6d + OpcodeI32DivU Opcode = 0x6e + OpcodeI32RemS Opcode = 0x6f + OpcodeI32RemU Opcode = 0x70 + OpcodeI32And Opcode = 0x71 + OpcodeI32Or Opcode = 0x72 + OpcodeI32Xor Opcode = 0x73 + OpcodeI32Shl Opcode = 0x74 + OpcodeI32ShrS Opcode = 0x75 + OpcodeI32ShrU Opcode = 0x76 + OpcodeI32Rotl Opcode = 0x77 + OpcodeI32Rotr Opcode = 0x78 + + OpcodeI64Clz Opcode = 0x79 + OpcodeI64Ctz Opcode = 0x7a + OpcodeI64Popcnt Opcode = 0x7b + OpcodeI64Add Opcode = 0x7c + OpcodeI64Sub Opcode = 0x7d + OpcodeI64Mul Opcode = 0x7e + OpcodeI64DivS Opcode = 0x7f + OpcodeI64DivU Opcode = 0x80 + OpcodeI64RemS Opcode = 0x81 + OpcodeI64RemU Opcode = 0x82 + OpcodeI64And Opcode = 0x83 + OpcodeI64Or Opcode = 0x84 + OpcodeI64Xor Opcode = 0x85 + OpcodeI64Shl Opcode = 0x86 + OpcodeI64ShrS Opcode = 0x87 + OpcodeI64ShrU Opcode = 0x88 + OpcodeI64Rotl Opcode = 0x89 + OpcodeI64Rotr Opcode = 0x8a + + OpcodeF32Abs Opcode = 0x8b + OpcodeF32Neg Opcode = 0x8c + OpcodeF32Ceil Opcode = 0x8d + OpcodeF32Floor Opcode = 0x8e + OpcodeF32Trunc Opcode = 0x8f + OpcodeF32Nearest Opcode = 0x90 + OpcodeF32Sqrt Opcode = 0x91 + OpcodeF32Add Opcode = 0x92 + OpcodeF32Sub Opcode = 0x93 + OpcodeF32Mul Opcode = 0x94 + OpcodeF32Div Opcode = 0x95 + OpcodeF32Min Opcode = 0x96 + OpcodeF32Max Opcode = 0x97 + OpcodeF32Copysign Opcode = 0x98 + + OpcodeF64Abs Opcode = 0x99 + OpcodeF64Neg Opcode = 0x9a + OpcodeF64Ceil Opcode = 0x9b + OpcodeF64Floor Opcode = 0x9c + OpcodeF64Trunc Opcode = 0x9d + OpcodeF64Nearest Opcode = 0x9e + OpcodeF64Sqrt Opcode = 0x9f + OpcodeF64Add Opcode = 0xa0 + OpcodeF64Sub Opcode = 0xa1 + OpcodeF64Mul Opcode = 0xa2 + OpcodeF64Div Opcode = 0xa3 + OpcodeF64Min Opcode = 0xa4 + OpcodeF64Max Opcode = 0xa5 + OpcodeF64Copysign Opcode = 0xa6 + + OpcodeI32WrapI64 Opcode = 0xa7 + OpcodeI32TruncF32S Opcode = 0xa8 + OpcodeI32TruncF32U Opcode = 0xa9 + OpcodeI32TruncF64S Opcode = 0xaa + OpcodeI32TruncF64U Opcode = 0xab + + OpcodeI64ExtendI32S Opcode = 0xac + OpcodeI64ExtendI32U Opcode = 0xad + OpcodeI64TruncF32S Opcode = 0xae + OpcodeI64TruncF32U Opcode = 0xaf + OpcodeI64TruncF64S Opcode = 0xb0 + OpcodeI64TruncF64U Opcode = 0xb1 + + OpcodeF32ConvertI32S Opcode = 0xb2 + OpcodeF32ConvertI32U Opcode = 0xb3 + OpcodeF32ConvertI64S Opcode = 0xb4 + OpcodeF32ConvertI64U Opcode = 0xb5 + OpcodeF32DemoteF64 Opcode = 0xb6 + + OpcodeF64ConvertI32S Opcode = 0xb7 + OpcodeF64ConvertI32U Opcode = 0xb8 + OpcodeF64ConvertI64S Opcode = 0xb9 + OpcodeF64ConvertI64U Opcode = 0xba + OpcodeF64PromoteF32 Opcode = 0xbb + + OpcodeI32ReinterpretF32 Opcode = 0xbc + OpcodeI64ReinterpretF64 Opcode = 0xbd + OpcodeF32ReinterpretI32 Opcode = 0xbe + OpcodeF64ReinterpretI64 Opcode = 0xbf + + // OpcodeRefNull pushes a null reference value whose type is specified by immediate to this opcode. + // This is defined in the reference-types proposal, but necessary for CoreFeatureBulkMemoryOperations as well. + // + // Currently only supported in the constant expression in element segments. + OpcodeRefNull = 0xd0 + // OpcodeRefIsNull pops a reference value, and pushes 1 if it is null, 0 otherwise. + // This is defined in the reference-types proposal, but necessary for CoreFeatureBulkMemoryOperations as well. + // + // Currently not supported. + OpcodeRefIsNull = 0xd1 + // OpcodeRefFunc pushes a funcref value whose index equals the immediate to this opcode. + // This is defined in the reference-types proposal, but necessary for CoreFeatureBulkMemoryOperations as well. + // + // Currently, this is only supported in the constant expression in element segments. + OpcodeRefFunc = 0xd2 + + // Below are toggled with CoreFeatureSignExtensionOps + + // OpcodeI32Extend8S extends a signed 8-bit integer to a 32-bit integer. + // Note: This is dependent on the flag CoreFeatureSignExtensionOps + OpcodeI32Extend8S Opcode = 0xc0 + + // OpcodeI32Extend16S extends a signed 16-bit integer to a 32-bit integer. + // Note: This is dependent on the flag CoreFeatureSignExtensionOps + OpcodeI32Extend16S Opcode = 0xc1 + + // OpcodeI64Extend8S extends a signed 8-bit integer to a 64-bit integer. + // Note: This is dependent on the flag CoreFeatureSignExtensionOps + OpcodeI64Extend8S Opcode = 0xc2 + + // OpcodeI64Extend16S extends a signed 16-bit integer to a 64-bit integer. + // Note: This is dependent on the flag CoreFeatureSignExtensionOps + OpcodeI64Extend16S Opcode = 0xc3 + + // OpcodeI64Extend32S extends a signed 32-bit integer to a 64-bit integer. + // Note: This is dependent on the flag CoreFeatureSignExtensionOps + OpcodeI64Extend32S Opcode = 0xc4 + + // OpcodeMiscPrefix is the prefix of various multi-byte opcodes. + // Introduced in CoreFeatureNonTrappingFloatToIntConversion, but used in other + // features, such as CoreFeatureBulkMemoryOperations. + OpcodeMiscPrefix Opcode = 0xfc + + // OpcodeVecPrefix is the prefix of all vector isntructions introduced in + // CoreFeatureSIMD. + OpcodeVecPrefix Opcode = 0xfd + + // OpcodeAtomicPrefix is the prefix of all atomic instructions introduced in + // CoreFeatureThreads. + OpcodeAtomicPrefix Opcode = 0xfe +) + +// OpcodeMisc represents opcodes of the miscellaneous operations. +// Such an operations has multi-byte encoding which is prefixed by OpcodeMiscPrefix. +type OpcodeMisc = byte + +const ( + // Below are toggled with CoreFeatureNonTrappingFloatToIntConversion. + // https://github.com/WebAssembly/spec/blob/ce4b6c4d47eb06098cc7ab2e81f24748da822f20/proposals/nontrapping-float-to-int-conversion/Overview.md + + OpcodeMiscI32TruncSatF32S OpcodeMisc = 0x00 + OpcodeMiscI32TruncSatF32U OpcodeMisc = 0x01 + OpcodeMiscI32TruncSatF64S OpcodeMisc = 0x02 + OpcodeMiscI32TruncSatF64U OpcodeMisc = 0x03 + OpcodeMiscI64TruncSatF32S OpcodeMisc = 0x04 + OpcodeMiscI64TruncSatF32U OpcodeMisc = 0x05 + OpcodeMiscI64TruncSatF64S OpcodeMisc = 0x06 + OpcodeMiscI64TruncSatF64U OpcodeMisc = 0x07 + + // Below are toggled with CoreFeatureBulkMemoryOperations. + // Opcodes are those new in document/core/appendix/index-instructions.rst (the commit that merged the feature). + // See https://github.com/WebAssembly/spec/commit/7fa2f20a6df4cf1c114582c8cb60f5bfcdbf1be1 + // See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#bulk-memory-and-table-instructions + + OpcodeMiscMemoryInit OpcodeMisc = 0x08 + OpcodeMiscDataDrop OpcodeMisc = 0x09 + OpcodeMiscMemoryCopy OpcodeMisc = 0x0a + OpcodeMiscMemoryFill OpcodeMisc = 0x0b + OpcodeMiscTableInit OpcodeMisc = 0x0c + OpcodeMiscElemDrop OpcodeMisc = 0x0d + OpcodeMiscTableCopy OpcodeMisc = 0x0e + + // Below are toggled with CoreFeatureReferenceTypes + + OpcodeMiscTableGrow OpcodeMisc = 0x0f + OpcodeMiscTableSize OpcodeMisc = 0x10 + OpcodeMiscTableFill OpcodeMisc = 0x11 +) + +// OpcodeVec represents an opcode of a vector instructions which has +// multi-byte encoding and is prefixed by OpcodeMiscPrefix. +// +// These opcodes are toggled with CoreFeatureSIMD. +type OpcodeVec = byte + +const ( + // Loads and stores. + + OpcodeVecV128Load OpcodeVec = 0x00 + OpcodeVecV128Load8x8s OpcodeVec = 0x01 + OpcodeVecV128Load8x8u OpcodeVec = 0x02 + OpcodeVecV128Load16x4s OpcodeVec = 0x03 + OpcodeVecV128Load16x4u OpcodeVec = 0x04 + OpcodeVecV128Load32x2s OpcodeVec = 0x05 + OpcodeVecV128Load32x2u OpcodeVec = 0x06 + OpcodeVecV128Load8Splat OpcodeVec = 0x07 + OpcodeVecV128Load16Splat OpcodeVec = 0x08 + OpcodeVecV128Load32Splat OpcodeVec = 0x09 + OpcodeVecV128Load64Splat OpcodeVec = 0x0a + + OpcodeVecV128Load32zero OpcodeVec = 0x5c + OpcodeVecV128Load64zero OpcodeVec = 0x5d + + OpcodeVecV128Store OpcodeVec = 0x0b + OpcodeVecV128Load8Lane OpcodeVec = 0x54 + OpcodeVecV128Load16Lane OpcodeVec = 0x55 + OpcodeVecV128Load32Lane OpcodeVec = 0x56 + OpcodeVecV128Load64Lane OpcodeVec = 0x57 + OpcodeVecV128Store8Lane OpcodeVec = 0x58 + OpcodeVecV128Store16Lane OpcodeVec = 0x59 + OpcodeVecV128Store32Lane OpcodeVec = 0x5a + OpcodeVecV128Store64Lane OpcodeVec = 0x5b + + // OpcodeVecV128Const is the vector const instruction. + OpcodeVecV128Const OpcodeVec = 0x0c + + // OpcodeVecV128i8x16Shuffle is the vector shuffle instruction. + OpcodeVecV128i8x16Shuffle OpcodeVec = 0x0d + + // Extrac and replaces. + + OpcodeVecI8x16ExtractLaneS OpcodeVec = 0x15 + OpcodeVecI8x16ExtractLaneU OpcodeVec = 0x16 + OpcodeVecI8x16ReplaceLane OpcodeVec = 0x17 + OpcodeVecI16x8ExtractLaneS OpcodeVec = 0x18 + OpcodeVecI16x8ExtractLaneU OpcodeVec = 0x19 + OpcodeVecI16x8ReplaceLane OpcodeVec = 0x1a + OpcodeVecI32x4ExtractLane OpcodeVec = 0x1b + OpcodeVecI32x4ReplaceLane OpcodeVec = 0x1c + OpcodeVecI64x2ExtractLane OpcodeVec = 0x1d + OpcodeVecI64x2ReplaceLane OpcodeVec = 0x1e + OpcodeVecF32x4ExtractLane OpcodeVec = 0x1f + OpcodeVecF32x4ReplaceLane OpcodeVec = 0x20 + OpcodeVecF64x2ExtractLane OpcodeVec = 0x21 + OpcodeVecF64x2ReplaceLane OpcodeVec = 0x22 + + // Splat and swizzle. + + OpcodeVecI8x16Swizzle OpcodeVec = 0x0e + OpcodeVecI8x16Splat OpcodeVec = 0x0f + OpcodeVecI16x8Splat OpcodeVec = 0x10 + OpcodeVecI32x4Splat OpcodeVec = 0x11 + OpcodeVecI64x2Splat OpcodeVec = 0x12 + OpcodeVecF32x4Splat OpcodeVec = 0x13 + OpcodeVecF64x2Splat OpcodeVec = 0x14 + + // i8 comparisons. + + OpcodeVecI8x16Eq OpcodeVec = 0x23 + OpcodeVecI8x16Ne OpcodeVec = 0x24 + OpcodeVecI8x16LtS OpcodeVec = 0x25 + OpcodeVecI8x16LtU OpcodeVec = 0x26 + OpcodeVecI8x16GtS OpcodeVec = 0x27 + OpcodeVecI8x16GtU OpcodeVec = 0x28 + OpcodeVecI8x16LeS OpcodeVec = 0x29 + OpcodeVecI8x16LeU OpcodeVec = 0x2a + OpcodeVecI8x16GeS OpcodeVec = 0x2b + OpcodeVecI8x16GeU OpcodeVec = 0x2c + + // i16 comparisons. + + OpcodeVecI16x8Eq OpcodeVec = 0x2d + OpcodeVecI16x8Ne OpcodeVec = 0x2e + OpcodeVecI16x8LtS OpcodeVec = 0x2f + OpcodeVecI16x8LtU OpcodeVec = 0x30 + OpcodeVecI16x8GtS OpcodeVec = 0x31 + OpcodeVecI16x8GtU OpcodeVec = 0x32 + OpcodeVecI16x8LeS OpcodeVec = 0x33 + OpcodeVecI16x8LeU OpcodeVec = 0x34 + OpcodeVecI16x8GeS OpcodeVec = 0x35 + OpcodeVecI16x8GeU OpcodeVec = 0x36 + + // i32 comparisons. + + OpcodeVecI32x4Eq OpcodeVec = 0x37 + OpcodeVecI32x4Ne OpcodeVec = 0x38 + OpcodeVecI32x4LtS OpcodeVec = 0x39 + OpcodeVecI32x4LtU OpcodeVec = 0x3a + OpcodeVecI32x4GtS OpcodeVec = 0x3b + OpcodeVecI32x4GtU OpcodeVec = 0x3c + OpcodeVecI32x4LeS OpcodeVec = 0x3d + OpcodeVecI32x4LeU OpcodeVec = 0x3e + OpcodeVecI32x4GeS OpcodeVec = 0x3f + OpcodeVecI32x4GeU OpcodeVec = 0x40 + + // i64 comparisons. + + OpcodeVecI64x2Eq OpcodeVec = 0xd6 + OpcodeVecI64x2Ne OpcodeVec = 0xd7 + OpcodeVecI64x2LtS OpcodeVec = 0xd8 + OpcodeVecI64x2GtS OpcodeVec = 0xd9 + OpcodeVecI64x2LeS OpcodeVec = 0xda + OpcodeVecI64x2GeS OpcodeVec = 0xdb + + // f32 comparisons. + + OpcodeVecF32x4Eq OpcodeVec = 0x41 + OpcodeVecF32x4Ne OpcodeVec = 0x42 + OpcodeVecF32x4Lt OpcodeVec = 0x43 + OpcodeVecF32x4Gt OpcodeVec = 0x44 + OpcodeVecF32x4Le OpcodeVec = 0x45 + OpcodeVecF32x4Ge OpcodeVec = 0x46 + + // f64 comparisons. + + OpcodeVecF64x2Eq OpcodeVec = 0x47 + OpcodeVecF64x2Ne OpcodeVec = 0x48 + OpcodeVecF64x2Lt OpcodeVec = 0x49 + OpcodeVecF64x2Gt OpcodeVec = 0x4a + OpcodeVecF64x2Le OpcodeVec = 0x4b + OpcodeVecF64x2Ge OpcodeVec = 0x4c + + // v128 logical instructions. + + OpcodeVecV128Not OpcodeVec = 0x4d + OpcodeVecV128And OpcodeVec = 0x4e + OpcodeVecV128AndNot OpcodeVec = 0x4f + OpcodeVecV128Or OpcodeVec = 0x50 + OpcodeVecV128Xor OpcodeVec = 0x51 + OpcodeVecV128Bitselect OpcodeVec = 0x52 + OpcodeVecV128AnyTrue OpcodeVec = 0x53 + + // i8 misc. + + OpcodeVecI8x16Abs OpcodeVec = 0x60 + OpcodeVecI8x16Neg OpcodeVec = 0x61 + OpcodeVecI8x16Popcnt OpcodeVec = 0x62 + OpcodeVecI8x16AllTrue OpcodeVec = 0x63 + OpcodeVecI8x16BitMask OpcodeVec = 0x64 + OpcodeVecI8x16NarrowI16x8S OpcodeVec = 0x65 + OpcodeVecI8x16NarrowI16x8U OpcodeVec = 0x66 + + OpcodeVecI8x16Shl OpcodeVec = 0x6b + OpcodeVecI8x16ShrS OpcodeVec = 0x6c + OpcodeVecI8x16ShrU OpcodeVec = 0x6d + OpcodeVecI8x16Add OpcodeVec = 0x6e + OpcodeVecI8x16AddSatS OpcodeVec = 0x6f + + OpcodeVecI8x16AddSatU OpcodeVec = 0x70 + OpcodeVecI8x16Sub OpcodeVec = 0x71 + OpcodeVecI8x16SubSatS OpcodeVec = 0x72 + OpcodeVecI8x16SubSatU OpcodeVec = 0x73 + OpcodeVecI8x16MinS OpcodeVec = 0x76 + OpcodeVecI8x16MinU OpcodeVec = 0x77 + OpcodeVecI8x16MaxS OpcodeVec = 0x78 + OpcodeVecI8x16MaxU OpcodeVec = 0x79 + OpcodeVecI8x16AvgrU OpcodeVec = 0x7b + + // i16 misc. + + OpcodeVecI16x8ExtaddPairwiseI8x16S OpcodeVec = 0x7c + OpcodeVecI16x8ExtaddPairwiseI8x16U OpcodeVec = 0x7d + OpcodeVecI16x8Abs OpcodeVec = 0x80 + OpcodeVecI16x8Neg OpcodeVec = 0x81 + OpcodeVecI16x8Q15mulrSatS OpcodeVec = 0x82 + OpcodeVecI16x8AllTrue OpcodeVec = 0x83 + OpcodeVecI16x8BitMask OpcodeVec = 0x84 + OpcodeVecI16x8NarrowI32x4S OpcodeVec = 0x85 + OpcodeVecI16x8NarrowI32x4U OpcodeVec = 0x86 + OpcodeVecI16x8ExtendLowI8x16S OpcodeVec = 0x87 + OpcodeVecI16x8ExtendHighI8x16S OpcodeVec = 0x88 + OpcodeVecI16x8ExtendLowI8x16U OpcodeVec = 0x89 + OpcodeVecI16x8ExtendHighI8x16U OpcodeVec = 0x8a + OpcodeVecI16x8Shl OpcodeVec = 0x8b + OpcodeVecI16x8ShrS OpcodeVec = 0x8c + OpcodeVecI16x8ShrU OpcodeVec = 0x8d + OpcodeVecI16x8Add OpcodeVec = 0x8e + OpcodeVecI16x8AddSatS OpcodeVec = 0x8f + OpcodeVecI16x8AddSatU OpcodeVec = 0x90 + OpcodeVecI16x8Sub OpcodeVec = 0x91 + OpcodeVecI16x8SubSatS OpcodeVec = 0x92 + OpcodeVecI16x8SubSatU OpcodeVec = 0x93 + OpcodeVecI16x8Mul OpcodeVec = 0x95 + OpcodeVecI16x8MinS OpcodeVec = 0x96 + OpcodeVecI16x8MinU OpcodeVec = 0x97 + OpcodeVecI16x8MaxS OpcodeVec = 0x98 + OpcodeVecI16x8MaxU OpcodeVec = 0x99 + OpcodeVecI16x8AvgrU OpcodeVec = 0x9b + OpcodeVecI16x8ExtMulLowI8x16S OpcodeVec = 0x9c + OpcodeVecI16x8ExtMulHighI8x16S OpcodeVec = 0x9d + OpcodeVecI16x8ExtMulLowI8x16U OpcodeVec = 0x9e + OpcodeVecI16x8ExtMulHighI8x16U OpcodeVec = 0x9f + + // i32 misc. + + OpcodeVecI32x4ExtaddPairwiseI16x8S OpcodeVec = 0x7e + OpcodeVecI32x4ExtaddPairwiseI16x8U OpcodeVec = 0x7f + OpcodeVecI32x4Abs OpcodeVec = 0xa0 + OpcodeVecI32x4Neg OpcodeVec = 0xa1 + OpcodeVecI32x4AllTrue OpcodeVec = 0xa3 + OpcodeVecI32x4BitMask OpcodeVec = 0xa4 + OpcodeVecI32x4ExtendLowI16x8S OpcodeVec = 0xa7 + OpcodeVecI32x4ExtendHighI16x8S OpcodeVec = 0xa8 + OpcodeVecI32x4ExtendLowI16x8U OpcodeVec = 0xa9 + OpcodeVecI32x4ExtendHighI16x8U OpcodeVec = 0xaa + OpcodeVecI32x4Shl OpcodeVec = 0xab + OpcodeVecI32x4ShrS OpcodeVec = 0xac + OpcodeVecI32x4ShrU OpcodeVec = 0xad + OpcodeVecI32x4Add OpcodeVec = 0xae + OpcodeVecI32x4Sub OpcodeVec = 0xb1 + OpcodeVecI32x4Mul OpcodeVec = 0xb5 + OpcodeVecI32x4MinS OpcodeVec = 0xb6 + OpcodeVecI32x4MinU OpcodeVec = 0xb7 + OpcodeVecI32x4MaxS OpcodeVec = 0xb8 + OpcodeVecI32x4MaxU OpcodeVec = 0xb9 + OpcodeVecI32x4DotI16x8S OpcodeVec = 0xba + OpcodeVecI32x4ExtMulLowI16x8S OpcodeVec = 0xbc + OpcodeVecI32x4ExtMulHighI16x8S OpcodeVec = 0xbd + OpcodeVecI32x4ExtMulLowI16x8U OpcodeVec = 0xbe + OpcodeVecI32x4ExtMulHighI16x8U OpcodeVec = 0xbf + + // i64 misc. + + OpcodeVecI64x2Abs OpcodeVec = 0xc0 + OpcodeVecI64x2Neg OpcodeVec = 0xc1 + OpcodeVecI64x2AllTrue OpcodeVec = 0xc3 + OpcodeVecI64x2BitMask OpcodeVec = 0xc4 + OpcodeVecI64x2ExtendLowI32x4S OpcodeVec = 0xc7 + OpcodeVecI64x2ExtendHighI32x4S OpcodeVec = 0xc8 + OpcodeVecI64x2ExtendLowI32x4U OpcodeVec = 0xc9 + OpcodeVecI64x2ExtendHighI32x4U OpcodeVec = 0xca + OpcodeVecI64x2Shl OpcodeVec = 0xcb + OpcodeVecI64x2ShrS OpcodeVec = 0xcc + OpcodeVecI64x2ShrU OpcodeVec = 0xcd + OpcodeVecI64x2Add OpcodeVec = 0xce + OpcodeVecI64x2Sub OpcodeVec = 0xd1 + OpcodeVecI64x2Mul OpcodeVec = 0xd5 + OpcodeVecI64x2ExtMulLowI32x4S OpcodeVec = 0xdc + OpcodeVecI64x2ExtMulHighI32x4S OpcodeVec = 0xdd + OpcodeVecI64x2ExtMulLowI32x4U OpcodeVec = 0xde + OpcodeVecI64x2ExtMulHighI32x4U OpcodeVec = 0xdf + + // f32 misc. + + OpcodeVecF32x4Ceil OpcodeVec = 0x67 + OpcodeVecF32x4Floor OpcodeVec = 0x68 + OpcodeVecF32x4Trunc OpcodeVec = 0x69 + OpcodeVecF32x4Nearest OpcodeVec = 0x6a + OpcodeVecF32x4Abs OpcodeVec = 0xe0 + OpcodeVecF32x4Neg OpcodeVec = 0xe1 + OpcodeVecF32x4Sqrt OpcodeVec = 0xe3 + OpcodeVecF32x4Add OpcodeVec = 0xe4 + OpcodeVecF32x4Sub OpcodeVec = 0xe5 + OpcodeVecF32x4Mul OpcodeVec = 0xe6 + OpcodeVecF32x4Div OpcodeVec = 0xe7 + OpcodeVecF32x4Min OpcodeVec = 0xe8 + OpcodeVecF32x4Max OpcodeVec = 0xe9 + OpcodeVecF32x4Pmin OpcodeVec = 0xea + OpcodeVecF32x4Pmax OpcodeVec = 0xeb + + // f64 misc. + + OpcodeVecF64x2Ceil OpcodeVec = 0x74 + OpcodeVecF64x2Floor OpcodeVec = 0x75 + OpcodeVecF64x2Trunc OpcodeVec = 0x7a + OpcodeVecF64x2Nearest OpcodeVec = 0x94 + OpcodeVecF64x2Abs OpcodeVec = 0xec + OpcodeVecF64x2Neg OpcodeVec = 0xed + OpcodeVecF64x2Sqrt OpcodeVec = 0xef + OpcodeVecF64x2Add OpcodeVec = 0xf0 + OpcodeVecF64x2Sub OpcodeVec = 0xf1 + OpcodeVecF64x2Mul OpcodeVec = 0xf2 + OpcodeVecF64x2Div OpcodeVec = 0xf3 + OpcodeVecF64x2Min OpcodeVec = 0xf4 + OpcodeVecF64x2Max OpcodeVec = 0xf5 + OpcodeVecF64x2Pmin OpcodeVec = 0xf6 + OpcodeVecF64x2Pmax OpcodeVec = 0xf7 + + // conversions. + + OpcodeVecI32x4TruncSatF32x4S OpcodeVec = 0xf8 + OpcodeVecI32x4TruncSatF32x4U OpcodeVec = 0xf9 + OpcodeVecF32x4ConvertI32x4S OpcodeVec = 0xfa + OpcodeVecF32x4ConvertI32x4U OpcodeVec = 0xfb + OpcodeVecI32x4TruncSatF64x2SZero OpcodeVec = 0xfc + OpcodeVecI32x4TruncSatF64x2UZero OpcodeVec = 0xfd + OpcodeVecF64x2ConvertLowI32x4S OpcodeVec = 0xfe + OpcodeVecF64x2ConvertLowI32x4U OpcodeVec = 0xff + OpcodeVecF32x4DemoteF64x2Zero OpcodeVec = 0x5e + OpcodeVecF64x2PromoteLowF32x4Zero OpcodeVec = 0x5f +) + +// OpcodeAtomic represents an opcode of atomic instructions which has +// multi-byte encoding and is prefixed by OpcodeAtomicPrefix. +// +// These opcodes are toggled with CoreFeaturesThreads. +type OpcodeAtomic = byte + +const ( + // OpcodeAtomicMemoryNotify represents the instruction memory.atomic.notify. + OpcodeAtomicMemoryNotify OpcodeAtomic = 0x00 + // OpcodeAtomicMemoryWait32 represents the instruction memory.atomic.wait32. + OpcodeAtomicMemoryWait32 OpcodeAtomic = 0x01 + // OpcodeAtomicMemoryWait64 represents the instruction memory.atomic.wait64. + OpcodeAtomicMemoryWait64 OpcodeAtomic = 0x02 + // OpcodeAtomicFence represents the instruction atomic.fence. + OpcodeAtomicFence OpcodeAtomic = 0x03 + + // OpcodeAtomicI32Load represents the instruction i32.atomic.load. + OpcodeAtomicI32Load OpcodeAtomic = 0x10 + // OpcodeAtomicI64Load represents the instruction i64.atomic.load. + OpcodeAtomicI64Load OpcodeAtomic = 0x11 + // OpcodeAtomicI32Load8U represents the instruction i32.atomic.load8_u. + OpcodeAtomicI32Load8U OpcodeAtomic = 0x12 + // OpcodeAtomicI32Load16U represents the instruction i32.atomic.load16_u. + OpcodeAtomicI32Load16U OpcodeAtomic = 0x13 + // OpcodeAtomicI64Load8U represents the instruction i64.atomic.load8_u. + OpcodeAtomicI64Load8U OpcodeAtomic = 0x14 + // OpcodeAtomicI64Load16U represents the instruction i64.atomic.load16_u. + OpcodeAtomicI64Load16U OpcodeAtomic = 0x15 + // OpcodeAtomicI64Load32U represents the instruction i64.atomic.load32_u. + OpcodeAtomicI64Load32U OpcodeAtomic = 0x16 + // OpcodeAtomicI32Store represents the instruction i32.atomic.store. + OpcodeAtomicI32Store OpcodeAtomic = 0x17 + // OpcodeAtomicI64Store represents the instruction i64.atomic.store. + OpcodeAtomicI64Store OpcodeAtomic = 0x18 + // OpcodeAtomicI32Store8 represents the instruction i32.atomic.store8. + OpcodeAtomicI32Store8 OpcodeAtomic = 0x19 + // OpcodeAtomicI32Store16 represents the instruction i32.atomic.store16. + OpcodeAtomicI32Store16 OpcodeAtomic = 0x1a + // OpcodeAtomicI64Store8 represents the instruction i64.atomic.store8. + OpcodeAtomicI64Store8 OpcodeAtomic = 0x1b + // OpcodeAtomicI64Store16 represents the instruction i64.atomic.store16. + OpcodeAtomicI64Store16 OpcodeAtomic = 0x1c + // OpcodeAtomicI64Store32 represents the instruction i64.atomic.store32. + OpcodeAtomicI64Store32 OpcodeAtomic = 0x1d + + // OpcodeAtomicI32RmwAdd represents the instruction i32.atomic.rmw.add. + OpcodeAtomicI32RmwAdd OpcodeAtomic = 0x1e + // OpcodeAtomicI64RmwAdd represents the instruction i64.atomic.rmw.add. + OpcodeAtomicI64RmwAdd OpcodeAtomic = 0x1f + // OpcodeAtomicI32Rmw8AddU represents the instruction i32.atomic.rmw8.add_u. + OpcodeAtomicI32Rmw8AddU OpcodeAtomic = 0x20 + // OpcodeAtomicI32Rmw16AddU represents the instruction i32.atomic.rmw16.add_u. + OpcodeAtomicI32Rmw16AddU OpcodeAtomic = 0x21 + // OpcodeAtomicI64Rmw8AddU represents the instruction i64.atomic.rmw8.add_u. + OpcodeAtomicI64Rmw8AddU OpcodeAtomic = 0x22 + // OpcodeAtomicI64Rmw16AddU represents the instruction i64.atomic.rmw16.add_u. + OpcodeAtomicI64Rmw16AddU OpcodeAtomic = 0x23 + // OpcodeAtomicI64Rmw32AddU represents the instruction i64.atomic.rmw32.add_u. + OpcodeAtomicI64Rmw32AddU OpcodeAtomic = 0x24 + + // OpcodeAtomicI32RmwSub represents the instruction i32.atomic.rmw.sub. + OpcodeAtomicI32RmwSub OpcodeAtomic = 0x25 + // OpcodeAtomicI64RmwSub represents the instruction i64.atomic.rmw.sub. + OpcodeAtomicI64RmwSub OpcodeAtomic = 0x26 + // OpcodeAtomicI32Rmw8SubU represents the instruction i32.atomic.rmw8.sub_u. + OpcodeAtomicI32Rmw8SubU OpcodeAtomic = 0x27 + // OpcodeAtomicI32Rmw16SubU represents the instruction i32.atomic.rmw16.sub_u. + OpcodeAtomicI32Rmw16SubU OpcodeAtomic = 0x28 + // OpcodeAtomicI64Rmw8SubU represents the instruction i64.atomic.rmw8.sub_u. + OpcodeAtomicI64Rmw8SubU OpcodeAtomic = 0x29 + // OpcodeAtomicI64Rmw16SubU represents the instruction i64.atomic.rmw16.sub_u. + OpcodeAtomicI64Rmw16SubU OpcodeAtomic = 0x2a + // OpcodeAtomicI64Rmw32SubU represents the instruction i64.atomic.rmw32.sub_u. + OpcodeAtomicI64Rmw32SubU OpcodeAtomic = 0x2b + + // OpcodeAtomicI32RmwAnd represents the instruction i32.atomic.rmw.and. + OpcodeAtomicI32RmwAnd OpcodeAtomic = 0x2c + // OpcodeAtomicI64RmwAnd represents the instruction i64.atomic.rmw.and. + OpcodeAtomicI64RmwAnd OpcodeAtomic = 0x2d + // OpcodeAtomicI32Rmw8AndU represents the instruction i32.atomic.rmw8.and_u. + OpcodeAtomicI32Rmw8AndU OpcodeAtomic = 0x2e + // OpcodeAtomicI32Rmw16AndU represents the instruction i32.atomic.rmw16.and_u. + OpcodeAtomicI32Rmw16AndU OpcodeAtomic = 0x2f + // OpcodeAtomicI64Rmw8AndU represents the instruction i64.atomic.rmw8.and_u. + OpcodeAtomicI64Rmw8AndU OpcodeAtomic = 0x30 + // OpcodeAtomicI64Rmw16AndU represents the instruction i64.atomic.rmw16.and_u. + OpcodeAtomicI64Rmw16AndU OpcodeAtomic = 0x31 + // OpcodeAtomicI64Rmw32AndU represents the instruction i64.atomic.rmw32.and_u. + OpcodeAtomicI64Rmw32AndU OpcodeAtomic = 0x32 + + // OpcodeAtomicI32RmwOr represents the instruction i32.atomic.rmw.or. + OpcodeAtomicI32RmwOr OpcodeAtomic = 0x33 + // OpcodeAtomicI64RmwOr represents the instruction i64.atomic.rmw.or. + OpcodeAtomicI64RmwOr OpcodeAtomic = 0x34 + // OpcodeAtomicI32Rmw8OrU represents the instruction i32.atomic.rmw8.or_u. + OpcodeAtomicI32Rmw8OrU OpcodeAtomic = 0x35 + // OpcodeAtomicI32Rmw16OrU represents the instruction i32.atomic.rmw16.or_u. + OpcodeAtomicI32Rmw16OrU OpcodeAtomic = 0x36 + // OpcodeAtomicI64Rmw8OrU represents the instruction i64.atomic.rmw8.or_u. + OpcodeAtomicI64Rmw8OrU OpcodeAtomic = 0x37 + // OpcodeAtomicI64Rmw16OrU represents the instruction i64.atomic.rmw16.or_u. + OpcodeAtomicI64Rmw16OrU OpcodeAtomic = 0x38 + // OpcodeAtomicI64Rmw32OrU represents the instruction i64.atomic.rmw32.or_u. + OpcodeAtomicI64Rmw32OrU OpcodeAtomic = 0x39 + + // OpcodeAtomicI32RmwXor represents the instruction i32.atomic.rmw.xor. + OpcodeAtomicI32RmwXor OpcodeAtomic = 0x3a + // OpcodeAtomicI64RmwXor represents the instruction i64.atomic.rmw.xor. + OpcodeAtomicI64RmwXor OpcodeAtomic = 0x3b + // OpcodeAtomicI32Rmw8XorU represents the instruction i32.atomic.rmw8.xor_u. + OpcodeAtomicI32Rmw8XorU OpcodeAtomic = 0x3c + // OpcodeAtomicI32Rmw16XorU represents the instruction i32.atomic.rmw16.xor_u. + OpcodeAtomicI32Rmw16XorU OpcodeAtomic = 0x3d + // OpcodeAtomicI64Rmw8XorU represents the instruction i64.atomic.rmw8.xor_u. + OpcodeAtomicI64Rmw8XorU OpcodeAtomic = 0x3e + // OpcodeAtomicI64Rmw16XorU represents the instruction i64.atomic.rmw16.xor_u. + OpcodeAtomicI64Rmw16XorU OpcodeAtomic = 0x3f + // OpcodeAtomicI64Rmw32XorU represents the instruction i64.atomic.rmw32.xor_u. + OpcodeAtomicI64Rmw32XorU OpcodeAtomic = 0x40 + + // OpcodeAtomicI32RmwXchg represents the instruction i32.atomic.rmw.xchg. + OpcodeAtomicI32RmwXchg OpcodeAtomic = 0x41 + // OpcodeAtomicI64RmwXchg represents the instruction i64.atomic.rmw.xchg. + OpcodeAtomicI64RmwXchg OpcodeAtomic = 0x42 + // OpcodeAtomicI32Rmw8XchgU represents the instruction i32.atomic.rmw8.xchg_u. + OpcodeAtomicI32Rmw8XchgU OpcodeAtomic = 0x43 + // OpcodeAtomicI32Rmw16XchgU represents the instruction i32.atomic.rmw16.xchg_u. + OpcodeAtomicI32Rmw16XchgU OpcodeAtomic = 0x44 + // OpcodeAtomicI64Rmw8XchgU represents the instruction i64.atomic.rmw8.xchg_u. + OpcodeAtomicI64Rmw8XchgU OpcodeAtomic = 0x45 + // OpcodeAtomicI64Rmw16XchgU represents the instruction i64.atomic.rmw16.xchg_u. + OpcodeAtomicI64Rmw16XchgU OpcodeAtomic = 0x46 + // OpcodeAtomicI64Rmw32XchgU represents the instruction i64.atomic.rmw32.xchg_u. + OpcodeAtomicI64Rmw32XchgU OpcodeAtomic = 0x47 + + // OpcodeAtomicI32RmwCmpxchg represents the instruction i32.atomic.rmw.cmpxchg. + OpcodeAtomicI32RmwCmpxchg OpcodeAtomic = 0x48 + // OpcodeAtomicI64RmwCmpxchg represents the instruction i64.atomic.rmw.cmpxchg. + OpcodeAtomicI64RmwCmpxchg OpcodeAtomic = 0x49 + // OpcodeAtomicI32Rmw8CmpxchgU represents the instruction i32.atomic.rmw8.cmpxchg_u. + OpcodeAtomicI32Rmw8CmpxchgU OpcodeAtomic = 0x4a + // OpcodeAtomicI32Rmw16CmpxchgU represents the instruction i32.atomic.rmw16.cmpxchg_u. + OpcodeAtomicI32Rmw16CmpxchgU OpcodeAtomic = 0x4b + // OpcodeAtomicI64Rmw8CmpxchgU represents the instruction i64.atomic.rmw8.cmpxchg_u. + OpcodeAtomicI64Rmw8CmpxchgU OpcodeAtomic = 0x4c + // OpcodeAtomicI64Rmw16CmpxchgU represents the instruction i64.atomic.rmw16.cmpxchg_u. + OpcodeAtomicI64Rmw16CmpxchgU OpcodeAtomic = 0x4d + // OpcodeAtomicI64Rmw32CmpxchgU represents the instruction i64.atomic.rmw32.cmpxchg_u. + OpcodeAtomicI64Rmw32CmpxchgU OpcodeAtomic = 0x4e +) + +// OpcodeTailCall represents an opcode of a tail call instructions. +// +// These opcodes are toggled with CoreFeaturesTailCall. +type OpcodeTailCall = byte + +const ( + OpcodeTailCallReturnCall OpcodeTailCall = 0x12 + OpcodeTailCallReturnCallIndirect OpcodeTailCall = 0x13 +) + +const ( + OpcodeUnreachableName = "unreachable" + OpcodeNopName = "nop" + OpcodeBlockName = "block" + OpcodeLoopName = "loop" + OpcodeIfName = "if" + OpcodeElseName = "else" + OpcodeEndName = "end" + OpcodeBrName = "br" + OpcodeBrIfName = "br_if" + OpcodeBrTableName = "br_table" + OpcodeReturnName = "return" + OpcodeCallName = "call" + OpcodeCallIndirectName = "call_indirect" + OpcodeDropName = "drop" + OpcodeSelectName = "select" + OpcodeTypedSelectName = "typed_select" + OpcodeLocalGetName = "local.get" + OpcodeLocalSetName = "local.set" + OpcodeLocalTeeName = "local.tee" + OpcodeGlobalGetName = "global.get" + OpcodeGlobalSetName = "global.set" + OpcodeI32LoadName = "i32.load" + OpcodeI64LoadName = "i64.load" + OpcodeF32LoadName = "f32.load" + OpcodeF64LoadName = "f64.load" + OpcodeI32Load8SName = "i32.load8_s" + OpcodeI32Load8UName = "i32.load8_u" + OpcodeI32Load16SName = "i32.load16_s" + OpcodeI32Load16UName = "i32.load16_u" + OpcodeI64Load8SName = "i64.load8_s" + OpcodeI64Load8UName = "i64.load8_u" + OpcodeI64Load16SName = "i64.load16_s" + OpcodeI64Load16UName = "i64.load16_u" + OpcodeI64Load32SName = "i64.load32_s" + OpcodeI64Load32UName = "i64.load32_u" + OpcodeI32StoreName = "i32.store" + OpcodeI64StoreName = "i64.store" + OpcodeF32StoreName = "f32.store" + OpcodeF64StoreName = "f64.store" + OpcodeI32Store8Name = "i32.store8" + OpcodeI32Store16Name = "i32.store16" + OpcodeI64Store8Name = "i64.store8" + OpcodeI64Store16Name = "i64.store16" + OpcodeI64Store32Name = "i64.store32" + OpcodeMemorySizeName = "memory.size" + OpcodeMemoryGrowName = "memory.grow" + OpcodeI32ConstName = "i32.const" + OpcodeI64ConstName = "i64.const" + OpcodeF32ConstName = "f32.const" + OpcodeF64ConstName = "f64.const" + OpcodeI32EqzName = "i32.eqz" + OpcodeI32EqName = "i32.eq" + OpcodeI32NeName = "i32.ne" + OpcodeI32LtSName = "i32.lt_s" + OpcodeI32LtUName = "i32.lt_u" + OpcodeI32GtSName = "i32.gt_s" + OpcodeI32GtUName = "i32.gt_u" + OpcodeI32LeSName = "i32.le_s" + OpcodeI32LeUName = "i32.le_u" + OpcodeI32GeSName = "i32.ge_s" + OpcodeI32GeUName = "i32.ge_u" + OpcodeI64EqzName = "i64.eqz" + OpcodeI64EqName = "i64.eq" + OpcodeI64NeName = "i64.ne" + OpcodeI64LtSName = "i64.lt_s" + OpcodeI64LtUName = "i64.lt_u" + OpcodeI64GtSName = "i64.gt_s" + OpcodeI64GtUName = "i64.gt_u" + OpcodeI64LeSName = "i64.le_s" + OpcodeI64LeUName = "i64.le_u" + OpcodeI64GeSName = "i64.ge_s" + OpcodeI64GeUName = "i64.ge_u" + OpcodeF32EqName = "f32.eq" + OpcodeF32NeName = "f32.ne" + OpcodeF32LtName = "f32.lt" + OpcodeF32GtName = "f32.gt" + OpcodeF32LeName = "f32.le" + OpcodeF32GeName = "f32.ge" + OpcodeF64EqName = "f64.eq" + OpcodeF64NeName = "f64.ne" + OpcodeF64LtName = "f64.lt" + OpcodeF64GtName = "f64.gt" + OpcodeF64LeName = "f64.le" + OpcodeF64GeName = "f64.ge" + OpcodeI32ClzName = "i32.clz" + OpcodeI32CtzName = "i32.ctz" + OpcodeI32PopcntName = "i32.popcnt" + OpcodeI32AddName = "i32.add" + OpcodeI32SubName = "i32.sub" + OpcodeI32MulName = "i32.mul" + OpcodeI32DivSName = "i32.div_s" + OpcodeI32DivUName = "i32.div_u" + OpcodeI32RemSName = "i32.rem_s" + OpcodeI32RemUName = "i32.rem_u" + OpcodeI32AndName = "i32.and" + OpcodeI32OrName = "i32.or" + OpcodeI32XorName = "i32.xor" + OpcodeI32ShlName = "i32.shl" + OpcodeI32ShrSName = "i32.shr_s" + OpcodeI32ShrUName = "i32.shr_u" + OpcodeI32RotlName = "i32.rotl" + OpcodeI32RotrName = "i32.rotr" + OpcodeI64ClzName = "i64.clz" + OpcodeI64CtzName = "i64.ctz" + OpcodeI64PopcntName = "i64.popcnt" + OpcodeI64AddName = "i64.add" + OpcodeI64SubName = "i64.sub" + OpcodeI64MulName = "i64.mul" + OpcodeI64DivSName = "i64.div_s" + OpcodeI64DivUName = "i64.div_u" + OpcodeI64RemSName = "i64.rem_s" + OpcodeI64RemUName = "i64.rem_u" + OpcodeI64AndName = "i64.and" + OpcodeI64OrName = "i64.or" + OpcodeI64XorName = "i64.xor" + OpcodeI64ShlName = "i64.shl" + OpcodeI64ShrSName = "i64.shr_s" + OpcodeI64ShrUName = "i64.shr_u" + OpcodeI64RotlName = "i64.rotl" + OpcodeI64RotrName = "i64.rotr" + OpcodeF32AbsName = "f32.abs" + OpcodeF32NegName = "f32.neg" + OpcodeF32CeilName = "f32.ceil" + OpcodeF32FloorName = "f32.floor" + OpcodeF32TruncName = "f32.trunc" + OpcodeF32NearestName = "f32.nearest" + OpcodeF32SqrtName = "f32.sqrt" + OpcodeF32AddName = "f32.add" + OpcodeF32SubName = "f32.sub" + OpcodeF32MulName = "f32.mul" + OpcodeF32DivName = "f32.div" + OpcodeF32MinName = "f32.min" + OpcodeF32MaxName = "f32.max" + OpcodeF32CopysignName = "f32.copysign" + OpcodeF64AbsName = "f64.abs" + OpcodeF64NegName = "f64.neg" + OpcodeF64CeilName = "f64.ceil" + OpcodeF64FloorName = "f64.floor" + OpcodeF64TruncName = "f64.trunc" + OpcodeF64NearestName = "f64.nearest" + OpcodeF64SqrtName = "f64.sqrt" + OpcodeF64AddName = "f64.add" + OpcodeF64SubName = "f64.sub" + OpcodeF64MulName = "f64.mul" + OpcodeF64DivName = "f64.div" + OpcodeF64MinName = "f64.min" + OpcodeF64MaxName = "f64.max" + OpcodeF64CopysignName = "f64.copysign" + OpcodeI32WrapI64Name = "i32.wrap_i64" + OpcodeI32TruncF32SName = "i32.trunc_f32_s" + OpcodeI32TruncF32UName = "i32.trunc_f32_u" + OpcodeI32TruncF64SName = "i32.trunc_f64_s" + OpcodeI32TruncF64UName = "i32.trunc_f64_u" + OpcodeI64ExtendI32SName = "i64.extend_i32_s" + OpcodeI64ExtendI32UName = "i64.extend_i32_u" + OpcodeI64TruncF32SName = "i64.trunc_f32_s" + OpcodeI64TruncF32UName = "i64.trunc_f32_u" + OpcodeI64TruncF64SName = "i64.trunc_f64_s" + OpcodeI64TruncF64UName = "i64.trunc_f64_u" + OpcodeF32ConvertI32SName = "f32.convert_i32_s" + OpcodeF32ConvertI32UName = "f32.convert_i32_u" + OpcodeF32ConvertI64SName = "f32.convert_i64_s" + OpcodeF32ConvertI64UName = "f32.convert_i64u" + OpcodeF32DemoteF64Name = "f32.demote_f64" + OpcodeF64ConvertI32SName = "f64.convert_i32_s" + OpcodeF64ConvertI32UName = "f64.convert_i32_u" + OpcodeF64ConvertI64SName = "f64.convert_i64_s" + OpcodeF64ConvertI64UName = "f64.convert_i64_u" + OpcodeF64PromoteF32Name = "f64.promote_f32" + OpcodeI32ReinterpretF32Name = "i32.reinterpret_f32" + OpcodeI64ReinterpretF64Name = "i64.reinterpret_f64" + OpcodeF32ReinterpretI32Name = "f32.reinterpret_i32" + OpcodeF64ReinterpretI64Name = "f64.reinterpret_i64" + + OpcodeRefNullName = "ref.null" + OpcodeRefIsNullName = "ref.is_null" + OpcodeRefFuncName = "ref.func" + + OpcodeTableGetName = "table.get" + OpcodeTableSetName = "table.set" + + // Below are toggled with CoreFeatureSignExtensionOps + + OpcodeI32Extend8SName = "i32.extend8_s" + OpcodeI32Extend16SName = "i32.extend16_s" + OpcodeI64Extend8SName = "i64.extend8_s" + OpcodeI64Extend16SName = "i64.extend16_s" + OpcodeI64Extend32SName = "i64.extend32_s" + + OpcodeMiscPrefixName = "misc_prefix" + OpcodeVecPrefixName = "vector_prefix" + OpcodeAtomicPrefixName = "atomic_prefix" +) + +var instructionNames = [256]string{ + OpcodeUnreachable: OpcodeUnreachableName, + OpcodeNop: OpcodeNopName, + OpcodeBlock: OpcodeBlockName, + OpcodeLoop: OpcodeLoopName, + OpcodeIf: OpcodeIfName, + OpcodeElse: OpcodeElseName, + OpcodeEnd: OpcodeEndName, + OpcodeBr: OpcodeBrName, + OpcodeBrIf: OpcodeBrIfName, + OpcodeBrTable: OpcodeBrTableName, + OpcodeReturn: OpcodeReturnName, + OpcodeCall: OpcodeCallName, + OpcodeCallIndirect: OpcodeCallIndirectName, + OpcodeDrop: OpcodeDropName, + OpcodeSelect: OpcodeSelectName, + OpcodeTypedSelect: OpcodeTypedSelectName, + OpcodeLocalGet: OpcodeLocalGetName, + OpcodeLocalSet: OpcodeLocalSetName, + OpcodeLocalTee: OpcodeLocalTeeName, + OpcodeGlobalGet: OpcodeGlobalGetName, + OpcodeGlobalSet: OpcodeGlobalSetName, + OpcodeI32Load: OpcodeI32LoadName, + OpcodeI64Load: OpcodeI64LoadName, + OpcodeF32Load: OpcodeF32LoadName, + OpcodeF64Load: OpcodeF64LoadName, + OpcodeI32Load8S: OpcodeI32Load8SName, + OpcodeI32Load8U: OpcodeI32Load8UName, + OpcodeI32Load16S: OpcodeI32Load16SName, + OpcodeI32Load16U: OpcodeI32Load16UName, + OpcodeI64Load8S: OpcodeI64Load8SName, + OpcodeI64Load8U: OpcodeI64Load8UName, + OpcodeI64Load16S: OpcodeI64Load16SName, + OpcodeI64Load16U: OpcodeI64Load16UName, + OpcodeI64Load32S: OpcodeI64Load32SName, + OpcodeI64Load32U: OpcodeI64Load32UName, + OpcodeI32Store: OpcodeI32StoreName, + OpcodeI64Store: OpcodeI64StoreName, + OpcodeF32Store: OpcodeF32StoreName, + OpcodeF64Store: OpcodeF64StoreName, + OpcodeI32Store8: OpcodeI32Store8Name, + OpcodeI32Store16: OpcodeI32Store16Name, + OpcodeI64Store8: OpcodeI64Store8Name, + OpcodeI64Store16: OpcodeI64Store16Name, + OpcodeI64Store32: OpcodeI64Store32Name, + OpcodeMemorySize: OpcodeMemorySizeName, + OpcodeMemoryGrow: OpcodeMemoryGrowName, + OpcodeI32Const: OpcodeI32ConstName, + OpcodeI64Const: OpcodeI64ConstName, + OpcodeF32Const: OpcodeF32ConstName, + OpcodeF64Const: OpcodeF64ConstName, + OpcodeI32Eqz: OpcodeI32EqzName, + OpcodeI32Eq: OpcodeI32EqName, + OpcodeI32Ne: OpcodeI32NeName, + OpcodeI32LtS: OpcodeI32LtSName, + OpcodeI32LtU: OpcodeI32LtUName, + OpcodeI32GtS: OpcodeI32GtSName, + OpcodeI32GtU: OpcodeI32GtUName, + OpcodeI32LeS: OpcodeI32LeSName, + OpcodeI32LeU: OpcodeI32LeUName, + OpcodeI32GeS: OpcodeI32GeSName, + OpcodeI32GeU: OpcodeI32GeUName, + OpcodeI64Eqz: OpcodeI64EqzName, + OpcodeI64Eq: OpcodeI64EqName, + OpcodeI64Ne: OpcodeI64NeName, + OpcodeI64LtS: OpcodeI64LtSName, + OpcodeI64LtU: OpcodeI64LtUName, + OpcodeI64GtS: OpcodeI64GtSName, + OpcodeI64GtU: OpcodeI64GtUName, + OpcodeI64LeS: OpcodeI64LeSName, + OpcodeI64LeU: OpcodeI64LeUName, + OpcodeI64GeS: OpcodeI64GeSName, + OpcodeI64GeU: OpcodeI64GeUName, + OpcodeF32Eq: OpcodeF32EqName, + OpcodeF32Ne: OpcodeF32NeName, + OpcodeF32Lt: OpcodeF32LtName, + OpcodeF32Gt: OpcodeF32GtName, + OpcodeF32Le: OpcodeF32LeName, + OpcodeF32Ge: OpcodeF32GeName, + OpcodeF64Eq: OpcodeF64EqName, + OpcodeF64Ne: OpcodeF64NeName, + OpcodeF64Lt: OpcodeF64LtName, + OpcodeF64Gt: OpcodeF64GtName, + OpcodeF64Le: OpcodeF64LeName, + OpcodeF64Ge: OpcodeF64GeName, + OpcodeI32Clz: OpcodeI32ClzName, + OpcodeI32Ctz: OpcodeI32CtzName, + OpcodeI32Popcnt: OpcodeI32PopcntName, + OpcodeI32Add: OpcodeI32AddName, + OpcodeI32Sub: OpcodeI32SubName, + OpcodeI32Mul: OpcodeI32MulName, + OpcodeI32DivS: OpcodeI32DivSName, + OpcodeI32DivU: OpcodeI32DivUName, + OpcodeI32RemS: OpcodeI32RemSName, + OpcodeI32RemU: OpcodeI32RemUName, + OpcodeI32And: OpcodeI32AndName, + OpcodeI32Or: OpcodeI32OrName, + OpcodeI32Xor: OpcodeI32XorName, + OpcodeI32Shl: OpcodeI32ShlName, + OpcodeI32ShrS: OpcodeI32ShrSName, + OpcodeI32ShrU: OpcodeI32ShrUName, + OpcodeI32Rotl: OpcodeI32RotlName, + OpcodeI32Rotr: OpcodeI32RotrName, + OpcodeI64Clz: OpcodeI64ClzName, + OpcodeI64Ctz: OpcodeI64CtzName, + OpcodeI64Popcnt: OpcodeI64PopcntName, + OpcodeI64Add: OpcodeI64AddName, + OpcodeI64Sub: OpcodeI64SubName, + OpcodeI64Mul: OpcodeI64MulName, + OpcodeI64DivS: OpcodeI64DivSName, + OpcodeI64DivU: OpcodeI64DivUName, + OpcodeI64RemS: OpcodeI64RemSName, + OpcodeI64RemU: OpcodeI64RemUName, + OpcodeI64And: OpcodeI64AndName, + OpcodeI64Or: OpcodeI64OrName, + OpcodeI64Xor: OpcodeI64XorName, + OpcodeI64Shl: OpcodeI64ShlName, + OpcodeI64ShrS: OpcodeI64ShrSName, + OpcodeI64ShrU: OpcodeI64ShrUName, + OpcodeI64Rotl: OpcodeI64RotlName, + OpcodeI64Rotr: OpcodeI64RotrName, + OpcodeF32Abs: OpcodeF32AbsName, + OpcodeF32Neg: OpcodeF32NegName, + OpcodeF32Ceil: OpcodeF32CeilName, + OpcodeF32Floor: OpcodeF32FloorName, + OpcodeF32Trunc: OpcodeF32TruncName, + OpcodeF32Nearest: OpcodeF32NearestName, + OpcodeF32Sqrt: OpcodeF32SqrtName, + OpcodeF32Add: OpcodeF32AddName, + OpcodeF32Sub: OpcodeF32SubName, + OpcodeF32Mul: OpcodeF32MulName, + OpcodeF32Div: OpcodeF32DivName, + OpcodeF32Min: OpcodeF32MinName, + OpcodeF32Max: OpcodeF32MaxName, + OpcodeF32Copysign: OpcodeF32CopysignName, + OpcodeF64Abs: OpcodeF64AbsName, + OpcodeF64Neg: OpcodeF64NegName, + OpcodeF64Ceil: OpcodeF64CeilName, + OpcodeF64Floor: OpcodeF64FloorName, + OpcodeF64Trunc: OpcodeF64TruncName, + OpcodeF64Nearest: OpcodeF64NearestName, + OpcodeF64Sqrt: OpcodeF64SqrtName, + OpcodeF64Add: OpcodeF64AddName, + OpcodeF64Sub: OpcodeF64SubName, + OpcodeF64Mul: OpcodeF64MulName, + OpcodeF64Div: OpcodeF64DivName, + OpcodeF64Min: OpcodeF64MinName, + OpcodeF64Max: OpcodeF64MaxName, + OpcodeF64Copysign: OpcodeF64CopysignName, + OpcodeI32WrapI64: OpcodeI32WrapI64Name, + OpcodeI32TruncF32S: OpcodeI32TruncF32SName, + OpcodeI32TruncF32U: OpcodeI32TruncF32UName, + OpcodeI32TruncF64S: OpcodeI32TruncF64SName, + OpcodeI32TruncF64U: OpcodeI32TruncF64UName, + OpcodeI64ExtendI32S: OpcodeI64ExtendI32SName, + OpcodeI64ExtendI32U: OpcodeI64ExtendI32UName, + OpcodeI64TruncF32S: OpcodeI64TruncF32SName, + OpcodeI64TruncF32U: OpcodeI64TruncF32UName, + OpcodeI64TruncF64S: OpcodeI64TruncF64SName, + OpcodeI64TruncF64U: OpcodeI64TruncF64UName, + OpcodeF32ConvertI32S: OpcodeF32ConvertI32SName, + OpcodeF32ConvertI32U: OpcodeF32ConvertI32UName, + OpcodeF32ConvertI64S: OpcodeF32ConvertI64SName, + OpcodeF32ConvertI64U: OpcodeF32ConvertI64UName, + OpcodeF32DemoteF64: OpcodeF32DemoteF64Name, + OpcodeF64ConvertI32S: OpcodeF64ConvertI32SName, + OpcodeF64ConvertI32U: OpcodeF64ConvertI32UName, + OpcodeF64ConvertI64S: OpcodeF64ConvertI64SName, + OpcodeF64ConvertI64U: OpcodeF64ConvertI64UName, + OpcodeF64PromoteF32: OpcodeF64PromoteF32Name, + OpcodeI32ReinterpretF32: OpcodeI32ReinterpretF32Name, + OpcodeI64ReinterpretF64: OpcodeI64ReinterpretF64Name, + OpcodeF32ReinterpretI32: OpcodeF32ReinterpretI32Name, + OpcodeF64ReinterpretI64: OpcodeF64ReinterpretI64Name, + + OpcodeRefNull: OpcodeRefNullName, + OpcodeRefIsNull: OpcodeRefIsNullName, + OpcodeRefFunc: OpcodeRefFuncName, + + OpcodeTableGet: OpcodeTableGetName, + OpcodeTableSet: OpcodeTableSetName, + + // Below are toggled with CoreFeatureSignExtensionOps + + OpcodeI32Extend8S: OpcodeI32Extend8SName, + OpcodeI32Extend16S: OpcodeI32Extend16SName, + OpcodeI64Extend8S: OpcodeI64Extend8SName, + OpcodeI64Extend16S: OpcodeI64Extend16SName, + OpcodeI64Extend32S: OpcodeI64Extend32SName, + + OpcodeMiscPrefix: OpcodeMiscPrefixName, + OpcodeVecPrefix: OpcodeVecPrefixName, +} + +// InstructionName returns the instruction corresponding to this binary Opcode. +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#a7-index-of-instructions +func InstructionName(oc Opcode) string { + return instructionNames[oc] +} + +const ( + OpcodeI32TruncSatF32SName = "i32.trunc_sat_f32_s" + OpcodeI32TruncSatF32UName = "i32.trunc_sat_f32_u" + OpcodeI32TruncSatF64SName = "i32.trunc_sat_f64_s" + OpcodeI32TruncSatF64UName = "i32.trunc_sat_f64_u" + OpcodeI64TruncSatF32SName = "i64.trunc_sat_f32_s" + OpcodeI64TruncSatF32UName = "i64.trunc_sat_f32_u" + OpcodeI64TruncSatF64SName = "i64.trunc_sat_f64_s" + OpcodeI64TruncSatF64UName = "i64.trunc_sat_f64_u" + + OpcodeMemoryInitName = "memory.init" + OpcodeDataDropName = "data.drop" + OpcodeMemoryCopyName = "memory.copy" + OpcodeMemoryFillName = "memory.fill" + OpcodeTableInitName = "table.init" + OpcodeElemDropName = "elem.drop" + OpcodeTableCopyName = "table.copy" + OpcodeTableGrowName = "table.grow" + OpcodeTableSizeName = "table.size" + OpcodeTableFillName = "table.fill" +) + +var miscInstructionNames = [256]string{ + OpcodeMiscI32TruncSatF32S: OpcodeI32TruncSatF32SName, + OpcodeMiscI32TruncSatF32U: OpcodeI32TruncSatF32UName, + OpcodeMiscI32TruncSatF64S: OpcodeI32TruncSatF64SName, + OpcodeMiscI32TruncSatF64U: OpcodeI32TruncSatF64UName, + OpcodeMiscI64TruncSatF32S: OpcodeI64TruncSatF32SName, + OpcodeMiscI64TruncSatF32U: OpcodeI64TruncSatF32UName, + OpcodeMiscI64TruncSatF64S: OpcodeI64TruncSatF64SName, + OpcodeMiscI64TruncSatF64U: OpcodeI64TruncSatF64UName, + + OpcodeMiscMemoryInit: OpcodeMemoryInitName, + OpcodeMiscDataDrop: OpcodeDataDropName, + OpcodeMiscMemoryCopy: OpcodeMemoryCopyName, + OpcodeMiscMemoryFill: OpcodeMemoryFillName, + OpcodeMiscTableInit: OpcodeTableInitName, + OpcodeMiscElemDrop: OpcodeElemDropName, + OpcodeMiscTableCopy: OpcodeTableCopyName, + OpcodeMiscTableGrow: OpcodeTableGrowName, + OpcodeMiscTableSize: OpcodeTableSizeName, + OpcodeMiscTableFill: OpcodeTableFillName, +} + +// MiscInstructionName returns the instruction corresponding to this miscellaneous Opcode. +func MiscInstructionName(oc OpcodeMisc) string { + return miscInstructionNames[oc] +} + +const ( + OpcodeVecV128LoadName = "v128.load" + OpcodeVecV128Load8x8SName = "v128.load8x8_s" + OpcodeVecV128Load8x8UName = "v128.load8x8_u" + OpcodeVecV128Load16x4SName = "v128.load16x4_s" + OpcodeVecV128Load16x4UName = "v128.load16x4_u" + OpcodeVecV128Load32x2SName = "v128.load32x2_s" + OpcodeVecV128Load32x2UName = "v128.load32x2_u" + OpcodeVecV128Load8SplatName = "v128.load8_splat" + OpcodeVecV128Load16SplatName = "v128.load16_splat" + OpcodeVecV128Load32SplatName = "v128.load32_splat" + OpcodeVecV128Load64SplatName = "v128.load64_splat" + OpcodeVecV128Load32zeroName = "v128.load32_zero" + OpcodeVecV128Load64zeroName = "v128.load64_zero" + OpcodeVecV128StoreName = "v128.store" + OpcodeVecV128Load8LaneName = "v128.load8_lane" + OpcodeVecV128Load16LaneName = "v128.load16_lane" + OpcodeVecV128Load32LaneName = "v128.load32_lane" + OpcodeVecV128Load64LaneName = "v128.load64_lane" + OpcodeVecV128Store8LaneName = "v128.store8_lane" + OpcodeVecV128Store16LaneName = "v128.store16_lane" + OpcodeVecV128Store32LaneName = "v128.store32_lane" + OpcodeVecV128Store64LaneName = "v128.store64_lane" + OpcodeVecV128ConstName = "v128.const" + OpcodeVecV128i8x16ShuffleName = "v128.shuffle" + OpcodeVecI8x16ExtractLaneSName = "i8x16.extract_lane_s" + OpcodeVecI8x16ExtractLaneUName = "i8x16.extract_lane_u" + OpcodeVecI8x16ReplaceLaneName = "i8x16.replace_lane" + OpcodeVecI16x8ExtractLaneSName = "i16x8.extract_lane_s" + OpcodeVecI16x8ExtractLaneUName = "i16x8.extract_lane_u" + OpcodeVecI16x8ReplaceLaneName = "i16x8.replace_lane" + OpcodeVecI32x4ExtractLaneName = "i32x4.extract_lane" + OpcodeVecI32x4ReplaceLaneName = "i32x4.replace_lane" + OpcodeVecI64x2ExtractLaneName = "i64x2.extract_lane" + OpcodeVecI64x2ReplaceLaneName = "i64x2.replace_lane" + OpcodeVecF32x4ExtractLaneName = "f32x4.extract_lane" + OpcodeVecF32x4ReplaceLaneName = "f32x4.replace_lane" + OpcodeVecF64x2ExtractLaneName = "f64x2.extract_lane" + OpcodeVecF64x2ReplaceLaneName = "f64x2.replace_lane" + OpcodeVecI8x16SwizzleName = "i8x16.swizzle" + OpcodeVecI8x16SplatName = "i8x16.splat" + OpcodeVecI16x8SplatName = "i16x8.splat" + OpcodeVecI32x4SplatName = "i32x4.splat" + OpcodeVecI64x2SplatName = "i64x2.splat" + OpcodeVecF32x4SplatName = "f32x4.splat" + OpcodeVecF64x2SplatName = "f64x2.splat" + OpcodeVecI8x16EqName = "i8x16.eq" + OpcodeVecI8x16NeName = "i8x16.ne" + OpcodeVecI8x16LtSName = "i8x16.lt_s" + OpcodeVecI8x16LtUName = "i8x16.lt_u" + OpcodeVecI8x16GtSName = "i8x16.gt_s" + OpcodeVecI8x16GtUName = "i8x16.gt_u" + OpcodeVecI8x16LeSName = "i8x16.le_s" + OpcodeVecI8x16LeUName = "i8x16.le_u" + OpcodeVecI8x16GeSName = "i8x16.ge_s" + OpcodeVecI8x16GeUName = "i8x16.ge_u" + OpcodeVecI16x8EqName = "i16x8.eq" + OpcodeVecI16x8NeName = "i16x8.ne" + OpcodeVecI16x8LtSName = "i16x8.lt_s" + OpcodeVecI16x8LtUName = "i16x8.lt_u" + OpcodeVecI16x8GtSName = "i16x8.gt_s" + OpcodeVecI16x8GtUName = "i16x8.gt_u" + OpcodeVecI16x8LeSName = "i16x8.le_s" + OpcodeVecI16x8LeUName = "i16x8.le_u" + OpcodeVecI16x8GeSName = "i16x8.ge_s" + OpcodeVecI16x8GeUName = "i16x8.ge_u" + OpcodeVecI32x4EqName = "i32x4.eq" + OpcodeVecI32x4NeName = "i32x4.ne" + OpcodeVecI32x4LtSName = "i32x4.lt_s" + OpcodeVecI32x4LtUName = "i32x4.lt_u" + OpcodeVecI32x4GtSName = "i32x4.gt_s" + OpcodeVecI32x4GtUName = "i32x4.gt_u" + OpcodeVecI32x4LeSName = "i32x4.le_s" + OpcodeVecI32x4LeUName = "i32x4.le_u" + OpcodeVecI32x4GeSName = "i32x4.ge_s" + OpcodeVecI32x4GeUName = "i32x4.ge_u" + OpcodeVecI64x2EqName = "i64x2.eq" + OpcodeVecI64x2NeName = "i64x2.ne" + OpcodeVecI64x2LtSName = "i64x2.lt" + OpcodeVecI64x2GtSName = "i64x2.gt" + OpcodeVecI64x2LeSName = "i64x2.le" + OpcodeVecI64x2GeSName = "i64x2.ge" + OpcodeVecF32x4EqName = "f32x4.eq" + OpcodeVecF32x4NeName = "f32x4.ne" + OpcodeVecF32x4LtName = "f32x4.lt" + OpcodeVecF32x4GtName = "f32x4.gt" + OpcodeVecF32x4LeName = "f32x4.le" + OpcodeVecF32x4GeName = "f32x4.ge" + OpcodeVecF64x2EqName = "f64x2.eq" + OpcodeVecF64x2NeName = "f64x2.ne" + OpcodeVecF64x2LtName = "f64x2.lt" + OpcodeVecF64x2GtName = "f64x2.gt" + OpcodeVecF64x2LeName = "f64x2.le" + OpcodeVecF64x2GeName = "f64x2.ge" + OpcodeVecV128NotName = "v128.not" + OpcodeVecV128AndName = "v128.and" + OpcodeVecV128AndNotName = "v128.andnot" + OpcodeVecV128OrName = "v128.or" + OpcodeVecV128XorName = "v128.xor" + OpcodeVecV128BitselectName = "v128.bitselect" + OpcodeVecV128AnyTrueName = "v128.any_true" + OpcodeVecI8x16AbsName = "i8x16.abs" + OpcodeVecI8x16NegName = "i8x16.neg" + OpcodeVecI8x16PopcntName = "i8x16.popcnt" + OpcodeVecI8x16AllTrueName = "i8x16.all_true" + OpcodeVecI8x16BitMaskName = "i8x16.bitmask" + OpcodeVecI8x16NarrowI16x8SName = "i8x16.narrow_i16x8_s" + OpcodeVecI8x16NarrowI16x8UName = "i8x16.narrow_i16x8_u" + OpcodeVecI8x16ShlName = "i8x16.shl" + OpcodeVecI8x16ShrSName = "i8x16.shr_s" + OpcodeVecI8x16ShrUName = "i8x16.shr_u" + OpcodeVecI8x16AddName = "i8x16.add" + OpcodeVecI8x16AddSatSName = "i8x16.add_sat_s" + OpcodeVecI8x16AddSatUName = "i8x16.add_sat_u" + OpcodeVecI8x16SubName = "i8x16.sub" + OpcodeVecI8x16SubSatSName = "i8x16.sub_s" + OpcodeVecI8x16SubSatUName = "i8x16.sub_u" + OpcodeVecI8x16MinSName = "i8x16.min_s" + OpcodeVecI8x16MinUName = "i8x16.min_u" + OpcodeVecI8x16MaxSName = "i8x16.max_s" + OpcodeVecI8x16MaxUName = "i8x16.max_u" + OpcodeVecI8x16AvgrUName = "i8x16.avgr_u" + OpcodeVecI16x8ExtaddPairwiseI8x16SName = "i16x8.extadd_pairwise_i8x16_s" + OpcodeVecI16x8ExtaddPairwiseI8x16UName = "i16x8.extadd_pairwise_i8x16_u" + OpcodeVecI16x8AbsName = "i16x8.abs" + OpcodeVecI16x8NegName = "i16x8.neg" + OpcodeVecI16x8Q15mulrSatSName = "i16x8.q15mulr_sat_s" + OpcodeVecI16x8AllTrueName = "i16x8.all_true" + OpcodeVecI16x8BitMaskName = "i16x8.bitmask" + OpcodeVecI16x8NarrowI32x4SName = "i16x8.narrow_i32x4_s" + OpcodeVecI16x8NarrowI32x4UName = "i16x8.narrow_i32x4_u" + OpcodeVecI16x8ExtendLowI8x16SName = "i16x8.extend_low_i8x16_s" + OpcodeVecI16x8ExtendHighI8x16SName = "i16x8.extend_high_i8x16_s" + OpcodeVecI16x8ExtendLowI8x16UName = "i16x8.extend_low_i8x16_u" + OpcodeVecI16x8ExtendHighI8x16UName = "i16x8.extend_high_i8x16_u" + OpcodeVecI16x8ShlName = "i16x8.shl" + OpcodeVecI16x8ShrSName = "i16x8.shr_s" + OpcodeVecI16x8ShrUName = "i16x8.shr_u" + OpcodeVecI16x8AddName = "i16x8.add" + OpcodeVecI16x8AddSatSName = "i16x8.add_sat_s" + OpcodeVecI16x8AddSatUName = "i16x8.add_sat_u" + OpcodeVecI16x8SubName = "i16x8.sub" + OpcodeVecI16x8SubSatSName = "i16x8.sub_sat_s" + OpcodeVecI16x8SubSatUName = "i16x8.sub_sat_u" + OpcodeVecI16x8MulName = "i16x8.mul" + OpcodeVecI16x8MinSName = "i16x8.min_s" + OpcodeVecI16x8MinUName = "i16x8.min_u" + OpcodeVecI16x8MaxSName = "i16x8.max_s" + OpcodeVecI16x8MaxUName = "i16x8.max_u" + OpcodeVecI16x8AvgrUName = "i16x8.avgr_u" + OpcodeVecI16x8ExtMulLowI8x16SName = "i16x8.extmul_low_i8x16_s" + OpcodeVecI16x8ExtMulHighI8x16SName = "i16x8.extmul_high_i8x16_s" + OpcodeVecI16x8ExtMulLowI8x16UName = "i16x8.extmul_low_i8x16_u" + OpcodeVecI16x8ExtMulHighI8x16UName = "i16x8.extmul_high_i8x16_u" + OpcodeVecI32x4ExtaddPairwiseI16x8SName = "i32x4.extadd_pairwise_i16x8_s" + OpcodeVecI32x4ExtaddPairwiseI16x8UName = "i32x4.extadd_pairwise_i16x8_u" + OpcodeVecI32x4AbsName = "i32x4.abs" + OpcodeVecI32x4NegName = "i32x4.neg" + OpcodeVecI32x4AllTrueName = "i32x4.all_true" + OpcodeVecI32x4BitMaskName = "i32x4.bitmask" + OpcodeVecI32x4ExtendLowI16x8SName = "i32x4.extend_low_i16x8_s" + OpcodeVecI32x4ExtendHighI16x8SName = "i32x4.extend_high_i16x8_s" + OpcodeVecI32x4ExtendLowI16x8UName = "i32x4.extend_low_i16x8_u" + OpcodeVecI32x4ExtendHighI16x8UName = "i32x4.extend_high_i16x8_u" + OpcodeVecI32x4ShlName = "i32x4.shl" + OpcodeVecI32x4ShrSName = "i32x4.shr_s" + OpcodeVecI32x4ShrUName = "i32x4.shr_u" + OpcodeVecI32x4AddName = "i32x4.add" + OpcodeVecI32x4SubName = "i32x4.sub" + OpcodeVecI32x4MulName = "i32x4.mul" + OpcodeVecI32x4MinSName = "i32x4.min_s" + OpcodeVecI32x4MinUName = "i32x4.min_u" + OpcodeVecI32x4MaxSName = "i32x4.max_s" + OpcodeVecI32x4MaxUName = "i32x4.max_u" + OpcodeVecI32x4DotI16x8SName = "i32x4.dot_i16x8_s" + OpcodeVecI32x4ExtMulLowI16x8SName = "i32x4.extmul_low_i16x8_s" + OpcodeVecI32x4ExtMulHighI16x8SName = "i32x4.extmul_high_i16x8_s" + OpcodeVecI32x4ExtMulLowI16x8UName = "i32x4.extmul_low_i16x8_u" + OpcodeVecI32x4ExtMulHighI16x8UName = "i32x4.extmul_high_i16x8_u" + OpcodeVecI64x2AbsName = "i64x2.abs" + OpcodeVecI64x2NegName = "i64x2.neg" + OpcodeVecI64x2AllTrueName = "i64x2.all_true" + OpcodeVecI64x2BitMaskName = "i64x2.bitmask" + OpcodeVecI64x2ExtendLowI32x4SName = "i64x2.extend_low_i32x4_s" + OpcodeVecI64x2ExtendHighI32x4SName = "i64x2.extend_high_i32x4_s" + OpcodeVecI64x2ExtendLowI32x4UName = "i64x2.extend_low_i32x4_u" + OpcodeVecI64x2ExtendHighI32x4UName = "i64x2.extend_high_i32x4_u" + OpcodeVecI64x2ShlName = "i64x2.shl" + OpcodeVecI64x2ShrSName = "i64x2.shr_s" + OpcodeVecI64x2ShrUName = "i64x2.shr_u" + OpcodeVecI64x2AddName = "i64x2.add" + OpcodeVecI64x2SubName = "i64x2.sub" + OpcodeVecI64x2MulName = "i64x2.mul" + OpcodeVecI64x2ExtMulLowI32x4SName = "i64x2.extmul_low_i32x4_s" + OpcodeVecI64x2ExtMulHighI32x4SName = "i64x2.extmul_high_i32x4_s" + OpcodeVecI64x2ExtMulLowI32x4UName = "i64x2.extmul_low_i32x4_u" + OpcodeVecI64x2ExtMulHighI32x4UName = "i64x2.extmul_high_i32x4_u" + OpcodeVecF32x4CeilName = "f32x4.ceil" + OpcodeVecF32x4FloorName = "f32x4.floor" + OpcodeVecF32x4TruncName = "f32x4.trunc" + OpcodeVecF32x4NearestName = "f32x4.nearest" + OpcodeVecF32x4AbsName = "f32x4.abs" + OpcodeVecF32x4NegName = "f32x4.neg" + OpcodeVecF32x4SqrtName = "f32x4.sqrt" + OpcodeVecF32x4AddName = "f32x4.add" + OpcodeVecF32x4SubName = "f32x4.sub" + OpcodeVecF32x4MulName = "f32x4.mul" + OpcodeVecF32x4DivName = "f32x4.div" + OpcodeVecF32x4MinName = "f32x4.min" + OpcodeVecF32x4MaxName = "f32x4.max" + OpcodeVecF32x4PminName = "f32x4.pmin" + OpcodeVecF32x4PmaxName = "f32x4.pmax" + OpcodeVecF64x2CeilName = "f64x2.ceil" + OpcodeVecF64x2FloorName = "f64x2.floor" + OpcodeVecF64x2TruncName = "f64x2.trunc" + OpcodeVecF64x2NearestName = "f64x2.nearest" + OpcodeVecF64x2AbsName = "f64x2.abs" + OpcodeVecF64x2NegName = "f64x2.neg" + OpcodeVecF64x2SqrtName = "f64x2.sqrt" + OpcodeVecF64x2AddName = "f64x2.add" + OpcodeVecF64x2SubName = "f64x2.sub" + OpcodeVecF64x2MulName = "f64x2.mul" + OpcodeVecF64x2DivName = "f64x2.div" + OpcodeVecF64x2MinName = "f64x2.min" + OpcodeVecF64x2MaxName = "f64x2.max" + OpcodeVecF64x2PminName = "f64x2.pmin" + OpcodeVecF64x2PmaxName = "f64x2.pmax" + OpcodeVecI32x4TruncSatF32x4SName = "i32x4.trunc_sat_f32x4_s" + OpcodeVecI32x4TruncSatF32x4UName = "i32x4.trunc_sat_f32x4_u" + OpcodeVecF32x4ConvertI32x4SName = "f32x4.convert_i32x4_s" + OpcodeVecF32x4ConvertI32x4UName = "f32x4.convert_i32x4_u" + OpcodeVecI32x4TruncSatF64x2SZeroName = "i32x4.trunc_sat_f64x2_s_zero" + OpcodeVecI32x4TruncSatF64x2UZeroName = "i32x4.trunc_sat_f64x2_u_zero" + OpcodeVecF64x2ConvertLowI32x4SName = "f64x2.convert_low_i32x4_s" + OpcodeVecF64x2ConvertLowI32x4UName = "f64x2.convert_low_i32x4_u" + OpcodeVecF32x4DemoteF64x2ZeroName = "f32x4.demote_f64x2_zero" + OpcodeVecF64x2PromoteLowF32x4ZeroName = "f64x2.promote_low_f32x4" +) + +var vectorInstructionName = map[OpcodeVec]string{ + OpcodeVecV128Load: OpcodeVecV128LoadName, + OpcodeVecV128Load8x8s: OpcodeVecV128Load8x8SName, + OpcodeVecV128Load8x8u: OpcodeVecV128Load8x8UName, + OpcodeVecV128Load16x4s: OpcodeVecV128Load16x4SName, + OpcodeVecV128Load16x4u: OpcodeVecV128Load16x4UName, + OpcodeVecV128Load32x2s: OpcodeVecV128Load32x2SName, + OpcodeVecV128Load32x2u: OpcodeVecV128Load32x2UName, + OpcodeVecV128Load8Splat: OpcodeVecV128Load8SplatName, + OpcodeVecV128Load16Splat: OpcodeVecV128Load16SplatName, + OpcodeVecV128Load32Splat: OpcodeVecV128Load32SplatName, + OpcodeVecV128Load64Splat: OpcodeVecV128Load64SplatName, + OpcodeVecV128Load32zero: OpcodeVecV128Load32zeroName, + OpcodeVecV128Load64zero: OpcodeVecV128Load64zeroName, + OpcodeVecV128Store: OpcodeVecV128StoreName, + OpcodeVecV128Load8Lane: OpcodeVecV128Load8LaneName, + OpcodeVecV128Load16Lane: OpcodeVecV128Load16LaneName, + OpcodeVecV128Load32Lane: OpcodeVecV128Load32LaneName, + OpcodeVecV128Load64Lane: OpcodeVecV128Load64LaneName, + OpcodeVecV128Store8Lane: OpcodeVecV128Store8LaneName, + OpcodeVecV128Store16Lane: OpcodeVecV128Store16LaneName, + OpcodeVecV128Store32Lane: OpcodeVecV128Store32LaneName, + OpcodeVecV128Store64Lane: OpcodeVecV128Store64LaneName, + OpcodeVecV128Const: OpcodeVecV128ConstName, + OpcodeVecV128i8x16Shuffle: OpcodeVecV128i8x16ShuffleName, + OpcodeVecI8x16ExtractLaneS: OpcodeVecI8x16ExtractLaneSName, + OpcodeVecI8x16ExtractLaneU: OpcodeVecI8x16ExtractLaneUName, + OpcodeVecI8x16ReplaceLane: OpcodeVecI8x16ReplaceLaneName, + OpcodeVecI16x8ExtractLaneS: OpcodeVecI16x8ExtractLaneSName, + OpcodeVecI16x8ExtractLaneU: OpcodeVecI16x8ExtractLaneUName, + OpcodeVecI16x8ReplaceLane: OpcodeVecI16x8ReplaceLaneName, + OpcodeVecI32x4ExtractLane: OpcodeVecI32x4ExtractLaneName, + OpcodeVecI32x4ReplaceLane: OpcodeVecI32x4ReplaceLaneName, + OpcodeVecI64x2ExtractLane: OpcodeVecI64x2ExtractLaneName, + OpcodeVecI64x2ReplaceLane: OpcodeVecI64x2ReplaceLaneName, + OpcodeVecF32x4ExtractLane: OpcodeVecF32x4ExtractLaneName, + OpcodeVecF32x4ReplaceLane: OpcodeVecF32x4ReplaceLaneName, + OpcodeVecF64x2ExtractLane: OpcodeVecF64x2ExtractLaneName, + OpcodeVecF64x2ReplaceLane: OpcodeVecF64x2ReplaceLaneName, + OpcodeVecI8x16Swizzle: OpcodeVecI8x16SwizzleName, + OpcodeVecI8x16Splat: OpcodeVecI8x16SplatName, + OpcodeVecI16x8Splat: OpcodeVecI16x8SplatName, + OpcodeVecI32x4Splat: OpcodeVecI32x4SplatName, + OpcodeVecI64x2Splat: OpcodeVecI64x2SplatName, + OpcodeVecF32x4Splat: OpcodeVecF32x4SplatName, + OpcodeVecF64x2Splat: OpcodeVecF64x2SplatName, + OpcodeVecI8x16Eq: OpcodeVecI8x16EqName, + OpcodeVecI8x16Ne: OpcodeVecI8x16NeName, + OpcodeVecI8x16LtS: OpcodeVecI8x16LtSName, + OpcodeVecI8x16LtU: OpcodeVecI8x16LtUName, + OpcodeVecI8x16GtS: OpcodeVecI8x16GtSName, + OpcodeVecI8x16GtU: OpcodeVecI8x16GtUName, + OpcodeVecI8x16LeS: OpcodeVecI8x16LeSName, + OpcodeVecI8x16LeU: OpcodeVecI8x16LeUName, + OpcodeVecI8x16GeS: OpcodeVecI8x16GeSName, + OpcodeVecI8x16GeU: OpcodeVecI8x16GeUName, + OpcodeVecI16x8Eq: OpcodeVecI16x8EqName, + OpcodeVecI16x8Ne: OpcodeVecI16x8NeName, + OpcodeVecI16x8LtS: OpcodeVecI16x8LtSName, + OpcodeVecI16x8LtU: OpcodeVecI16x8LtUName, + OpcodeVecI16x8GtS: OpcodeVecI16x8GtSName, + OpcodeVecI16x8GtU: OpcodeVecI16x8GtUName, + OpcodeVecI16x8LeS: OpcodeVecI16x8LeSName, + OpcodeVecI16x8LeU: OpcodeVecI16x8LeUName, + OpcodeVecI16x8GeS: OpcodeVecI16x8GeSName, + OpcodeVecI16x8GeU: OpcodeVecI16x8GeUName, + OpcodeVecI32x4Eq: OpcodeVecI32x4EqName, + OpcodeVecI32x4Ne: OpcodeVecI32x4NeName, + OpcodeVecI32x4LtS: OpcodeVecI32x4LtSName, + OpcodeVecI32x4LtU: OpcodeVecI32x4LtUName, + OpcodeVecI32x4GtS: OpcodeVecI32x4GtSName, + OpcodeVecI32x4GtU: OpcodeVecI32x4GtUName, + OpcodeVecI32x4LeS: OpcodeVecI32x4LeSName, + OpcodeVecI32x4LeU: OpcodeVecI32x4LeUName, + OpcodeVecI32x4GeS: OpcodeVecI32x4GeSName, + OpcodeVecI32x4GeU: OpcodeVecI32x4GeUName, + OpcodeVecI64x2Eq: OpcodeVecI64x2EqName, + OpcodeVecI64x2Ne: OpcodeVecI64x2NeName, + OpcodeVecI64x2LtS: OpcodeVecI64x2LtSName, + OpcodeVecI64x2GtS: OpcodeVecI64x2GtSName, + OpcodeVecI64x2LeS: OpcodeVecI64x2LeSName, + OpcodeVecI64x2GeS: OpcodeVecI64x2GeSName, + OpcodeVecF32x4Eq: OpcodeVecF32x4EqName, + OpcodeVecF32x4Ne: OpcodeVecF32x4NeName, + OpcodeVecF32x4Lt: OpcodeVecF32x4LtName, + OpcodeVecF32x4Gt: OpcodeVecF32x4GtName, + OpcodeVecF32x4Le: OpcodeVecF32x4LeName, + OpcodeVecF32x4Ge: OpcodeVecF32x4GeName, + OpcodeVecF64x2Eq: OpcodeVecF64x2EqName, + OpcodeVecF64x2Ne: OpcodeVecF64x2NeName, + OpcodeVecF64x2Lt: OpcodeVecF64x2LtName, + OpcodeVecF64x2Gt: OpcodeVecF64x2GtName, + OpcodeVecF64x2Le: OpcodeVecF64x2LeName, + OpcodeVecF64x2Ge: OpcodeVecF64x2GeName, + OpcodeVecV128Not: OpcodeVecV128NotName, + OpcodeVecV128And: OpcodeVecV128AndName, + OpcodeVecV128AndNot: OpcodeVecV128AndNotName, + OpcodeVecV128Or: OpcodeVecV128OrName, + OpcodeVecV128Xor: OpcodeVecV128XorName, + OpcodeVecV128Bitselect: OpcodeVecV128BitselectName, + OpcodeVecV128AnyTrue: OpcodeVecV128AnyTrueName, + OpcodeVecI8x16Abs: OpcodeVecI8x16AbsName, + OpcodeVecI8x16Neg: OpcodeVecI8x16NegName, + OpcodeVecI8x16Popcnt: OpcodeVecI8x16PopcntName, + OpcodeVecI8x16AllTrue: OpcodeVecI8x16AllTrueName, + OpcodeVecI8x16BitMask: OpcodeVecI8x16BitMaskName, + OpcodeVecI8x16NarrowI16x8S: OpcodeVecI8x16NarrowI16x8SName, + OpcodeVecI8x16NarrowI16x8U: OpcodeVecI8x16NarrowI16x8UName, + OpcodeVecI8x16Shl: OpcodeVecI8x16ShlName, + OpcodeVecI8x16ShrS: OpcodeVecI8x16ShrSName, + OpcodeVecI8x16ShrU: OpcodeVecI8x16ShrUName, + OpcodeVecI8x16Add: OpcodeVecI8x16AddName, + OpcodeVecI8x16AddSatS: OpcodeVecI8x16AddSatSName, + OpcodeVecI8x16AddSatU: OpcodeVecI8x16AddSatUName, + OpcodeVecI8x16Sub: OpcodeVecI8x16SubName, + OpcodeVecI8x16SubSatS: OpcodeVecI8x16SubSatSName, + OpcodeVecI8x16SubSatU: OpcodeVecI8x16SubSatUName, + OpcodeVecI8x16MinS: OpcodeVecI8x16MinSName, + OpcodeVecI8x16MinU: OpcodeVecI8x16MinUName, + OpcodeVecI8x16MaxS: OpcodeVecI8x16MaxSName, + OpcodeVecI8x16MaxU: OpcodeVecI8x16MaxUName, + OpcodeVecI8x16AvgrU: OpcodeVecI8x16AvgrUName, + OpcodeVecI16x8ExtaddPairwiseI8x16S: OpcodeVecI16x8ExtaddPairwiseI8x16SName, + OpcodeVecI16x8ExtaddPairwiseI8x16U: OpcodeVecI16x8ExtaddPairwiseI8x16UName, + OpcodeVecI16x8Abs: OpcodeVecI16x8AbsName, + OpcodeVecI16x8Neg: OpcodeVecI16x8NegName, + OpcodeVecI16x8Q15mulrSatS: OpcodeVecI16x8Q15mulrSatSName, + OpcodeVecI16x8AllTrue: OpcodeVecI16x8AllTrueName, + OpcodeVecI16x8BitMask: OpcodeVecI16x8BitMaskName, + OpcodeVecI16x8NarrowI32x4S: OpcodeVecI16x8NarrowI32x4SName, + OpcodeVecI16x8NarrowI32x4U: OpcodeVecI16x8NarrowI32x4UName, + OpcodeVecI16x8ExtendLowI8x16S: OpcodeVecI16x8ExtendLowI8x16SName, + OpcodeVecI16x8ExtendHighI8x16S: OpcodeVecI16x8ExtendHighI8x16SName, + OpcodeVecI16x8ExtendLowI8x16U: OpcodeVecI16x8ExtendLowI8x16UName, + OpcodeVecI16x8ExtendHighI8x16U: OpcodeVecI16x8ExtendHighI8x16UName, + OpcodeVecI16x8Shl: OpcodeVecI16x8ShlName, + OpcodeVecI16x8ShrS: OpcodeVecI16x8ShrSName, + OpcodeVecI16x8ShrU: OpcodeVecI16x8ShrUName, + OpcodeVecI16x8Add: OpcodeVecI16x8AddName, + OpcodeVecI16x8AddSatS: OpcodeVecI16x8AddSatSName, + OpcodeVecI16x8AddSatU: OpcodeVecI16x8AddSatUName, + OpcodeVecI16x8Sub: OpcodeVecI16x8SubName, + OpcodeVecI16x8SubSatS: OpcodeVecI16x8SubSatSName, + OpcodeVecI16x8SubSatU: OpcodeVecI16x8SubSatUName, + OpcodeVecI16x8Mul: OpcodeVecI16x8MulName, + OpcodeVecI16x8MinS: OpcodeVecI16x8MinSName, + OpcodeVecI16x8MinU: OpcodeVecI16x8MinUName, + OpcodeVecI16x8MaxS: OpcodeVecI16x8MaxSName, + OpcodeVecI16x8MaxU: OpcodeVecI16x8MaxUName, + OpcodeVecI16x8AvgrU: OpcodeVecI16x8AvgrUName, + OpcodeVecI16x8ExtMulLowI8x16S: OpcodeVecI16x8ExtMulLowI8x16SName, + OpcodeVecI16x8ExtMulHighI8x16S: OpcodeVecI16x8ExtMulHighI8x16SName, + OpcodeVecI16x8ExtMulLowI8x16U: OpcodeVecI16x8ExtMulLowI8x16UName, + OpcodeVecI16x8ExtMulHighI8x16U: OpcodeVecI16x8ExtMulHighI8x16UName, + OpcodeVecI32x4ExtaddPairwiseI16x8S: OpcodeVecI32x4ExtaddPairwiseI16x8SName, + OpcodeVecI32x4ExtaddPairwiseI16x8U: OpcodeVecI32x4ExtaddPairwiseI16x8UName, + OpcodeVecI32x4Abs: OpcodeVecI32x4AbsName, + OpcodeVecI32x4Neg: OpcodeVecI32x4NegName, + OpcodeVecI32x4AllTrue: OpcodeVecI32x4AllTrueName, + OpcodeVecI32x4BitMask: OpcodeVecI32x4BitMaskName, + OpcodeVecI32x4ExtendLowI16x8S: OpcodeVecI32x4ExtendLowI16x8SName, + OpcodeVecI32x4ExtendHighI16x8S: OpcodeVecI32x4ExtendHighI16x8SName, + OpcodeVecI32x4ExtendLowI16x8U: OpcodeVecI32x4ExtendLowI16x8UName, + OpcodeVecI32x4ExtendHighI16x8U: OpcodeVecI32x4ExtendHighI16x8UName, + OpcodeVecI32x4Shl: OpcodeVecI32x4ShlName, + OpcodeVecI32x4ShrS: OpcodeVecI32x4ShrSName, + OpcodeVecI32x4ShrU: OpcodeVecI32x4ShrUName, + OpcodeVecI32x4Add: OpcodeVecI32x4AddName, + OpcodeVecI32x4Sub: OpcodeVecI32x4SubName, + OpcodeVecI32x4Mul: OpcodeVecI32x4MulName, + OpcodeVecI32x4MinS: OpcodeVecI32x4MinSName, + OpcodeVecI32x4MinU: OpcodeVecI32x4MinUName, + OpcodeVecI32x4MaxS: OpcodeVecI32x4MaxSName, + OpcodeVecI32x4MaxU: OpcodeVecI32x4MaxUName, + OpcodeVecI32x4DotI16x8S: OpcodeVecI32x4DotI16x8SName, + OpcodeVecI32x4ExtMulLowI16x8S: OpcodeVecI32x4ExtMulLowI16x8SName, + OpcodeVecI32x4ExtMulHighI16x8S: OpcodeVecI32x4ExtMulHighI16x8SName, + OpcodeVecI32x4ExtMulLowI16x8U: OpcodeVecI32x4ExtMulLowI16x8UName, + OpcodeVecI32x4ExtMulHighI16x8U: OpcodeVecI32x4ExtMulHighI16x8UName, + OpcodeVecI64x2Abs: OpcodeVecI64x2AbsName, + OpcodeVecI64x2Neg: OpcodeVecI64x2NegName, + OpcodeVecI64x2AllTrue: OpcodeVecI64x2AllTrueName, + OpcodeVecI64x2BitMask: OpcodeVecI64x2BitMaskName, + OpcodeVecI64x2ExtendLowI32x4S: OpcodeVecI64x2ExtendLowI32x4SName, + OpcodeVecI64x2ExtendHighI32x4S: OpcodeVecI64x2ExtendHighI32x4SName, + OpcodeVecI64x2ExtendLowI32x4U: OpcodeVecI64x2ExtendLowI32x4UName, + OpcodeVecI64x2ExtendHighI32x4U: OpcodeVecI64x2ExtendHighI32x4UName, + OpcodeVecI64x2Shl: OpcodeVecI64x2ShlName, + OpcodeVecI64x2ShrS: OpcodeVecI64x2ShrSName, + OpcodeVecI64x2ShrU: OpcodeVecI64x2ShrUName, + OpcodeVecI64x2Add: OpcodeVecI64x2AddName, + OpcodeVecI64x2Sub: OpcodeVecI64x2SubName, + OpcodeVecI64x2Mul: OpcodeVecI64x2MulName, + OpcodeVecI64x2ExtMulLowI32x4S: OpcodeVecI64x2ExtMulLowI32x4SName, + OpcodeVecI64x2ExtMulHighI32x4S: OpcodeVecI64x2ExtMulHighI32x4SName, + OpcodeVecI64x2ExtMulLowI32x4U: OpcodeVecI64x2ExtMulLowI32x4UName, + OpcodeVecI64x2ExtMulHighI32x4U: OpcodeVecI64x2ExtMulHighI32x4UName, + OpcodeVecF32x4Ceil: OpcodeVecF32x4CeilName, + OpcodeVecF32x4Floor: OpcodeVecF32x4FloorName, + OpcodeVecF32x4Trunc: OpcodeVecF32x4TruncName, + OpcodeVecF32x4Nearest: OpcodeVecF32x4NearestName, + OpcodeVecF32x4Abs: OpcodeVecF32x4AbsName, + OpcodeVecF32x4Neg: OpcodeVecF32x4NegName, + OpcodeVecF32x4Sqrt: OpcodeVecF32x4SqrtName, + OpcodeVecF32x4Add: OpcodeVecF32x4AddName, + OpcodeVecF32x4Sub: OpcodeVecF32x4SubName, + OpcodeVecF32x4Mul: OpcodeVecF32x4MulName, + OpcodeVecF32x4Div: OpcodeVecF32x4DivName, + OpcodeVecF32x4Min: OpcodeVecF32x4MinName, + OpcodeVecF32x4Max: OpcodeVecF32x4MaxName, + OpcodeVecF32x4Pmin: OpcodeVecF32x4PminName, + OpcodeVecF32x4Pmax: OpcodeVecF32x4PmaxName, + OpcodeVecF64x2Ceil: OpcodeVecF64x2CeilName, + OpcodeVecF64x2Floor: OpcodeVecF64x2FloorName, + OpcodeVecF64x2Trunc: OpcodeVecF64x2TruncName, + OpcodeVecF64x2Nearest: OpcodeVecF64x2NearestName, + OpcodeVecF64x2Abs: OpcodeVecF64x2AbsName, + OpcodeVecF64x2Neg: OpcodeVecF64x2NegName, + OpcodeVecF64x2Sqrt: OpcodeVecF64x2SqrtName, + OpcodeVecF64x2Add: OpcodeVecF64x2AddName, + OpcodeVecF64x2Sub: OpcodeVecF64x2SubName, + OpcodeVecF64x2Mul: OpcodeVecF64x2MulName, + OpcodeVecF64x2Div: OpcodeVecF64x2DivName, + OpcodeVecF64x2Min: OpcodeVecF64x2MinName, + OpcodeVecF64x2Max: OpcodeVecF64x2MaxName, + OpcodeVecF64x2Pmin: OpcodeVecF64x2PminName, + OpcodeVecF64x2Pmax: OpcodeVecF64x2PmaxName, + OpcodeVecI32x4TruncSatF32x4S: OpcodeVecI32x4TruncSatF32x4SName, + OpcodeVecI32x4TruncSatF32x4U: OpcodeVecI32x4TruncSatF32x4UName, + OpcodeVecF32x4ConvertI32x4S: OpcodeVecF32x4ConvertI32x4SName, + OpcodeVecF32x4ConvertI32x4U: OpcodeVecF32x4ConvertI32x4UName, + OpcodeVecI32x4TruncSatF64x2SZero: OpcodeVecI32x4TruncSatF64x2SZeroName, + OpcodeVecI32x4TruncSatF64x2UZero: OpcodeVecI32x4TruncSatF64x2UZeroName, + OpcodeVecF64x2ConvertLowI32x4S: OpcodeVecF64x2ConvertLowI32x4SName, + OpcodeVecF64x2ConvertLowI32x4U: OpcodeVecF64x2ConvertLowI32x4UName, + OpcodeVecF32x4DemoteF64x2Zero: OpcodeVecF32x4DemoteF64x2ZeroName, + OpcodeVecF64x2PromoteLowF32x4Zero: OpcodeVecF64x2PromoteLowF32x4ZeroName, +} + +// VectorInstructionName returns the instruction name corresponding to the vector Opcode. +func VectorInstructionName(oc OpcodeVec) (ret string) { + return vectorInstructionName[oc] +} + +const ( + OpcodeAtomicMemoryNotifyName = "memory.atomic.notify" + OpcodeAtomicMemoryWait32Name = "memory.atomic.wait32" + OpcodeAtomicMemoryWait64Name = "memory.atomic.wait64" + OpcodeAtomicFenceName = "atomic.fence" + + OpcodeAtomicI32LoadName = "i32.atomic.load" + OpcodeAtomicI64LoadName = "i64.atomic.load" + OpcodeAtomicI32Load8UName = "i32.atomic.load8_u" + OpcodeAtomicI32Load16UName = "i32.atomic.load16_u" + OpcodeAtomicI64Load8UName = "i64.atomic.load8_u" + OpcodeAtomicI64Load16UName = "i64.atomic.load16_u" + OpcodeAtomicI64Load32UName = "i64.atomic.load32_u" + OpcodeAtomicI32StoreName = "i32.atomic.store" + OpcodeAtomicI64StoreName = "i64.atomic.store" + OpcodeAtomicI32Store8Name = "i32.atomic.store8" + OpcodeAtomicI32Store16Name = "i32.atomic.store16" + OpcodeAtomicI64Store8Name = "i64.atomic.store8" + OpcodeAtomicI64Store16Name = "i64.atomic.store16" + OpcodeAtomicI64Store32Name = "i64.atomic.store32" + + OpcodeAtomicI32RmwAddName = "i32.atomic.rmw.add" + OpcodeAtomicI64RmwAddName = "i64.atomic.rmw.add" + OpcodeAtomicI32Rmw8AddUName = "i32.atomic.rmw8.add_u" + OpcodeAtomicI32Rmw16AddUName = "i32.atomic.rmw16.add_u" + OpcodeAtomicI64Rmw8AddUName = "i64.atomic.rmw8.add_u" + OpcodeAtomicI64Rmw16AddUName = "i64.atomic.rmw16.add_u" + OpcodeAtomicI64Rmw32AddUName = "i64.atomic.rmw32.add_u" + + OpcodeAtomicI32RmwSubName = "i32.atomic.rmw.sub" + OpcodeAtomicI64RmwSubName = "i64.atomic.rmw.sub" + OpcodeAtomicI32Rmw8SubUName = "i32.atomic.rmw8.sub_u" + OpcodeAtomicI32Rmw16SubUName = "i32.atomic.rmw16.sub_u" + OpcodeAtomicI64Rmw8SubUName = "i64.atomic.rmw8.sub_u" + OpcodeAtomicI64Rmw16SubUName = "i64.atomic.rmw16.sub_u" + OpcodeAtomicI64Rmw32SubUName = "i64.atomic.rmw32.sub_u" + + OpcodeAtomicI32RmwAndName = "i32.atomic.rmw.and" + OpcodeAtomicI64RmwAndName = "i64.atomic.rmw.and" + OpcodeAtomicI32Rmw8AndUName = "i32.atomic.rmw8.and_u" + OpcodeAtomicI32Rmw16AndUName = "i32.atomic.rmw16.and_u" + OpcodeAtomicI64Rmw8AndUName = "i64.atomic.rmw8.and_u" + OpcodeAtomicI64Rmw16AndUName = "i64.atomic.rmw16.and_u" + OpcodeAtomicI64Rmw32AndUName = "i64.atomic.rmw32.and_u" + + OpcodeAtomicI32RmwOrName = "i32.atomic.rmw.or" + OpcodeAtomicI64RmwOrName = "i64.atomic.rmw.or" + OpcodeAtomicI32Rmw8OrUName = "i32.atomic.rmw8.or_u" + OpcodeAtomicI32Rmw16OrUName = "i32.atomic.rmw16.or_u" + OpcodeAtomicI64Rmw8OrUName = "i64.atomic.rmw8.or_u" + OpcodeAtomicI64Rmw16OrUName = "i64.atomic.rmw16.or_u" + OpcodeAtomicI64Rmw32OrUName = "i64.atomic.rmw32.or_u" + + OpcodeAtomicI32RmwXorName = "i32.atomic.rmw.xor" + OpcodeAtomicI64RmwXorName = "i64.atomic.rmw.xor" + OpcodeAtomicI32Rmw8XorUName = "i32.atomic.rmw8.xor_u" + OpcodeAtomicI32Rmw16XorUName = "i32.atomic.rmw16.xor_u" + OpcodeAtomicI64Rmw8XorUName = "i64.atomic.rmw8.xor_u" + OpcodeAtomicI64Rmw16XorUName = "i64.atomic.rmw16.xor_u" + OpcodeAtomicI64Rmw32XorUName = "i64.atomic.rmw32.xor_u" + + OpcodeAtomicI32RmwXchgName = "i32.atomic.rmw.xchg" + OpcodeAtomicI64RmwXchgName = "i64.atomic.rmw.xchg" + OpcodeAtomicI32Rmw8XchgUName = "i32.atomic.rmw8.xchg_u" + OpcodeAtomicI32Rmw16XchgUName = "i32.atomic.rmw16.xchg_u" + OpcodeAtomicI64Rmw8XchgUName = "i64.atomic.rmw8.xchg_u" + OpcodeAtomicI64Rmw16XchgUName = "i64.atomic.rmw16.xchg_u" + OpcodeAtomicI64Rmw32XchgUName = "i64.atomic.rmw32.xchg_u" + + OpcodeAtomicI32RmwCmpxchgName = "i32.atomic.rmw.cmpxchg" + OpcodeAtomicI64RmwCmpxchgName = "i64.atomic.rmw.cmpxchg" + OpcodeAtomicI32Rmw8CmpxchgUName = "i32.atomic.rmw8.cmpxchg_u" + OpcodeAtomicI32Rmw16CmpxchgUName = "i32.atomic.rmw16.cmpxchg_u" + OpcodeAtomicI64Rmw8CmpxchgUName = "i64.atomic.rmw8.cmpxchg_u" + OpcodeAtomicI64Rmw16CmpxchgUName = "i64.atomic.rmw16.cmpxchg_u" + OpcodeAtomicI64Rmw32CmpxchgUName = "i64.atomic.rmw32.cmpxchg_u" +) + +var atomicInstructionName = map[OpcodeAtomic]string{ + OpcodeAtomicMemoryNotify: OpcodeAtomicMemoryNotifyName, + OpcodeAtomicMemoryWait32: OpcodeAtomicMemoryWait32Name, + OpcodeAtomicMemoryWait64: OpcodeAtomicMemoryWait64Name, + OpcodeAtomicFence: OpcodeAtomicFenceName, + + OpcodeAtomicI32Load: OpcodeAtomicI32LoadName, + OpcodeAtomicI64Load: OpcodeAtomicI64LoadName, + OpcodeAtomicI32Load8U: OpcodeAtomicI32Load8UName, + OpcodeAtomicI32Load16U: OpcodeAtomicI32Load16UName, + OpcodeAtomicI64Load8U: OpcodeAtomicI64Load8UName, + OpcodeAtomicI64Load16U: OpcodeAtomicI64Load16UName, + OpcodeAtomicI64Load32U: OpcodeAtomicI64Load32UName, + OpcodeAtomicI32Store: OpcodeAtomicI32StoreName, + OpcodeAtomicI64Store: OpcodeAtomicI64StoreName, + OpcodeAtomicI32Store8: OpcodeAtomicI32Store8Name, + OpcodeAtomicI32Store16: OpcodeAtomicI32Store16Name, + OpcodeAtomicI64Store8: OpcodeAtomicI64Store8Name, + OpcodeAtomicI64Store16: OpcodeAtomicI64Store16Name, + OpcodeAtomicI64Store32: OpcodeAtomicI64Store32Name, + + OpcodeAtomicI32RmwAdd: OpcodeAtomicI32RmwAddName, + OpcodeAtomicI64RmwAdd: OpcodeAtomicI64RmwAddName, + OpcodeAtomicI32Rmw8AddU: OpcodeAtomicI32Rmw8AddUName, + OpcodeAtomicI32Rmw16AddU: OpcodeAtomicI32Rmw16AddUName, + OpcodeAtomicI64Rmw8AddU: OpcodeAtomicI64Rmw8AddUName, + OpcodeAtomicI64Rmw16AddU: OpcodeAtomicI64Rmw16AddUName, + OpcodeAtomicI64Rmw32AddU: OpcodeAtomicI64Rmw32AddUName, + + OpcodeAtomicI32RmwSub: OpcodeAtomicI32RmwSubName, + OpcodeAtomicI64RmwSub: OpcodeAtomicI64RmwSubName, + OpcodeAtomicI32Rmw8SubU: OpcodeAtomicI32Rmw8SubUName, + OpcodeAtomicI32Rmw16SubU: OpcodeAtomicI32Rmw16SubUName, + OpcodeAtomicI64Rmw8SubU: OpcodeAtomicI64Rmw8SubUName, + OpcodeAtomicI64Rmw16SubU: OpcodeAtomicI64Rmw16SubUName, + OpcodeAtomicI64Rmw32SubU: OpcodeAtomicI64Rmw32SubUName, + + OpcodeAtomicI32RmwAnd: OpcodeAtomicI32RmwAndName, + OpcodeAtomicI64RmwAnd: OpcodeAtomicI64RmwAndName, + OpcodeAtomicI32Rmw8AndU: OpcodeAtomicI32Rmw8AndUName, + OpcodeAtomicI32Rmw16AndU: OpcodeAtomicI32Rmw16AndUName, + OpcodeAtomicI64Rmw8AndU: OpcodeAtomicI64Rmw8AndUName, + OpcodeAtomicI64Rmw16AndU: OpcodeAtomicI64Rmw16AndUName, + OpcodeAtomicI64Rmw32AndU: OpcodeAtomicI64Rmw32AndUName, + + OpcodeAtomicI32RmwOr: OpcodeAtomicI32RmwOrName, + OpcodeAtomicI64RmwOr: OpcodeAtomicI64RmwOrName, + OpcodeAtomicI32Rmw8OrU: OpcodeAtomicI32Rmw8OrUName, + OpcodeAtomicI32Rmw16OrU: OpcodeAtomicI32Rmw16OrUName, + OpcodeAtomicI64Rmw8OrU: OpcodeAtomicI64Rmw8OrUName, + OpcodeAtomicI64Rmw16OrU: OpcodeAtomicI64Rmw16OrUName, + OpcodeAtomicI64Rmw32OrU: OpcodeAtomicI64Rmw32OrUName, + + OpcodeAtomicI32RmwXor: OpcodeAtomicI32RmwXorName, + OpcodeAtomicI64RmwXor: OpcodeAtomicI64RmwXorName, + OpcodeAtomicI32Rmw8XorU: OpcodeAtomicI32Rmw8XorUName, + OpcodeAtomicI32Rmw16XorU: OpcodeAtomicI32Rmw16XorUName, + OpcodeAtomicI64Rmw8XorU: OpcodeAtomicI64Rmw8XorUName, + OpcodeAtomicI64Rmw16XorU: OpcodeAtomicI64Rmw16XorUName, + OpcodeAtomicI64Rmw32XorU: OpcodeAtomicI64Rmw32XorUName, + + OpcodeAtomicI32RmwXchg: OpcodeAtomicI32RmwXchgName, + OpcodeAtomicI64RmwXchg: OpcodeAtomicI64RmwXchgName, + OpcodeAtomicI32Rmw8XchgU: OpcodeAtomicI32Rmw8XchgUName, + OpcodeAtomicI32Rmw16XchgU: OpcodeAtomicI32Rmw16XchgUName, + OpcodeAtomicI64Rmw8XchgU: OpcodeAtomicI64Rmw8XchgUName, + OpcodeAtomicI64Rmw16XchgU: OpcodeAtomicI64Rmw16XchgUName, + OpcodeAtomicI64Rmw32XchgU: OpcodeAtomicI64Rmw32XchgUName, + + OpcodeAtomicI32RmwCmpxchg: OpcodeAtomicI32RmwCmpxchgName, + OpcodeAtomicI64RmwCmpxchg: OpcodeAtomicI64RmwCmpxchgName, + OpcodeAtomicI32Rmw8CmpxchgU: OpcodeAtomicI32Rmw8CmpxchgUName, + OpcodeAtomicI32Rmw16CmpxchgU: OpcodeAtomicI32Rmw16CmpxchgUName, + OpcodeAtomicI64Rmw8CmpxchgU: OpcodeAtomicI64Rmw8CmpxchgUName, + OpcodeAtomicI64Rmw16CmpxchgU: OpcodeAtomicI64Rmw16CmpxchgUName, + OpcodeAtomicI64Rmw32CmpxchgU: OpcodeAtomicI64Rmw32CmpxchgUName, +} + +// AtomicInstructionName returns the instruction name corresponding to the atomic Opcode. +func AtomicInstructionName(oc OpcodeAtomic) (ret string) { + return atomicInstructionName[oc] +} + +const ( + OpcodeTailCallReturnCallName = "return_call" + OpcodeTailCallReturnCallIndirectName = "return_call_indirect" +) + +var tailCallInstructionName = map[OpcodeTailCall]string{ + OpcodeTailCallReturnCall: OpcodeTailCallReturnCallName, + OpcodeTailCallReturnCallIndirect: OpcodeTailCallReturnCallIndirectName, +} + +// TailCallInstructionName returns the instruction name corresponding to the tail call Opcode. +func TailCallInstructionName(oc OpcodeTailCall) (ret string) { + return tailCallInstructionName[oc] +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/memory.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/memory.go new file mode 100644 index 00000000000..275530558c4 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/memory.go @@ -0,0 +1,475 @@ +package wasm + +import ( + "container/list" + "encoding/binary" + "fmt" + "math" + "reflect" + "sync" + "sync/atomic" + "time" + "unsafe" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental" + "github.com/tetratelabs/wazero/internal/internalapi" + "github.com/tetratelabs/wazero/internal/wasmruntime" +) + +const ( + // MemoryPageSize is the unit of memory length in WebAssembly, + // and is defined as 2^16 = 65536. + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0 + MemoryPageSize = uint32(65536) + // MemoryLimitPages is maximum number of pages defined (2^16). + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem + MemoryLimitPages = uint32(65536) + // MemoryPageSizeInBits satisfies the relation: "1 << MemoryPageSizeInBits == MemoryPageSize". + MemoryPageSizeInBits = 16 +) + +// compile-time check to ensure MemoryInstance implements api.Memory +var _ api.Memory = &MemoryInstance{} + +type waiters struct { + mux sync.Mutex + l *list.List +} + +// MemoryInstance represents a memory instance in a store, and implements api.Memory. +// +// Note: In WebAssembly 1.0 (20191205), there may be up to one Memory per store, which means the precise memory is always +// wasm.Store Memories index zero: `store.Memories[0]` +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0. +type MemoryInstance struct { + internalapi.WazeroOnlyType + + Buffer []byte + Min, Cap, Max uint32 + Shared bool + // definition is known at compile time. + definition api.MemoryDefinition + + // Mux is used in interpreter mode to prevent overlapping calls to atomic instructions, + // introduced with WebAssembly threads proposal, and in compiler mode to make memory modifications + // within Grow non-racy for the Go race detector. + Mux sync.Mutex + + // waiters implements atomic wait and notify. It is implemented similarly to golang.org/x/sync/semaphore, + // with a fixed weight of 1 and no spurious notifications. + waiters sync.Map + + // ownerModuleEngine is the module engine that owns this memory instance. + ownerModuleEngine ModuleEngine + + expBuffer experimental.LinearMemory +} + +// NewMemoryInstance creates a new instance based on the parameters in the SectionIDMemory. +func NewMemoryInstance(memSec *Memory, allocator experimental.MemoryAllocator, moduleEngine ModuleEngine) *MemoryInstance { + minBytes := MemoryPagesToBytesNum(memSec.Min) + capBytes := MemoryPagesToBytesNum(memSec.Cap) + maxBytes := MemoryPagesToBytesNum(memSec.Max) + + var buffer []byte + var expBuffer experimental.LinearMemory + if allocator != nil { + expBuffer = allocator.Allocate(capBytes, maxBytes) + buffer = expBuffer.Reallocate(minBytes) + _ = buffer[:minBytes] // Bounds check that the minimum was allocated. + } else if memSec.IsShared { + // Shared memory needs a fixed buffer, so allocate with the maximum size. + // + // The rationale as to why we can simply use make([]byte) to a fixed buffer is that Go's GC is non-relocating. + // That is not a part of Go spec, but is well-known thing in Go community (wazero's compiler heavily relies on it!) + // * https://github.com/go4org/unsafe-assume-no-moving-gc + // + // Also, allocating Max here isn't harmful as the Go runtime uses mmap for large allocations, therefore, + // the memory buffer allocation here is virtual and doesn't consume physical memory until it's used. + // * https://github.com/golang/go/blob/go1.24.0/src/runtime/malloc.go#L1059 + buffer = make([]byte, minBytes, maxBytes) + } else { + buffer = make([]byte, minBytes, capBytes) + } + return &MemoryInstance{ + Buffer: buffer, + Min: memSec.Min, + Cap: memoryBytesNumToPages(uint64(cap(buffer))), + Max: memSec.Max, + Shared: memSec.IsShared, + expBuffer: expBuffer, + ownerModuleEngine: moduleEngine, + } +} + +// Definition implements the same method as documented on api.Memory. +func (m *MemoryInstance) Definition() api.MemoryDefinition { + return m.definition +} + +// Size implements the same method as documented on api.Memory. +func (m *MemoryInstance) Size() uint32 { + return uint32(len(m.Buffer)) +} + +// ReadByte implements the same method as documented on api.Memory. +func (m *MemoryInstance) ReadByte(offset uint32) (byte, bool) { + if !m.hasSize(offset, 1) { + return 0, false + } + return m.Buffer[offset], true +} + +// ReadUint16Le implements the same method as documented on api.Memory. +func (m *MemoryInstance) ReadUint16Le(offset uint32) (uint16, bool) { + if !m.hasSize(offset, 2) { + return 0, false + } + return binary.LittleEndian.Uint16(m.Buffer[offset : offset+2]), true +} + +// ReadUint32Le implements the same method as documented on api.Memory. +func (m *MemoryInstance) ReadUint32Le(offset uint32) (uint32, bool) { + return m.readUint32Le(offset) +} + +// ReadFloat32Le implements the same method as documented on api.Memory. +func (m *MemoryInstance) ReadFloat32Le(offset uint32) (float32, bool) { + v, ok := m.readUint32Le(offset) + if !ok { + return 0, false + } + return math.Float32frombits(v), true +} + +// ReadUint64Le implements the same method as documented on api.Memory. +func (m *MemoryInstance) ReadUint64Le(offset uint32) (uint64, bool) { + return m.readUint64Le(offset) +} + +// ReadFloat64Le implements the same method as documented on api.Memory. +func (m *MemoryInstance) ReadFloat64Le(offset uint32) (float64, bool) { + v, ok := m.readUint64Le(offset) + if !ok { + return 0, false + } + return math.Float64frombits(v), true +} + +// Read implements the same method as documented on api.Memory. +func (m *MemoryInstance) Read(offset, byteCount uint32) ([]byte, bool) { + if !m.hasSize(offset, uint64(byteCount)) { + return nil, false + } + return m.Buffer[offset : offset+byteCount : offset+byteCount], true +} + +// WriteByte implements the same method as documented on api.Memory. +func (m *MemoryInstance) WriteByte(offset uint32, v byte) bool { + if !m.hasSize(offset, 1) { + return false + } + m.Buffer[offset] = v + return true +} + +// WriteUint16Le implements the same method as documented on api.Memory. +func (m *MemoryInstance) WriteUint16Le(offset uint32, v uint16) bool { + if !m.hasSize(offset, 2) { + return false + } + binary.LittleEndian.PutUint16(m.Buffer[offset:], v) + return true +} + +// WriteUint32Le implements the same method as documented on api.Memory. +func (m *MemoryInstance) WriteUint32Le(offset, v uint32) bool { + return m.writeUint32Le(offset, v) +} + +// WriteFloat32Le implements the same method as documented on api.Memory. +func (m *MemoryInstance) WriteFloat32Le(offset uint32, v float32) bool { + return m.writeUint32Le(offset, math.Float32bits(v)) +} + +// WriteUint64Le implements the same method as documented on api.Memory. +func (m *MemoryInstance) WriteUint64Le(offset uint32, v uint64) bool { + return m.writeUint64Le(offset, v) +} + +// WriteFloat64Le implements the same method as documented on api.Memory. +func (m *MemoryInstance) WriteFloat64Le(offset uint32, v float64) bool { + return m.writeUint64Le(offset, math.Float64bits(v)) +} + +// Write implements the same method as documented on api.Memory. +func (m *MemoryInstance) Write(offset uint32, val []byte) bool { + if !m.hasSize(offset, uint64(len(val))) { + return false + } + copy(m.Buffer[offset:], val) + return true +} + +// WriteString implements the same method as documented on api.Memory. +func (m *MemoryInstance) WriteString(offset uint32, val string) bool { + if !m.hasSize(offset, uint64(len(val))) { + return false + } + copy(m.Buffer[offset:], val) + return true +} + +// MemoryPagesToBytesNum converts the given pages into the number of bytes contained in these pages. +func MemoryPagesToBytesNum(pages uint32) (bytesNum uint64) { + return uint64(pages) << MemoryPageSizeInBits +} + +// Grow implements the same method as documented on api.Memory. +func (m *MemoryInstance) Grow(delta uint32) (result uint32, ok bool) { + if m.Shared { + m.Mux.Lock() + defer m.Mux.Unlock() + } + + currentPages := m.Pages() + if delta == 0 { + return currentPages, true + } + + newPages := currentPages + delta + if newPages > m.Max || int32(delta) < 0 { + return 0, false + } else if m.expBuffer != nil { + buffer := m.expBuffer.Reallocate(MemoryPagesToBytesNum(newPages)) + if buffer == nil { + // Allocator failed to grow. + return 0, false + } + if m.Shared { + if unsafe.SliceData(buffer) != unsafe.SliceData(m.Buffer) { + panic("shared memory cannot move, this is a bug in the memory allocator") + } + // We assume grow is called under a guest lock. + // But the memory length is accessed elsewhere, + // so use atomic to make the new length visible across threads. + atomicStoreLengthAndCap(&m.Buffer, uintptr(len(buffer)), uintptr(cap(buffer))) + m.Cap = memoryBytesNumToPages(uint64(cap(buffer))) + } else { + m.Buffer = buffer + m.Cap = newPages + } + } else if newPages > m.Cap { // grow the memory. + if m.Shared { + panic("shared memory cannot be grown, this is a bug in wazero") + } + m.Buffer = append(m.Buffer, make([]byte, MemoryPagesToBytesNum(delta))...) + m.Cap = newPages + } else { // We already have the capacity we need. + if m.Shared { + // We assume grow is called under a guest lock. + // But the memory length is accessed elsewhere, + // so use atomic to make the new length visible across threads. + atomicStoreLength(&m.Buffer, uintptr(MemoryPagesToBytesNum(newPages))) + } else { + m.Buffer = m.Buffer[:MemoryPagesToBytesNum(newPages)] + } + } + m.ownerModuleEngine.MemoryGrown() + return currentPages, true +} + +// Pages implements the same method as documented on api.Memory. +func (m *MemoryInstance) Pages() (result uint32) { + return memoryBytesNumToPages(uint64(len(m.Buffer))) +} + +// PagesToUnitOfBytes converts the pages to a human-readable form similar to what's specified. e.g. 1 -> "64Ki" +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0 +func PagesToUnitOfBytes(pages uint32) string { + k := pages * 64 + if k < 1024 { + return fmt.Sprintf("%d Ki", k) + } + m := k / 1024 + if m < 1024 { + return fmt.Sprintf("%d Mi", m) + } + g := m / 1024 + if g < 1024 { + return fmt.Sprintf("%d Gi", g) + } + return fmt.Sprintf("%d Ti", g/1024) +} + +// Below are raw functions used to implement the api.Memory API: + +// Uses atomic write to update the length of a slice. +func atomicStoreLengthAndCap(slice *[]byte, length uintptr, cap uintptr) { + //nolint:staticcheck + slicePtr := (*reflect.SliceHeader)(unsafe.Pointer(slice)) + capPtr := (*uintptr)(unsafe.Pointer(&slicePtr.Cap)) + atomic.StoreUintptr(capPtr, cap) + lenPtr := (*uintptr)(unsafe.Pointer(&slicePtr.Len)) + atomic.StoreUintptr(lenPtr, length) +} + +// Uses atomic write to update the length of a slice. +func atomicStoreLength(slice *[]byte, length uintptr) { + //nolint:staticcheck + slicePtr := (*reflect.SliceHeader)(unsafe.Pointer(slice)) + lenPtr := (*uintptr)(unsafe.Pointer(&slicePtr.Len)) + atomic.StoreUintptr(lenPtr, length) +} + +// memoryBytesNumToPages converts the given number of bytes into the number of pages. +func memoryBytesNumToPages(bytesNum uint64) (pages uint32) { + return uint32(bytesNum >> MemoryPageSizeInBits) +} + +// hasSize returns true if Len is sufficient for byteCount at the given offset. +// +// Note: This is always fine, because memory can grow, but never shrink. +func (m *MemoryInstance) hasSize(offset uint32, byteCount uint64) bool { + return uint64(offset)+byteCount <= uint64(len(m.Buffer)) // uint64 prevents overflow on add +} + +// readUint32Le implements ReadUint32Le without using a context. This is extracted as both ints and floats are stored in +// memory as uint32le. +func (m *MemoryInstance) readUint32Le(offset uint32) (uint32, bool) { + if !m.hasSize(offset, 4) { + return 0, false + } + return binary.LittleEndian.Uint32(m.Buffer[offset : offset+4]), true +} + +// readUint64Le implements ReadUint64Le without using a context. This is extracted as both ints and floats are stored in +// memory as uint64le. +func (m *MemoryInstance) readUint64Le(offset uint32) (uint64, bool) { + if !m.hasSize(offset, 8) { + return 0, false + } + return binary.LittleEndian.Uint64(m.Buffer[offset : offset+8]), true +} + +// writeUint32Le implements WriteUint32Le without using a context. This is extracted as both ints and floats are stored +// in memory as uint32le. +func (m *MemoryInstance) writeUint32Le(offset uint32, v uint32) bool { + if !m.hasSize(offset, 4) { + return false + } + binary.LittleEndian.PutUint32(m.Buffer[offset:], v) + return true +} + +// writeUint64Le implements WriteUint64Le without using a context. This is extracted as both ints and floats are stored +// in memory as uint64le. +func (m *MemoryInstance) writeUint64Le(offset uint32, v uint64) bool { + if !m.hasSize(offset, 8) { + return false + } + binary.LittleEndian.PutUint64(m.Buffer[offset:], v) + return true +} + +// Wait32 suspends the caller until the offset is notified by a different agent. +func (m *MemoryInstance) Wait32(offset uint32, exp uint32, timeout int64, reader func(mem *MemoryInstance, offset uint32) uint32) uint64 { + w := m.getWaiters(offset) + w.mux.Lock() + + cur := reader(m, offset) + if cur != exp { + w.mux.Unlock() + return 1 + } + + return m.wait(w, timeout) +} + +// Wait64 suspends the caller until the offset is notified by a different agent. +func (m *MemoryInstance) Wait64(offset uint32, exp uint64, timeout int64, reader func(mem *MemoryInstance, offset uint32) uint64) uint64 { + w := m.getWaiters(offset) + w.mux.Lock() + + cur := reader(m, offset) + if cur != exp { + w.mux.Unlock() + return 1 + } + + return m.wait(w, timeout) +} + +func (m *MemoryInstance) wait(w *waiters, timeout int64) uint64 { + if w.l == nil { + w.l = list.New() + } + + // The specification requires a trap if the number of existing waiters + 1 == 2^32, so we add a check here. + // In practice, it is unlikely the application would ever accumulate such a large number of waiters as it + // indicates several GB of RAM used just for the list of waiters. + // https://github.com/WebAssembly/threads/blob/main/proposals/threads/Overview.md#wait + if uint64(w.l.Len()+1) == 1<<32 { + w.mux.Unlock() + panic(wasmruntime.ErrRuntimeTooManyWaiters) + } + + ready := make(chan struct{}) + elem := w.l.PushBack(ready) + w.mux.Unlock() + + if timeout < 0 { + <-ready + return 0 + } else { + select { + case <-ready: + return 0 + case <-time.After(time.Duration(timeout)): + // While we could see if the channel completed by now and ignore the timeout, similar to x/sync/semaphore, + // the Wasm spec doesn't specify this behavior, so we keep things simple by prioritizing the timeout. + w.mux.Lock() + w.l.Remove(elem) + w.mux.Unlock() + return 2 + } + } +} + +func (m *MemoryInstance) getWaiters(offset uint32) *waiters { + wAny, ok := m.waiters.Load(offset) + if !ok { + // The first time an address is waited on, simultaneous waits will cause extra allocations. + // Further operations will be loaded above, which is also the general pattern of usage with + // mutexes. + wAny, _ = m.waiters.LoadOrStore(offset, &waiters{}) + } + + return wAny.(*waiters) +} + +// Notify wakes up at most count waiters at the given offset. +func (m *MemoryInstance) Notify(offset uint32, count uint32) uint32 { + wAny, ok := m.waiters.Load(offset) + if !ok { + return 0 + } + w := wAny.(*waiters) + + w.mux.Lock() + defer w.mux.Unlock() + if w.l == nil { + return 0 + } + + res := uint32(0) + for num := w.l.Len(); num > 0 && res < count; num = w.l.Len() { + w := w.l.Remove(w.l.Front()).(chan struct{}) + close(w) + res++ + } + + return res +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/memory_definition.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/memory_definition.go new file mode 100644 index 00000000000..03d6fd30307 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/memory_definition.go @@ -0,0 +1,128 @@ +package wasm + +import ( + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/internalapi" +) + +// ImportedMemories implements the same method as documented on wazero.CompiledModule. +func (m *Module) ImportedMemories() (ret []api.MemoryDefinition) { + for i := range m.MemoryDefinitionSection { + d := &m.MemoryDefinitionSection[i] + if d.importDesc != nil { + ret = append(ret, d) + } + } + return +} + +// ExportedMemories implements the same method as documented on wazero.CompiledModule. +func (m *Module) ExportedMemories() map[string]api.MemoryDefinition { + ret := map[string]api.MemoryDefinition{} + for i := range m.MemoryDefinitionSection { + d := &m.MemoryDefinitionSection[i] + for _, e := range d.exportNames { + ret[e] = d + } + } + return ret +} + +// BuildMemoryDefinitions generates memory metadata that can be parsed from +// the module. This must be called after all validation. +// +// Note: This is exported for wazero.Runtime `CompileModule`. +func (m *Module) BuildMemoryDefinitions() { + var moduleName string + if m.NameSection != nil { + moduleName = m.NameSection.ModuleName + } + + memoryCount := m.ImportMemoryCount + if m.MemorySection != nil { + memoryCount++ + } + + if memoryCount == 0 { + return + } + + m.MemoryDefinitionSection = make([]MemoryDefinition, 0, memoryCount) + importMemIdx := Index(0) + for i := range m.ImportSection { + imp := &m.ImportSection[i] + if imp.Type != ExternTypeMemory { + continue + } + + m.MemoryDefinitionSection = append(m.MemoryDefinitionSection, MemoryDefinition{ + importDesc: &[2]string{imp.Module, imp.Name}, + index: importMemIdx, + memory: imp.DescMem, + }) + importMemIdx++ + } + + if m.MemorySection != nil { + m.MemoryDefinitionSection = append(m.MemoryDefinitionSection, MemoryDefinition{ + index: importMemIdx, + memory: m.MemorySection, + }) + } + + for i := range m.MemoryDefinitionSection { + d := &m.MemoryDefinitionSection[i] + d.moduleName = moduleName + for i := range m.ExportSection { + e := &m.ExportSection[i] + if e.Type == ExternTypeMemory && e.Index == d.index { + d.exportNames = append(d.exportNames, e.Name) + } + } + } +} + +// MemoryDefinition implements api.MemoryDefinition +type MemoryDefinition struct { + internalapi.WazeroOnlyType + moduleName string + index Index + importDesc *[2]string + exportNames []string + memory *Memory +} + +// ModuleName implements the same method as documented on api.MemoryDefinition. +func (f *MemoryDefinition) ModuleName() string { + return f.moduleName +} + +// Index implements the same method as documented on api.MemoryDefinition. +func (f *MemoryDefinition) Index() uint32 { + return f.index +} + +// Import implements the same method as documented on api.MemoryDefinition. +func (f *MemoryDefinition) Import() (moduleName, name string, isImport bool) { + if importDesc := f.importDesc; importDesc != nil { + moduleName, name, isImport = importDesc[0], importDesc[1], true + } + return +} + +// ExportNames implements the same method as documented on api.MemoryDefinition. +func (f *MemoryDefinition) ExportNames() []string { + return f.exportNames +} + +// Min implements the same method as documented on api.MemoryDefinition. +func (f *MemoryDefinition) Min() uint32 { + return f.memory.Min +} + +// Max implements the same method as documented on api.MemoryDefinition. +func (f *MemoryDefinition) Max() (max uint32, encoded bool) { + max = f.memory.Max + encoded = f.memory.IsMaxEncoded + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/module.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/module.go new file mode 100644 index 00000000000..8369ad9ed6a --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/module.go @@ -0,0 +1,1078 @@ +package wasm + +import ( + "bytes" + "crypto/sha256" + "encoding/binary" + "errors" + "fmt" + "io" + "sort" + "strings" + "sync" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental" + "github.com/tetratelabs/wazero/internal/ieee754" + "github.com/tetratelabs/wazero/internal/leb128" + "github.com/tetratelabs/wazero/internal/wasmdebug" +) + +// Module is a WebAssembly binary representation. +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#modules%E2%91%A8 +// +// Differences from the specification: +// * NameSection is the only key ("name") decoded from the SectionIDCustom. +// * ExportSection is represented as a map for lookup convenience. +// * Code.GoFunc is contains any go `func`. It may be present when Code.Body is not. +type Module struct { + // TypeSection contains the unique FunctionType of functions imported or defined in this module. + // + // Note: Currently, there is no type ambiguity in the index as WebAssembly 1.0 only defines function type. + // In the future, other types may be introduced to support CoreFeatures such as module linking. + // + // Note: In the Binary Format, this is SectionIDType. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#types%E2%91%A0%E2%91%A0 + TypeSection []FunctionType + + // ImportSection contains imported functions, tables, memories or globals required for instantiation + // (Store.Instantiate). + // + // Note: there are no unique constraints relating to the two-level namespace of Import.Module and Import.Name. + // + // Note: In the Binary Format, this is SectionIDImport. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#import-section%E2%91%A0 + ImportSection []Import + // ImportFunctionCount ImportGlobalCount ImportMemoryCount, and ImportTableCount are + // the cached import count per ExternType set during decoding. + ImportFunctionCount, + ImportGlobalCount, + ImportMemoryCount, + ImportTableCount Index + // ImportPerModule maps a module name to the list of Import to be imported from the module. + // This is used to do fast import resolution during instantiation. + ImportPerModule map[string][]*Import + + // FunctionSection contains the index in TypeSection of each function defined in this module. + // + // Note: The function Index space begins with imported functions and ends with those defined in this module. + // For example, if there are two imported functions and one defined in this module, the function Index 3 is defined + // in this module at FunctionSection[0]. + // + // Note: FunctionSection is index correlated with the CodeSection. If given the same position, e.g. 2, a function + // type is at TypeSection[FunctionSection[2]], while its locals and body are at CodeSection[2]. + // + // Note: In the Binary Format, this is SectionIDFunction. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-section%E2%91%A0 + FunctionSection []Index + + // TableSection contains each table defined in this module. + // + // Note: The table Index space begins with imported tables and ends with those defined in this module. + // For example, if there are two imported tables and one defined in this module, the table Index 3 is defined in + // this module at TableSection[0]. + // + // Note: Version 1.0 (20191205) of the WebAssembly spec allows at most one table definition per module, so the + // length of the TableSection can be zero or one, and can only be one if there is no imported table. + // + // Note: In the Binary Format, this is SectionIDTable. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#table-section%E2%91%A0 + TableSection []Table + + // MemorySection contains each memory defined in this module. + // + // Note: The memory Index space begins with imported memories and ends with those defined in this module. + // For example, if there are two imported memories and one defined in this module, the memory Index 3 is defined in + // this module at TableSection[0]. + // + // Note: Version 1.0 (20191205) of the WebAssembly spec allows at most one memory definition per module, so the + // length of the MemorySection can be zero or one, and can only be one if there is no imported memory. + // + // Note: In the Binary Format, this is SectionIDMemory. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-section%E2%91%A0 + MemorySection *Memory + + // GlobalSection contains each global defined in this module. + // + // Global indexes are offset by any imported globals because the global index begins with imports, followed by + // ones defined in this module. For example, if there are two imported globals and three defined in this module, the + // global at index 3 is defined in this module at GlobalSection[0]. + // + // Note: In the Binary Format, this is SectionIDGlobal. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-section%E2%91%A0 + GlobalSection []Global + + // ExportSection contains each export defined in this module. + // + // Note: In the Binary Format, this is SectionIDExport. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A0 + ExportSection []Export + // Exports maps a name to Export, and is convenient for fast look up of exported instances at runtime. + // Each item of this map points to an element of ExportSection. + Exports map[string]*Export + + // StartSection is the index of a function to call before returning from Store.Instantiate. + // + // Note: The index here is not the position in the FunctionSection, rather in the function index, which + // begins with imported functions. + // + // Note: In the Binary Format, this is SectionIDStart. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#start-section%E2%91%A0 + StartSection *Index + + // Note: In the Binary Format, this is SectionIDElement. + ElementSection []ElementSegment + + // CodeSection is index-correlated with FunctionSection and contains each + // function's locals and body. + // + // When present, the HostFunctionSection of the same index must be nil. + // + // Note: In the Binary Format, this is SectionIDCode. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#code-section%E2%91%A0 + CodeSection []Code + + // Note: In the Binary Format, this is SectionIDData. + DataSection []DataSegment + + // NameSection is set when the SectionIDCustom "name" was successfully decoded from the binary format. + // + // Note: This is the only SectionIDCustom defined in the WebAssembly 1.0 (20191205) Binary Format. + // Others are skipped as they are not used in wazero. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0 + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0 + NameSection *NameSection + + // CustomSections are set when the SectionIDCustom other than "name" were successfully decoded from the binary format. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0 + CustomSections []*CustomSection + + // DataCountSection is the optional section and holds the number of data segments in the data section. + // + // Note: This may exist in WebAssembly 2.0 or WebAssembly 1.0 with CoreFeatureBulkMemoryOperations. + // See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-count-section + // See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#bulk-memory-and-table-instructions + DataCountSection *uint32 + + // ID is the sha256 value of the source wasm plus the configurations which affect the runtime representation of + // Wasm binary. This is only used for caching. + ID ModuleID + + // IsHostModule true if this is the host module, false otherwise. + IsHostModule bool + + // functionDefinitionSectionInitOnce guards FunctionDefinitionSection so that it is initialized exactly once. + functionDefinitionSectionInitOnce sync.Once + + // FunctionDefinitionSection is a wazero-specific section. + FunctionDefinitionSection []FunctionDefinition + + // MemoryDefinitionSection is a wazero-specific section. + MemoryDefinitionSection []MemoryDefinition + + // DWARFLines is used to emit DWARF based stack trace. This is created from the multiple custom sections + // as described in https://yurydelendik.github.io/webassembly-dwarf/, though it is not specified in the Wasm + // specification: https://github.com/WebAssembly/debugging/issues/1 + DWARFLines *wasmdebug.DWARFLines +} + +// ModuleID represents sha256 hash value uniquely assigned to Module. +type ModuleID = [sha256.Size]byte + +// The wazero specific limitation described at RATIONALE.md. +// TL;DR; We multiply by 8 (to get offsets in bytes) and the multiplication result must be less than 32bit max +const ( + MaximumGlobals = uint32(1 << 27) + MaximumFunctionIndex = uint32(1 << 27) + MaximumTableIndex = uint32(1 << 27) +) + +// AssignModuleID calculates a sha256 checksum on `wasm` and other args, and set Module.ID to the result. +// See the doc on Module.ID on what it's used for. +func (m *Module) AssignModuleID(wasm []byte, listeners []experimental.FunctionListener, withEnsureTermination bool) { + h := sha256.New() + h.Write(wasm) + // Use the pre-allocated space backed by m.ID below. + + // Write the existence of listeners to the checksum per function. + for i, l := range listeners { + binary.LittleEndian.PutUint32(m.ID[:], uint32(i)) + m.ID[4] = boolToByte(l != nil) + h.Write(m.ID[:5]) + } + // Write the flag of ensureTermination to the checksum. + m.ID[0] = boolToByte(withEnsureTermination) + h.Write(m.ID[:1]) + // Get checksum by passing the slice underlying m.ID. + h.Sum(m.ID[:0]) +} + +func boolToByte(b bool) (ret byte) { + if b { + ret = 1 + } + return +} + +// typeOfFunction returns the wasm.FunctionType for the given function space index or nil. +func (m *Module) typeOfFunction(funcIdx Index) *FunctionType { + typeSectionLength, importedFunctionCount := uint32(len(m.TypeSection)), m.ImportFunctionCount + if funcIdx < importedFunctionCount { + // Imports are not exclusively functions. This is the current function index in the loop. + cur := Index(0) + for i := range m.ImportSection { + imp := &m.ImportSection[i] + if imp.Type != ExternTypeFunc { + continue + } + if funcIdx == cur { + if imp.DescFunc >= typeSectionLength { + return nil + } + return &m.TypeSection[imp.DescFunc] + } + cur++ + } + } + + funcSectionIdx := funcIdx - m.ImportFunctionCount + if funcSectionIdx >= uint32(len(m.FunctionSection)) { + return nil + } + typeIdx := m.FunctionSection[funcSectionIdx] + if typeIdx >= typeSectionLength { + return nil + } + return &m.TypeSection[typeIdx] +} + +func (m *Module) Validate(enabledFeatures api.CoreFeatures) error { + for i := range m.TypeSection { + tp := &m.TypeSection[i] + tp.CacheNumInUint64() + } + + if err := m.validateStartSection(); err != nil { + return err + } + + functions, globals, memory, tables, err := m.AllDeclarations() + if err != nil { + return err + } + + if err = m.validateImports(enabledFeatures); err != nil { + return err + } + + if err = m.validateGlobals(globals, uint32(len(functions)), MaximumGlobals); err != nil { + return err + } + + if err = m.validateMemory(memory, globals, enabledFeatures); err != nil { + return err + } + + if err = m.validateExports(enabledFeatures, functions, globals, memory, tables); err != nil { + return err + } + + if m.CodeSection != nil { + if err = m.validateFunctions(enabledFeatures, functions, globals, memory, tables, MaximumFunctionIndex); err != nil { + return err + } + } // No need to validate host functions as NewHostModule validates + + if err = m.validateTable(enabledFeatures, tables, MaximumTableIndex); err != nil { + return err + } + + if err = m.validateDataCountSection(); err != nil { + return err + } + return nil +} + +func (m *Module) validateStartSection() error { + // Check the start function is valid. + // TODO: this should be verified during decode so that errors have the correct source positions + if m.StartSection != nil { + startIndex := *m.StartSection + ft := m.typeOfFunction(startIndex) + if ft == nil { // TODO: move this check to decoder so that a module can never be decoded invalidly + return fmt.Errorf("invalid start function: func[%d] has an invalid type", startIndex) + } + if len(ft.Params) > 0 || len(ft.Results) > 0 { + return fmt.Errorf("invalid start function: func[%d] must have an empty (nullary) signature: %s", startIndex, ft) + } + } + return nil +} + +func (m *Module) validateGlobals(globals []GlobalType, numFuncts, maxGlobals uint32) error { + if uint32(len(globals)) > maxGlobals { + return fmt.Errorf("too many globals in a module") + } + + // Global initialization constant expression can only reference the imported globals. + // See the note on https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#constant-expressions%E2%91%A0 + importedGlobals := globals[:m.ImportGlobalCount] + for i := range m.GlobalSection { + g := &m.GlobalSection[i] + if err := validateConstExpression(importedGlobals, numFuncts, &g.Init, g.Type.ValType); err != nil { + return err + } + } + return nil +} + +func (m *Module) validateFunctions(enabledFeatures api.CoreFeatures, functions []Index, globals []GlobalType, memory *Memory, tables []Table, maximumFunctionIndex uint32) error { + if uint32(len(functions)) > maximumFunctionIndex { + return fmt.Errorf("too many functions (%d) in a module", len(functions)) + } + + functionCount := m.SectionElementCount(SectionIDFunction) + codeCount := m.SectionElementCount(SectionIDCode) + if functionCount == 0 && codeCount == 0 { + return nil + } + + typeCount := m.SectionElementCount(SectionIDType) + if codeCount != functionCount { + return fmt.Errorf("code count (%d) != function count (%d)", codeCount, functionCount) + } + + declaredFuncIndexes, err := m.declaredFunctionIndexes() + if err != nil { + return err + } + + // Create bytes.Reader once as it causes allocation, and + // we frequently need it (e.g. on every If instruction). + br := bytes.NewReader(nil) + // Also, we reuse the stacks across multiple function validations to reduce allocations. + vs := &stacks{} + for idx, typeIndex := range m.FunctionSection { + if typeIndex >= typeCount { + return fmt.Errorf("invalid %s: type section index %d out of range", m.funcDesc(SectionIDFunction, Index(idx)), typeIndex) + } + c := &m.CodeSection[idx] + if c.GoFunc != nil { + continue + } + if err = m.validateFunction(vs, enabledFeatures, Index(idx), functions, globals, memory, tables, declaredFuncIndexes, br); err != nil { + return fmt.Errorf("invalid %s: %w", m.funcDesc(SectionIDFunction, Index(idx)), err) + } + } + return nil +} + +// declaredFunctionIndexes returns a set of function indexes that can be used as an immediate for OpcodeRefFunc instruction. +// +// The criteria for which function indexes can be available for that instruction is vague in the spec: +// +// - "References: the list of function indices that occur in the module outside functions and can hence be used to form references inside them." +// - https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/valid/conventions.html#contexts +// - "Ref is the set funcidx(module with functions=ε, start=ε) , i.e., the set of function indices occurring in the module, except in its functions or start function." +// - https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/valid/modules.html#valid-module +// +// To clarify, we reverse-engineer logic required to pass the WebAssembly Core specification 2.0 test suite: +// https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/ref_func.wast#L78-L115 +// +// To summarize, the function indexes OpcodeRefFunc can refer include: +// - existing in an element section regardless of its mode (active, passive, declarative). +// - defined as globals whose value type is ValueRefFunc. +// - used as an exported function. +// +// See https://github.com/WebAssembly/reference-types/issues/31 +// See https://github.com/WebAssembly/reference-types/issues/76 +func (m *Module) declaredFunctionIndexes() (ret map[Index]struct{}, err error) { + ret = map[uint32]struct{}{} + + for i := range m.ExportSection { + exp := &m.ExportSection[i] + if exp.Type == ExternTypeFunc { + ret[exp.Index] = struct{}{} + } + } + + for i := range m.GlobalSection { + g := &m.GlobalSection[i] + if g.Init.Opcode == OpcodeRefFunc { + var index uint32 + index, _, err = leb128.LoadUint32(g.Init.Data) + if err != nil { + err = fmt.Errorf("%s[%d] failed to initialize: %w", SectionIDName(SectionIDGlobal), i, err) + return + } + ret[index] = struct{}{} + } + } + + for i := range m.ElementSection { + elem := &m.ElementSection[i] + for _, index := range elem.Init { + if index != ElementInitNullReference { + ret[index] = struct{}{} + } + } + } + return +} + +func (m *Module) funcDesc(sectionID SectionID, sectionIndex Index) string { + // Try to improve the error message by collecting any exports: + var exportNames []string + funcIdx := sectionIndex + m.ImportFunctionCount + for i := range m.ExportSection { + exp := &m.ExportSection[i] + if exp.Index == funcIdx && exp.Type == ExternTypeFunc { + exportNames = append(exportNames, fmt.Sprintf("%q", exp.Name)) + } + } + sectionIDName := SectionIDName(sectionID) + if exportNames == nil { + return fmt.Sprintf("%s[%d]", sectionIDName, sectionIndex) + } + sort.Strings(exportNames) // go map keys do not iterate consistently + return fmt.Sprintf("%s[%d] export[%s]", sectionIDName, sectionIndex, strings.Join(exportNames, ",")) +} + +func (m *Module) validateMemory(memory *Memory, globals []GlobalType, _ api.CoreFeatures) error { + var activeElementCount int + for i := range m.DataSection { + d := &m.DataSection[i] + if !d.IsPassive() { + activeElementCount++ + } + } + if activeElementCount > 0 && memory == nil { + return fmt.Errorf("unknown memory") + } + + // Constant expression can only reference imported globals. + // https://github.com/WebAssembly/spec/blob/5900d839f38641989a9d8df2df4aee0513365d39/test/core/data.wast#L84-L91 + importedGlobals := globals[:m.ImportGlobalCount] + for i := range m.DataSection { + d := &m.DataSection[i] + if !d.IsPassive() { + if err := validateConstExpression(importedGlobals, 0, &d.OffsetExpression, ValueTypeI32); err != nil { + return fmt.Errorf("calculate offset: %w", err) + } + } + } + return nil +} + +func (m *Module) validateImports(enabledFeatures api.CoreFeatures) error { + for i := range m.ImportSection { + imp := &m.ImportSection[i] + if imp.Module == "" { + return fmt.Errorf("import[%d] has an empty module name", i) + } + switch imp.Type { + case ExternTypeFunc: + if int(imp.DescFunc) >= len(m.TypeSection) { + return fmt.Errorf("invalid import[%q.%q] function: type index out of range", imp.Module, imp.Name) + } + case ExternTypeGlobal: + if !imp.DescGlobal.Mutable { + continue + } + if err := enabledFeatures.RequireEnabled(api.CoreFeatureMutableGlobal); err != nil { + return fmt.Errorf("invalid import[%q.%q] global: %w", imp.Module, imp.Name, err) + } + } + } + return nil +} + +func (m *Module) validateExports(enabledFeatures api.CoreFeatures, functions []Index, globals []GlobalType, memory *Memory, tables []Table) error { + for i := range m.ExportSection { + exp := &m.ExportSection[i] + index := exp.Index + switch exp.Type { + case ExternTypeFunc: + if index >= uint32(len(functions)) { + return fmt.Errorf("unknown function for export[%q]", exp.Name) + } + case ExternTypeGlobal: + if index >= uint32(len(globals)) { + return fmt.Errorf("unknown global for export[%q]", exp.Name) + } + if !globals[index].Mutable { + continue + } + if err := enabledFeatures.RequireEnabled(api.CoreFeatureMutableGlobal); err != nil { + return fmt.Errorf("invalid export[%q] global[%d]: %w", exp.Name, index, err) + } + case ExternTypeMemory: + if index > 0 || memory == nil { + return fmt.Errorf("memory for export[%q] out of range", exp.Name) + } + case ExternTypeTable: + if index >= uint32(len(tables)) { + return fmt.Errorf("table for export[%q] out of range", exp.Name) + } + } + } + return nil +} + +func validateConstExpression(globals []GlobalType, numFuncs uint32, expr *ConstantExpression, expectedType ValueType) (err error) { + var actualType ValueType + switch expr.Opcode { + case OpcodeI32Const: + // Treat constants as signed as their interpretation is not yet known per /RATIONALE.md + _, _, err = leb128.LoadInt32(expr.Data) + if err != nil { + return fmt.Errorf("read i32: %w", err) + } + actualType = ValueTypeI32 + case OpcodeI64Const: + // Treat constants as signed as their interpretation is not yet known per /RATIONALE.md + _, _, err = leb128.LoadInt64(expr.Data) + if err != nil { + return fmt.Errorf("read i64: %w", err) + } + actualType = ValueTypeI64 + case OpcodeF32Const: + _, err = ieee754.DecodeFloat32(expr.Data) + if err != nil { + return fmt.Errorf("read f32: %w", err) + } + actualType = ValueTypeF32 + case OpcodeF64Const: + _, err = ieee754.DecodeFloat64(expr.Data) + if err != nil { + return fmt.Errorf("read f64: %w", err) + } + actualType = ValueTypeF64 + case OpcodeGlobalGet: + id, _, err := leb128.LoadUint32(expr.Data) + if err != nil { + return fmt.Errorf("read index of global: %w", err) + } + if uint32(len(globals)) <= id { + return fmt.Errorf("global index out of range") + } + actualType = globals[id].ValType + case OpcodeRefNull: + if len(expr.Data) == 0 { + return fmt.Errorf("read reference type for ref.null: %w", io.ErrShortBuffer) + } + reftype := expr.Data[0] + if reftype != RefTypeFuncref && reftype != RefTypeExternref { + return fmt.Errorf("invalid type for ref.null: 0x%x", reftype) + } + actualType = reftype + case OpcodeRefFunc: + index, _, err := leb128.LoadUint32(expr.Data) + if err != nil { + return fmt.Errorf("read i32: %w", err) + } else if index >= numFuncs { + return fmt.Errorf("ref.func index out of range [%d] with length %d", index, numFuncs-1) + } + actualType = ValueTypeFuncref + case OpcodeVecV128Const: + if len(expr.Data) != 16 { + return fmt.Errorf("%s needs 16 bytes but was %d bytes", OpcodeVecV128ConstName, len(expr.Data)) + } + actualType = ValueTypeV128 + default: + return fmt.Errorf("invalid opcode for const expression: 0x%x", expr.Opcode) + } + + if actualType != expectedType { + return fmt.Errorf("const expression type mismatch expected %s but got %s", + ValueTypeName(expectedType), ValueTypeName(actualType)) + } + return nil +} + +func (m *Module) validateDataCountSection() (err error) { + if m.DataCountSection != nil && int(*m.DataCountSection) != len(m.DataSection) { + err = fmt.Errorf("data count section (%d) doesn't match the length of data section (%d)", + *m.DataCountSection, len(m.DataSection)) + } + return +} + +func (m *ModuleInstance) buildGlobals(module *Module, funcRefResolver func(funcIndex Index) Reference) { + importedGlobals := m.Globals[:module.ImportGlobalCount] + + me := m.Engine + engineOwnGlobal := me.OwnsGlobals() + for i := Index(0); i < Index(len(module.GlobalSection)); i++ { + gs := &module.GlobalSection[i] + g := &GlobalInstance{} + if engineOwnGlobal { + g.Me = me + g.Index = i + module.ImportGlobalCount + } + m.Globals[i+module.ImportGlobalCount] = g + g.Type = gs.Type + g.initialize(importedGlobals, &gs.Init, funcRefResolver) + } +} + +func paramNames(localNames IndirectNameMap, funcIdx uint32, paramLen int) []string { + for i := range localNames { + nm := &localNames[i] + // Only build parameter names if we have one for each. + if nm.Index != funcIdx || len(nm.NameMap) < paramLen { + continue + } + + ret := make([]string, paramLen) + for j := range nm.NameMap { + p := &nm.NameMap[j] + if int(p.Index) < paramLen { + ret[p.Index] = p.Name + } + } + return ret + } + return nil +} + +func (m *ModuleInstance) buildMemory(module *Module, allocator experimental.MemoryAllocator) { + memSec := module.MemorySection + if memSec != nil { + m.MemoryInstance = NewMemoryInstance(memSec, allocator, m.Engine) + m.MemoryInstance.definition = &module.MemoryDefinitionSection[0] + } +} + +// Index is the offset in an index, not necessarily an absolute position in a Module section. This is because +// indexs are often preceded by a corresponding type in the Module.ImportSection. +// +// For example, the function index starts with any ExternTypeFunc in the Module.ImportSection followed by +// the Module.FunctionSection +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-index +type Index = uint32 + +// FunctionType is a possibly empty function signature. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-types%E2%91%A0 +type FunctionType struct { + // Params are the possibly empty sequence of value types accepted by a function with this signature. + Params []ValueType + + // Results are the possibly empty sequence of value types returned by a function with this signature. + // + // Note: In WebAssembly 1.0 (20191205), there can be at most one result. + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#result-types%E2%91%A0 + Results []ValueType + + // string is cached as it is used both for String and key + string string + + // ParamNumInUint64 is the number of uint64 values requires to represent the Wasm param type. + ParamNumInUint64 int + + // ResultsNumInUint64 is the number of uint64 values requires to represent the Wasm result type. + ResultNumInUint64 int +} + +func (f *FunctionType) CacheNumInUint64() { + if f.ParamNumInUint64 == 0 { + for _, tp := range f.Params { + f.ParamNumInUint64++ + if tp == ValueTypeV128 { + f.ParamNumInUint64++ + } + } + } + + if f.ResultNumInUint64 == 0 { + for _, tp := range f.Results { + f.ResultNumInUint64++ + if tp == ValueTypeV128 { + f.ResultNumInUint64++ + } + } + } +} + +// EqualsSignature returns true if the function type has the same parameters and results. +func (f *FunctionType) EqualsSignature(params []ValueType, results []ValueType) bool { + return bytes.Equal(f.Params, params) && bytes.Equal(f.Results, results) +} + +// key gets or generates the key for Store.typeIDs. e.g. "i32_v" for one i32 parameter and no (void) result. +func (f *FunctionType) key() string { + if f.string != "" { + return f.string + } + var ret string + for _, b := range f.Params { + ret += ValueTypeName(b) + } + if len(f.Params) == 0 { + ret += "v_" + } else { + ret += "_" + } + for _, b := range f.Results { + ret += ValueTypeName(b) + } + if len(f.Results) == 0 { + ret += "v" + } + f.string = ret + return ret +} + +// String implements fmt.Stringer. +func (f *FunctionType) String() string { + return f.key() +} + +// Import is the binary representation of an import indicated by Type +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-import +type Import struct { + Type ExternType + // Module is the possibly empty primary namespace of this import + Module string + // Module is the possibly empty secondary namespace of this import + Name string + // DescFunc is the index in Module.TypeSection when Type equals ExternTypeFunc + DescFunc Index + // DescTable is the inlined Table when Type equals ExternTypeTable + DescTable Table + // DescMem is the inlined Memory when Type equals ExternTypeMemory + DescMem *Memory + // DescGlobal is the inlined GlobalType when Type equals ExternTypeGlobal + DescGlobal GlobalType + // IndexPerType has the index of this import per ExternType. + IndexPerType Index +} + +// Memory describes the limits of pages (64KB) in a memory. +type Memory struct { + Min, Cap, Max uint32 + // IsMaxEncoded true if the Max is encoded in the original binary. + IsMaxEncoded bool + // IsShared true if the memory is shared for access from multiple agents. + IsShared bool +} + +// Validate ensures values assigned to Min, Cap and Max are within valid thresholds. +func (m *Memory) Validate(memoryLimitPages uint32) error { + min, capacity, max := m.Min, m.Cap, m.Max + + if max > memoryLimitPages { + return fmt.Errorf("max %d pages (%s) over limit of %d pages (%s)", + max, PagesToUnitOfBytes(max), memoryLimitPages, PagesToUnitOfBytes(memoryLimitPages)) + } else if min > memoryLimitPages { + return fmt.Errorf("min %d pages (%s) over limit of %d pages (%s)", + min, PagesToUnitOfBytes(min), memoryLimitPages, PagesToUnitOfBytes(memoryLimitPages)) + } else if min > max { + return fmt.Errorf("min %d pages (%s) > max %d pages (%s)", + min, PagesToUnitOfBytes(min), max, PagesToUnitOfBytes(max)) + } else if capacity < min { + return fmt.Errorf("capacity %d pages (%s) less than minimum %d pages (%s)", + capacity, PagesToUnitOfBytes(capacity), min, PagesToUnitOfBytes(min)) + } else if capacity > memoryLimitPages { + return fmt.Errorf("capacity %d pages (%s) over limit of %d pages (%s)", + capacity, PagesToUnitOfBytes(capacity), memoryLimitPages, PagesToUnitOfBytes(memoryLimitPages)) + } + return nil +} + +type GlobalType struct { + ValType ValueType + Mutable bool +} + +type Global struct { + Type GlobalType + Init ConstantExpression +} + +type ConstantExpression struct { + Opcode Opcode + Data []byte +} + +// Export is the binary representation of an export indicated by Type +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-export +type Export struct { + Type ExternType + + // Name is what the host refers to this definition as. + Name string + + // Index is the index of the definition to export, the index is by Type + // e.g. If ExternTypeFunc, this is a position in the function index. + Index Index +} + +// Code is an entry in the Module.CodeSection containing the locals and body of the function. +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-code +type Code struct { + // LocalTypes are any function-scoped variables in insertion order. + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-local + LocalTypes []ValueType + + // Body is a sequence of expressions ending in OpcodeEnd + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-expr + Body []byte + + // GoFunc is non-nil when IsHostFunction and defined in go, either + // api.GoFunction or api.GoModuleFunction. When present, LocalTypes and Body must + // be nil. + // + // Note: This has no serialization format, so is not encodable. + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#host-functions%E2%91%A2 + GoFunc interface{} + + // BodyOffsetInCodeSection is the offset of the beginning of the body in the code section. + // This is used for DWARF based stack trace where a program counter represents an offset in code section. + BodyOffsetInCodeSection uint64 +} + +type DataSegment struct { + OffsetExpression ConstantExpression + Init []byte + Passive bool +} + +// IsPassive returns true if this data segment is "passive" in the sense that memory offset and +// index is determined at runtime and used by OpcodeMemoryInitName instruction in the bulk memory +// operations proposal. +// +// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#bulk-memory-and-table-instructions +func (d *DataSegment) IsPassive() bool { + return d.Passive +} + +// NameSection represent the known custom name subsections defined in the WebAssembly Binary Format +// +// Note: This can be nil if no names were decoded for any reason including configuration. +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0 +type NameSection struct { + // ModuleName is the symbolic identifier for a module. e.g. math + // + // Note: This can be empty for any reason including configuration. + ModuleName string + + // FunctionNames is an association of a function index to its symbolic identifier. e.g. add + // + // * the key (idx) is in the function index, where module defined functions are preceded by imported ones. + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#functions%E2%91%A7 + // + // For example, assuming the below text format is the second import, you would expect FunctionNames[1] = "mul" + // (import "Math" "Mul" (func $mul (param $x f32) (param $y f32) (result f32))) + // + // Note: FunctionNames are only used for debugging. At runtime, functions are called based on raw numeric index. + // Note: This can be nil for any reason including configuration. + FunctionNames NameMap + + // LocalNames contains symbolic names for function parameters or locals that have one. + // + // Note: In the Text Format, function local names can inherit parameter + // names from their type. Here are some examples: + // * (module (import (func (param $x i32) (param i32))) (func (type 0))) = [{0, {x,0}}] + // * (module (import (func (param i32) (param $y i32))) (func (type 0) (local $z i32))) = [0, [{y,1},{z,2}]] + // * (module (func (param $x i32) (local $y i32) (local $z i32))) = [{x,0},{y,1},{z,2}] + // + // Note: LocalNames are only used for debugging. At runtime, locals are called based on raw numeric index. + // Note: This can be nil for any reason including configuration. + LocalNames IndirectNameMap + + // ResultNames is a wazero-specific mechanism to store result names. + ResultNames IndirectNameMap +} + +// CustomSection contains the name and raw data of a custom section. +type CustomSection struct { + Name string + Data []byte +} + +// NameMap associates an index with any associated names. +// +// Note: Often the index bridges multiple sections. For example, the function index starts with any +// ExternTypeFunc in the Module.ImportSection followed by the Module.FunctionSection +// +// Note: NameMap is unique by NameAssoc.Index, but NameAssoc.Name needn't be unique. +// Note: When encoding in the Binary format, this must be ordered by NameAssoc.Index +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namemap +type NameMap []NameAssoc + +type NameAssoc struct { + Index Index + Name string +} + +// IndirectNameMap associates an index with an association of names. +// +// Note: IndirectNameMap is unique by NameMapAssoc.Index, but NameMapAssoc.NameMap needn't be unique. +// Note: When encoding in the Binary format, this must be ordered by NameMapAssoc.Index +// https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-indirectnamemap +type IndirectNameMap []NameMapAssoc + +type NameMapAssoc struct { + Index Index + NameMap NameMap +} + +// AllDeclarations returns all declarations for functions, globals, memories and tables in a module including imported ones. +func (m *Module) AllDeclarations() (functions []Index, globals []GlobalType, memory *Memory, tables []Table, err error) { + for i := range m.ImportSection { + imp := &m.ImportSection[i] + switch imp.Type { + case ExternTypeFunc: + functions = append(functions, imp.DescFunc) + case ExternTypeGlobal: + globals = append(globals, imp.DescGlobal) + case ExternTypeMemory: + memory = imp.DescMem + case ExternTypeTable: + tables = append(tables, imp.DescTable) + } + } + + functions = append(functions, m.FunctionSection...) + for i := range m.GlobalSection { + g := &m.GlobalSection[i] + globals = append(globals, g.Type) + } + if m.MemorySection != nil { + if memory != nil { // shouldn't be possible due to Validate + err = errors.New("at most one table allowed in module") + return + } + memory = m.MemorySection + } + if m.TableSection != nil { + tables = append(tables, m.TableSection...) + } + return +} + +// SectionID identifies the sections of a Module in the WebAssembly 1.0 (20191205) Binary Format. +// +// Note: these are defined in the wasm package, instead of the binary package, as a key per section is needed regardless +// of format, and deferring to the binary type avoids confusion. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0 +type SectionID = byte + +const ( + // SectionIDCustom includes the standard defined NameSection and possibly others not defined in the standard. + SectionIDCustom SectionID = iota // don't add anything not in https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0 + SectionIDType + SectionIDImport + SectionIDFunction + SectionIDTable + SectionIDMemory + SectionIDGlobal + SectionIDExport + SectionIDStart + SectionIDElement + SectionIDCode + SectionIDData + + // SectionIDDataCount may exist in WebAssembly 2.0 or WebAssembly 1.0 with CoreFeatureBulkMemoryOperations enabled. + // + // See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-count-section + // See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#bulk-memory-and-table-instructions + SectionIDDataCount +) + +// SectionIDName returns the canonical name of a module section. +// https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0 +func SectionIDName(sectionID SectionID) string { + switch sectionID { + case SectionIDCustom: + return "custom" + case SectionIDType: + return "type" + case SectionIDImport: + return "import" + case SectionIDFunction: + return "function" + case SectionIDTable: + return "table" + case SectionIDMemory: + return "memory" + case SectionIDGlobal: + return "global" + case SectionIDExport: + return "export" + case SectionIDStart: + return "start" + case SectionIDElement: + return "element" + case SectionIDCode: + return "code" + case SectionIDData: + return "data" + case SectionIDDataCount: + return "data_count" + } + return "unknown" +} + +// ValueType is an alias of api.ValueType defined to simplify imports. +type ValueType = api.ValueType + +const ( + ValueTypeI32 = api.ValueTypeI32 + ValueTypeI64 = api.ValueTypeI64 + ValueTypeF32 = api.ValueTypeF32 + ValueTypeF64 = api.ValueTypeF64 + // TODO: ValueTypeV128 is not exposed in the api pkg yet. + ValueTypeV128 ValueType = 0x7b + // TODO: ValueTypeFuncref is not exposed in the api pkg yet. + ValueTypeFuncref ValueType = 0x70 + ValueTypeExternref = api.ValueTypeExternref +) + +// ValueTypeName is an alias of api.ValueTypeName defined to simplify imports. +func ValueTypeName(t ValueType) string { + if t == ValueTypeFuncref { + return "funcref" + } else if t == ValueTypeV128 { + return "v128" + } + return api.ValueTypeName(t) +} + +func isReferenceValueType(vt ValueType) bool { + return vt == ValueTypeExternref || vt == ValueTypeFuncref +} + +// ExternType is an alias of api.ExternType defined to simplify imports. +type ExternType = api.ExternType + +const ( + ExternTypeFunc = api.ExternTypeFunc + ExternTypeFuncName = api.ExternTypeFuncName + ExternTypeTable = api.ExternTypeTable + ExternTypeTableName = api.ExternTypeTableName + ExternTypeMemory = api.ExternTypeMemory + ExternTypeMemoryName = api.ExternTypeMemoryName + ExternTypeGlobal = api.ExternTypeGlobal + ExternTypeGlobalName = api.ExternTypeGlobalName +) + +// ExternTypeName is an alias of api.ExternTypeName defined to simplify imports. +func ExternTypeName(t ValueType) string { + return api.ExternTypeName(t) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/module_instance.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/module_instance.go new file mode 100644 index 00000000000..20c733e6f7f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/module_instance.go @@ -0,0 +1,251 @@ +package wasm + +import ( + "context" + "errors" + "fmt" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/sys" +) + +// FailIfClosed returns a sys.ExitError if CloseWithExitCode was called. +func (m *ModuleInstance) FailIfClosed() (err error) { + if closed := m.Closed.Load(); closed != 0 { + switch closed & exitCodeFlagMask { + case exitCodeFlagResourceClosed: + case exitCodeFlagResourceNotClosed: + // This happens when this module is closed asynchronously in CloseModuleOnCanceledOrTimeout, + // and the closure of resources have been deferred here. + _ = m.ensureResourcesClosed(context.Background()) + } + return sys.NewExitError(uint32(closed >> 32)) // Unpack the high order bits as the exit code. + } + return nil +} + +// CloseModuleOnCanceledOrTimeout take a context `ctx`, which might be a Cancel or Timeout context, +// and spawns the Goroutine to check the context is canceled ot deadline exceeded. If it reaches +// one of the conditions, it sets the appropriate exit code. +// +// Callers of this function must invoke the returned context.CancelFunc to release the spawned Goroutine. +func (m *ModuleInstance) CloseModuleOnCanceledOrTimeout(ctx context.Context) context.CancelFunc { + // Creating an empty channel in this case is a bit more efficient than + // creating a context.Context and canceling it with the same effect. We + // really just need to be notified when to stop listening to the users + // context. Closing the channel will unblock the select in the goroutine + // causing it to return an stop listening to ctx.Done(). + cancelChan := make(chan struct{}) + go m.closeModuleOnCanceledOrTimeout(ctx, cancelChan) + return func() { close(cancelChan) } +} + +// closeModuleOnCanceledOrTimeout is extracted from CloseModuleOnCanceledOrTimeout for testing. +func (m *ModuleInstance) closeModuleOnCanceledOrTimeout(ctx context.Context, cancelChan <-chan struct{}) { + select { + case <-ctx.Done(): + select { + case <-cancelChan: + // In some cases by the time this goroutine is scheduled, the caller + // has already closed both the context and the cancelChan. In this + // case go will randomize which branch of the outer select to enter + // and we don't want to close the module. + default: + // This is the same logic as CloseWithCtxErr except this calls closeWithExitCodeWithoutClosingResource + // so that we can defer the resource closure in FailIfClosed. + switch { + case errors.Is(ctx.Err(), context.Canceled): + // TODO: figure out how to report error here. + _ = m.closeWithExitCodeWithoutClosingResource(sys.ExitCodeContextCanceled) + case errors.Is(ctx.Err(), context.DeadlineExceeded): + // TODO: figure out how to report error here. + _ = m.closeWithExitCodeWithoutClosingResource(sys.ExitCodeDeadlineExceeded) + } + } + case <-cancelChan: + } +} + +// CloseWithCtxErr closes the module with an exit code based on the type of +// error reported by the context. +// +// If the context's error is unknown or nil, the module does not close. +func (m *ModuleInstance) CloseWithCtxErr(ctx context.Context) { + switch { + case errors.Is(ctx.Err(), context.Canceled): + // TODO: figure out how to report error here. + _ = m.CloseWithExitCode(ctx, sys.ExitCodeContextCanceled) + case errors.Is(ctx.Err(), context.DeadlineExceeded): + // TODO: figure out how to report error here. + _ = m.CloseWithExitCode(ctx, sys.ExitCodeDeadlineExceeded) + } +} + +// Name implements the same method as documented on api.Module +func (m *ModuleInstance) Name() string { + return m.ModuleName +} + +// String implements the same method as documented on api.Module +func (m *ModuleInstance) String() string { + return fmt.Sprintf("Module[%s]", m.Name()) +} + +// Close implements the same method as documented on api.Module. +func (m *ModuleInstance) Close(ctx context.Context) (err error) { + return m.CloseWithExitCode(ctx, 0) +} + +// CloseWithExitCode implements the same method as documented on api.Module. +func (m *ModuleInstance) CloseWithExitCode(ctx context.Context, exitCode uint32) (err error) { + if !m.setExitCode(exitCode, exitCodeFlagResourceClosed) { + return nil // not an error to have already closed + } + _ = m.s.deleteModule(m) + return m.ensureResourcesClosed(ctx) +} + +// IsClosed implements the same method as documented on api.Module. +func (m *ModuleInstance) IsClosed() bool { + return m.Closed.Load() != 0 +} + +func (m *ModuleInstance) closeWithExitCodeWithoutClosingResource(exitCode uint32) (err error) { + if !m.setExitCode(exitCode, exitCodeFlagResourceNotClosed) { + return nil // not an error to have already closed + } + _ = m.s.deleteModule(m) + return nil +} + +// closeWithExitCode is the same as CloseWithExitCode besides this doesn't delete it from Store.moduleList. +func (m *ModuleInstance) closeWithExitCode(ctx context.Context, exitCode uint32) (err error) { + if !m.setExitCode(exitCode, exitCodeFlagResourceClosed) { + return nil // not an error to have already closed + } + return m.ensureResourcesClosed(ctx) +} + +type exitCodeFlag = uint64 + +const exitCodeFlagMask = 0xff + +const ( + // exitCodeFlagResourceClosed indicates that the module was closed and resources were already closed. + exitCodeFlagResourceClosed = 1 << iota + // exitCodeFlagResourceNotClosed indicates that the module was closed while resources are not closed yet. + exitCodeFlagResourceNotClosed +) + +func (m *ModuleInstance) setExitCode(exitCode uint32, flag exitCodeFlag) bool { + closed := flag | uint64(exitCode)<<32 // Store exitCode as high-order bits. + return m.Closed.CompareAndSwap(0, closed) +} + +// ensureResourcesClosed ensures that resources assigned to ModuleInstance is released. +// Only one call will happen per module, due to external atomic guards on Closed. +func (m *ModuleInstance) ensureResourcesClosed(ctx context.Context) (err error) { + if closeNotifier := m.CloseNotifier; closeNotifier != nil { // experimental + closeNotifier.CloseNotify(ctx, uint32(m.Closed.Load()>>32)) + m.CloseNotifier = nil + } + + if sysCtx := m.Sys; sysCtx != nil { // nil if from HostModuleBuilder + err = sysCtx.FS().Close() + m.Sys = nil + } + + if mem := m.MemoryInstance; mem != nil { + if mem.expBuffer != nil { + mem.expBuffer.Free() + mem.expBuffer = nil + } + } + + if m.CodeCloser != nil { + if e := m.CodeCloser.Close(ctx); err == nil { + err = e + } + m.CodeCloser = nil + } + return err +} + +// Memory implements the same method as documented on api.Module. +func (m *ModuleInstance) Memory() api.Memory { + return m.MemoryInstance +} + +// ExportedMemory implements the same method as documented on api.Module. +func (m *ModuleInstance) ExportedMemory(name string) api.Memory { + _, err := m.getExport(name, ExternTypeMemory) + if err != nil { + return nil + } + // We Assume that we have at most one memory. + return m.MemoryInstance +} + +// ExportedMemoryDefinitions implements the same method as documented on +// api.Module. +func (m *ModuleInstance) ExportedMemoryDefinitions() map[string]api.MemoryDefinition { + // Special case as we currently only support one memory. + if mem := m.MemoryInstance; mem != nil { + // Now, find out if it is exported + for name, exp := range m.Exports { + if exp.Type == ExternTypeMemory { + return map[string]api.MemoryDefinition{name: mem.definition} + } + } + } + return map[string]api.MemoryDefinition{} +} + +// ExportedFunction implements the same method as documented on api.Module. +func (m *ModuleInstance) ExportedFunction(name string) api.Function { + exp, err := m.getExport(name, ExternTypeFunc) + if err != nil { + return nil + } + return m.Engine.NewFunction(exp.Index) +} + +// ExportedFunctionDefinitions implements the same method as documented on +// api.Module. +func (m *ModuleInstance) ExportedFunctionDefinitions() map[string]api.FunctionDefinition { + result := map[string]api.FunctionDefinition{} + for name, exp := range m.Exports { + if exp.Type == ExternTypeFunc { + result[name] = m.Source.FunctionDefinition(exp.Index) + } + } + return result +} + +// GlobalVal is an internal hack to get the lower 64 bits of a global. +func (m *ModuleInstance) GlobalVal(idx Index) uint64 { + return m.Globals[idx].Val +} + +// ExportedGlobal implements the same method as documented on api.Module. +func (m *ModuleInstance) ExportedGlobal(name string) api.Global { + exp, err := m.getExport(name, ExternTypeGlobal) + if err != nil { + return nil + } + g := m.Globals[exp.Index] + if g.Type.Mutable { + return mutableGlobal{g: g} + } + return constantGlobal{g: g} +} + +// NumGlobal implements experimental.InternalModule. +func (m *ModuleInstance) NumGlobal() int { + return len(m.Globals) +} + +// Global implements experimental.InternalModule. +func (m *ModuleInstance) Global(idx int) api.Global { + return constantGlobal{g: m.Globals[idx]} +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/module_instance_lookup.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/module_instance_lookup.go new file mode 100644 index 00000000000..442d26a2243 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/module_instance_lookup.go @@ -0,0 +1,73 @@ +package wasm + +import ( + "context" + "fmt" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/internalapi" +) + +// LookupFunction looks up the table by the given index, and returns the api.Function implementation if found, +// otherwise this panics according to the same semantics as call_indirect instruction. +// Currently, this is only used by emscripten which needs to do call_indirect-like operation in the host function. +func (m *ModuleInstance) LookupFunction(t *TableInstance, typeId FunctionTypeID, tableOffset Index) api.Function { + fm, index := m.Engine.LookupFunction(t, typeId, tableOffset) + if source := fm.Source; source.IsHostModule { + // This case, the found function is a host function stored in the table. Generally, Engine.NewFunction are only + // responsible for calling Wasm-defined functions (not designed for calling Go functions!). Hence we need to wrap + // the host function as a special case. + def := &source.FunctionDefinitionSection[index] + goF := source.CodeSection[index].GoFunc + switch typed := goF.(type) { + case api.GoFunction: + // GoFunction doesn't need looked up module. + return &lookedUpGoFunction{def: def, g: goFunctionAsGoModuleFunction(typed)} + case api.GoModuleFunction: + return &lookedUpGoFunction{def: def, lookedUpModule: m, g: typed} + default: + panic(fmt.Sprintf("unexpected GoFunc type: %T", goF)) + } + } else { + return fm.Engine.NewFunction(index) + } +} + +// lookedUpGoFunction implements lookedUpGoModuleFunction. +type lookedUpGoFunction struct { + internalapi.WazeroOnly + def *FunctionDefinition + // lookedUpModule is the *ModuleInstance from which this Go function is looked up, i.e. owner of the table. + lookedUpModule *ModuleInstance + g api.GoModuleFunction +} + +// goFunctionAsGoModuleFunction converts api.GoFunction to api.GoModuleFunction which ignores the api.Module argument. +func goFunctionAsGoModuleFunction(g api.GoFunction) api.GoModuleFunction { + return api.GoModuleFunc(func(ctx context.Context, _ api.Module, stack []uint64) { + g.Call(ctx, stack) + }) +} + +// Definition implements api.Function. +func (l *lookedUpGoFunction) Definition() api.FunctionDefinition { return l.def } + +// Call implements api.Function. +func (l *lookedUpGoFunction) Call(ctx context.Context, params ...uint64) ([]uint64, error) { + typ := l.def.Functype + stackSize := typ.ParamNumInUint64 + rn := typ.ResultNumInUint64 + if rn > stackSize { + stackSize = rn + } + stack := make([]uint64, stackSize) + copy(stack, params) + return stack[:rn], l.CallWithStack(ctx, stack) +} + +// CallWithStack implements api.Function. +func (l *lookedUpGoFunction) CallWithStack(ctx context.Context, stack []uint64) error { + // The Go host function always needs to access caller's module, in this case the one holding the table. + l.g.Call(ctx, l.lookedUpModule, stack) + return nil +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/store.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/store.go new file mode 100644 index 00000000000..ee5e8678e80 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/store.go @@ -0,0 +1,679 @@ +package wasm + +import ( + "context" + "encoding/binary" + "errors" + "fmt" + "sync" + "sync/atomic" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental" + "github.com/tetratelabs/wazero/internal/expctxkeys" + "github.com/tetratelabs/wazero/internal/internalapi" + "github.com/tetratelabs/wazero/internal/leb128" + internalsys "github.com/tetratelabs/wazero/internal/sys" + "github.com/tetratelabs/wazero/sys" +) + +// nameToModuleShrinkThreshold is the size the nameToModule map can grow to +// before it starts to be monitored for shrinking. +// The capacity will never be smaller than this once the threshold is met. +const nameToModuleShrinkThreshold = 100 + +type ( + // Store is the runtime representation of "instantiated" Wasm module and objects. + // Multiple modules can be instantiated within a single store, and each instance, + // (e.g. function instance) can be referenced by other module instances in a Store via Module.ImportSection. + // + // Every type whose name ends with "Instance" suffix belongs to exactly one store. + // + // Note that store is not thread (concurrency) safe, meaning that using single Store + // via multiple goroutines might result in race conditions. In that case, the invocation + // and access to any methods and field of Store must be guarded by mutex. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#store%E2%91%A0 + Store struct { + // moduleList ensures modules are closed in reverse initialization order. + moduleList *ModuleInstance // guarded by mux + + // nameToModule holds the instantiated Wasm modules by module name from Instantiate. + // It ensures no race conditions instantiating two modules of the same name. + nameToModule map[string]*ModuleInstance // guarded by mux + + // nameToModuleCap tracks the growth of the nameToModule map in order to + // track when to shrink it. + nameToModuleCap int // guarded by mux + + // EnabledFeatures are read-only to allow optimizations. + EnabledFeatures api.CoreFeatures + + // Engine is a global context for a Store which is in responsible for compilation and execution of Wasm modules. + Engine Engine + + // typeIDs maps each FunctionType.String() to a unique FunctionTypeID. This is used at runtime to + // do type-checks on indirect function calls. + typeIDs map[string]FunctionTypeID + + // functionMaxTypes represents the limit on the number of function types in a store. + // Note: this is fixed to 2^27 but have this a field for testability. + functionMaxTypes uint32 + + // mux is used to guard the fields from concurrent access. + mux sync.RWMutex + } + + // ModuleInstance represents instantiated wasm module. + // The difference from the spec is that in wazero, a ModuleInstance holds pointers + // to the instances, rather than "addresses" (i.e. index to Store.Functions, Globals, etc) for convenience. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-moduleinst + // + // This implements api.Module. + ModuleInstance struct { + internalapi.WazeroOnlyType + + ModuleName string + Exports map[string]*Export + Globals []*GlobalInstance + MemoryInstance *MemoryInstance + Tables []*TableInstance + + // Engine implements function calls for this module. + Engine ModuleEngine + + // TypeIDs is index-correlated with types and holds typeIDs which is uniquely assigned to a type by store. + // This is necessary to achieve fast runtime type checking for indirect function calls at runtime. + TypeIDs []FunctionTypeID + + // DataInstances holds data segments bytes of the module. + // This is only used by bulk memory operations. + // + // https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/runtime.html#data-instances + DataInstances []DataInstance + + // ElementInstances holds the element instance, and each holds the references to either functions + // or external objects (unimplemented). + ElementInstances []ElementInstance + + // Sys is exposed for use in special imports such as WASI, assemblyscript. + // + // # Notes + // + // - This is a part of ModuleInstance so that scope and Close is coherent. + // - This is not exposed outside this repository (as a host function + // parameter) because we haven't thought through capabilities based + // security implications. + Sys *internalsys.Context + + // Closed is used both to guard moduleEngine.CloseWithExitCode and to store the exit code. + // + // The update value is closedType + exitCode << 32. This ensures an exit code of zero isn't mistaken for never closed. + // + // Note: Exclusively reading and updating this with atomics guarantees cross-goroutine observations. + // See /RATIONALE.md + Closed atomic.Uint64 + + // CodeCloser is non-nil when the code should be closed after this module. + CodeCloser api.Closer + + // s is the Store on which this module is instantiated. + s *Store + // prev and next hold the nodes in the linked list of ModuleInstance held by Store. + prev, next *ModuleInstance + // Source is a pointer to the Module from which this ModuleInstance derives. + Source *Module + + // CloseNotifier is an experimental hook called once on close. + CloseNotifier experimental.CloseNotifier + } + + // DataInstance holds bytes corresponding to the data segment in a module. + // + // https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/runtime.html#data-instances + DataInstance = []byte + + // GlobalInstance represents a global instance in a store. + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-instances%E2%91%A0 + GlobalInstance struct { + Type GlobalType + // Val holds a 64-bit representation of the actual value. + // If me is non-nil, the value will not be updated and the current value is stored in the module engine. + Val uint64 + // ValHi is only used for vector type globals, and holds the higher bits of the vector. + // If me is non-nil, the value will not be updated and the current value is stored in the module engine. + ValHi uint64 + // Me is the module engine that owns this global instance. + // The .Val and .ValHi fields are only valid when me is nil. + // If me is non-nil, the value is stored in the module engine. + Me ModuleEngine + Index Index + } + + // FunctionTypeID is a uniquely assigned integer for a function type. + // This is wazero specific runtime object and specific to a store, + // and used at runtime to do type-checks on indirect function calls. + FunctionTypeID uint32 +) + +// The wazero specific limitations described at RATIONALE.md. +const maximumFunctionTypes = 1 << 27 + +// GetFunctionTypeID is used by emscripten. +func (m *ModuleInstance) GetFunctionTypeID(t *FunctionType) FunctionTypeID { + id, err := m.s.GetFunctionTypeID(t) + if err != nil { + // This is not recoverable in practice since the only error GetFunctionTypeID returns is + // when there's too many function types in the store. + panic(err) + } + return id +} + +func (m *ModuleInstance) buildElementInstances(elements []ElementSegment) { + m.ElementInstances = make([][]Reference, len(elements)) + for i, elm := range elements { + if elm.Type == RefTypeFuncref && elm.Mode == ElementModePassive { + // Only passive elements can be access as element instances. + // See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/syntax/modules.html#element-segments + inits := elm.Init + inst := make([]Reference, len(inits)) + m.ElementInstances[i] = inst + for j, idx := range inits { + if index, ok := unwrapElementInitGlobalReference(idx); ok { + global := m.Globals[index] + inst[j] = Reference(global.Val) + } else { + if idx != ElementInitNullReference { + inst[j] = m.Engine.FunctionInstanceReference(idx) + } + } + } + } + } +} + +func (m *ModuleInstance) applyElements(elems []ElementSegment) { + for elemI := range elems { + elem := &elems[elemI] + if !elem.IsActive() || + // Per https://github.com/WebAssembly/spec/issues/1427 init can be no-op. + len(elem.Init) == 0 { + continue + } + var offset uint32 + if elem.OffsetExpr.Opcode == OpcodeGlobalGet { + // Ignore error as it's already validated. + globalIdx, _, _ := leb128.LoadUint32(elem.OffsetExpr.Data) + global := m.Globals[globalIdx] + offset = uint32(global.Val) + } else { + // Ignore error as it's already validated. + o, _, _ := leb128.LoadInt32(elem.OffsetExpr.Data) + offset = uint32(o) + } + + table := m.Tables[elem.TableIndex] + references := table.References + if int(offset)+len(elem.Init) > len(references) { + // ErrElementOffsetOutOfBounds is the error raised when the active element offset exceeds the table length. + // Before CoreFeatureReferenceTypes, this was checked statically before instantiation, after the proposal, + // this must be raised as runtime error (as in assert_trap in spectest), not even an instantiation error. + // https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/linking.wast#L264-L274 + // + // In wazero, we ignore it since in any way, the instantiated module and engines are fine and can be used + // for function invocations. + return + } + + if table.Type == RefTypeExternref { + for i := 0; i < len(elem.Init); i++ { + references[offset+uint32(i)] = Reference(0) + } + } else { + for i, init := range elem.Init { + if init == ElementInitNullReference { + continue + } + + var ref Reference + if index, ok := unwrapElementInitGlobalReference(init); ok { + global := m.Globals[index] + ref = Reference(global.Val) + } else { + ref = m.Engine.FunctionInstanceReference(index) + } + references[offset+uint32(i)] = ref + } + } + } +} + +// validateData ensures that data segments are valid in terms of memory boundary. +// Note: this is used only when bulk-memory/reference type feature is disabled. +func (m *ModuleInstance) validateData(data []DataSegment) (err error) { + for i := range data { + d := &data[i] + if !d.IsPassive() { + offset := int(executeConstExpressionI32(m.Globals, &d.OffsetExpression)) + ceil := offset + len(d.Init) + if offset < 0 || ceil > len(m.MemoryInstance.Buffer) { + return fmt.Errorf("%s[%d]: out of bounds memory access", SectionIDName(SectionIDData), i) + } + } + } + return +} + +// applyData uses the given data segments and mutate the memory according to the initial contents on it +// and populate the `DataInstances`. This is called after all the validation phase passes and out of +// bounds memory access error here is not a validation error, but rather a runtime error. +func (m *ModuleInstance) applyData(data []DataSegment) error { + m.DataInstances = make([][]byte, len(data)) + for i := range data { + d := &data[i] + m.DataInstances[i] = d.Init + if !d.IsPassive() { + offset := executeConstExpressionI32(m.Globals, &d.OffsetExpression) + if offset < 0 || int(offset)+len(d.Init) > len(m.MemoryInstance.Buffer) { + return fmt.Errorf("%s[%d]: out of bounds memory access", SectionIDName(SectionIDData), i) + } + copy(m.MemoryInstance.Buffer[offset:], d.Init) + } + } + return nil +} + +// GetExport returns an export of the given name and type or errs if not exported or the wrong type. +func (m *ModuleInstance) getExport(name string, et ExternType) (*Export, error) { + exp, ok := m.Exports[name] + if !ok { + return nil, fmt.Errorf("%q is not exported in module %q", name, m.ModuleName) + } + if exp.Type != et { + return nil, fmt.Errorf("export %q in module %q is a %s, not a %s", name, m.ModuleName, ExternTypeName(exp.Type), ExternTypeName(et)) + } + return exp, nil +} + +func NewStore(enabledFeatures api.CoreFeatures, engine Engine) *Store { + return &Store{ + nameToModule: map[string]*ModuleInstance{}, + nameToModuleCap: nameToModuleShrinkThreshold, + EnabledFeatures: enabledFeatures, + Engine: engine, + typeIDs: map[string]FunctionTypeID{}, + functionMaxTypes: maximumFunctionTypes, + } +} + +// Instantiate uses name instead of the Module.NameSection ModuleName as it allows instantiating the same module under +// different names safely and concurrently. +// +// * ctx: the default context used for function calls. +// * name: the name of the module. +// * sys: the system context, which will be closed (SysContext.Close) on ModuleInstance.Close. +// +// Note: Module.Validate must be called prior to instantiation. +func (s *Store) Instantiate( + ctx context.Context, + module *Module, + name string, + sys *internalsys.Context, + typeIDs []FunctionTypeID, +) (*ModuleInstance, error) { + // Instantiate the module and add it to the store so that other modules can import it. + m, err := s.instantiate(ctx, module, name, sys, typeIDs) + if err != nil { + return nil, err + } + + // Now that the instantiation is complete without error, add it. + if err = s.registerModule(m); err != nil { + _ = m.Close(ctx) + return nil, err + } + return m, nil +} + +func (s *Store) instantiate( + ctx context.Context, + module *Module, + name string, + sysCtx *internalsys.Context, + typeIDs []FunctionTypeID, +) (m *ModuleInstance, err error) { + m = &ModuleInstance{ModuleName: name, TypeIDs: typeIDs, Sys: sysCtx, s: s, Source: module} + + m.Tables = make([]*TableInstance, int(module.ImportTableCount)+len(module.TableSection)) + m.Globals = make([]*GlobalInstance, int(module.ImportGlobalCount)+len(module.GlobalSection)) + m.Engine, err = s.Engine.NewModuleEngine(module, m) + if err != nil { + return nil, err + } + + if err = m.resolveImports(ctx, module); err != nil { + return nil, err + } + + err = m.buildTables(module, + // As of reference-types proposal, boundary check must be done after instantiation. + s.EnabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes)) + if err != nil { + return nil, err + } + + allocator, _ := ctx.Value(expctxkeys.MemoryAllocatorKey{}).(experimental.MemoryAllocator) + + m.buildGlobals(module, m.Engine.FunctionInstanceReference) + m.buildMemory(module, allocator) + m.Exports = module.Exports + for _, exp := range m.Exports { + if exp.Type == ExternTypeTable { + t := m.Tables[exp.Index] + t.involvingModuleInstances = append(t.involvingModuleInstances, m) + } + } + + // As of reference types proposal, data segment validation must happen after instantiation, + // and the side effect must persist even if there's out of bounds error after instantiation. + // https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/linking.wast#L395-L405 + if !s.EnabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes) { + if err = m.validateData(module.DataSection); err != nil { + return nil, err + } + } + + // After engine creation, we can create the funcref element instances and initialize funcref type globals. + m.buildElementInstances(module.ElementSection) + + // Now all the validation passes, we are safe to mutate memory instances (possibly imported ones). + if err = m.applyData(module.DataSection); err != nil { + return nil, err + } + + m.applyElements(module.ElementSection) + + m.Engine.DoneInstantiation() + + // Execute the start function. + if module.StartSection != nil { + funcIdx := *module.StartSection + ce := m.Engine.NewFunction(funcIdx) + _, err = ce.Call(ctx) + if exitErr, ok := err.(*sys.ExitError); ok { // Don't wrap an exit error! + return nil, exitErr + } else if err != nil { + return nil, fmt.Errorf("start %s failed: %w", module.funcDesc(SectionIDFunction, funcIdx), err) + } + } + return +} + +func (m *ModuleInstance) resolveImports(ctx context.Context, module *Module) (err error) { + // Check if ctx contains an ImportResolver. + resolveImport, _ := ctx.Value(expctxkeys.ImportResolverKey{}).(experimental.ImportResolver) + + for moduleName, imports := range module.ImportPerModule { + var importedModule *ModuleInstance + if resolveImport != nil { + if v := resolveImport(moduleName); v != nil { + importedModule = v.(*ModuleInstance) + } + } + if importedModule == nil { + importedModule, err = m.s.module(moduleName) + if err != nil { + return err + } + } + + for _, i := range imports { + var imported *Export + imported, err = importedModule.getExport(i.Name, i.Type) + if err != nil { + return + } + + switch i.Type { + case ExternTypeFunc: + expectedType := &module.TypeSection[i.DescFunc] + src := importedModule.Source + actual := src.typeOfFunction(imported.Index) + if !actual.EqualsSignature(expectedType.Params, expectedType.Results) { + err = errorInvalidImport(i, fmt.Errorf("signature mismatch: %s != %s", expectedType, actual)) + return + } + + m.Engine.ResolveImportedFunction(i.IndexPerType, i.DescFunc, imported.Index, importedModule.Engine) + case ExternTypeTable: + expected := i.DescTable + importedTable := importedModule.Tables[imported.Index] + if expected.Type != importedTable.Type { + err = errorInvalidImport(i, fmt.Errorf("table type mismatch: %s != %s", + RefTypeName(expected.Type), RefTypeName(importedTable.Type))) + return + } + + if uint64(expected.Min) > uint64(len(importedTable.References)) { + err = errorMinSizeMismatch(i, expected.Min, importedTable.Min) + return + } + + if expected.Max != nil { + expectedMax := *expected.Max + if importedTable.Max == nil { + err = errorNoMax(i, expectedMax) + return + } else if expectedMax < *importedTable.Max { + err = errorMaxSizeMismatch(i, expectedMax, *importedTable.Max) + return + } + } + m.Tables[i.IndexPerType] = importedTable + importedTable.involvingModuleInstancesMutex.Lock() + if len(importedTable.involvingModuleInstances) == 0 { + panic("BUG: involvingModuleInstances must not be nil when it's imported") + } + importedTable.involvingModuleInstances = append(importedTable.involvingModuleInstances, m) + importedTable.involvingModuleInstancesMutex.Unlock() + case ExternTypeMemory: + expected := i.DescMem + importedMemory := importedModule.MemoryInstance + + if expected.Min > memoryBytesNumToPages(uint64(len(importedMemory.Buffer))) { + err = errorMinSizeMismatch(i, expected.Min, importedMemory.Min) + return + } + + if expected.Max < importedMemory.Max { + err = errorMaxSizeMismatch(i, expected.Max, importedMemory.Max) + return + } + m.MemoryInstance = importedMemory + m.Engine.ResolveImportedMemory(importedModule.Engine) + case ExternTypeGlobal: + expected := i.DescGlobal + importedGlobal := importedModule.Globals[imported.Index] + + if expected.Mutable != importedGlobal.Type.Mutable { + err = errorInvalidImport(i, fmt.Errorf("mutability mismatch: %t != %t", + expected.Mutable, importedGlobal.Type.Mutable)) + return + } + + if expected.ValType != importedGlobal.Type.ValType { + err = errorInvalidImport(i, fmt.Errorf("value type mismatch: %s != %s", + ValueTypeName(expected.ValType), ValueTypeName(importedGlobal.Type.ValType))) + return + } + m.Globals[i.IndexPerType] = importedGlobal + } + } + } + return +} + +func errorMinSizeMismatch(i *Import, expected, actual uint32) error { + return errorInvalidImport(i, fmt.Errorf("minimum size mismatch: %d > %d", expected, actual)) +} + +func errorNoMax(i *Import, expected uint32) error { + return errorInvalidImport(i, fmt.Errorf("maximum size mismatch: %d, but actual has no max", expected)) +} + +func errorMaxSizeMismatch(i *Import, expected, actual uint32) error { + return errorInvalidImport(i, fmt.Errorf("maximum size mismatch: %d < %d", expected, actual)) +} + +func errorInvalidImport(i *Import, err error) error { + return fmt.Errorf("import %s[%s.%s]: %w", ExternTypeName(i.Type), i.Module, i.Name, err) +} + +// executeConstExpressionI32 executes the ConstantExpression which returns ValueTypeI32. +// The validity of the expression is ensured when calling this function as this is only called +// during instantiation phrase, and the validation happens in compilation (validateConstExpression). +func executeConstExpressionI32(importedGlobals []*GlobalInstance, expr *ConstantExpression) (ret int32) { + switch expr.Opcode { + case OpcodeI32Const: + ret, _, _ = leb128.LoadInt32(expr.Data) + case OpcodeGlobalGet: + id, _, _ := leb128.LoadUint32(expr.Data) + g := importedGlobals[id] + ret = int32(g.Val) + } + return +} + +// initialize initializes the value of this global instance given the const expr and imported globals. +// funcRefResolver is called to get the actual funcref (engine specific) from the OpcodeRefFunc const expr. +// +// Global initialization constant expression can only reference the imported globals. +// See the note on https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#constant-expressions%E2%91%A0 +func (g *GlobalInstance) initialize(importedGlobals []*GlobalInstance, expr *ConstantExpression, funcRefResolver func(funcIndex Index) Reference) { + switch expr.Opcode { + case OpcodeI32Const: + // Treat constants as signed as their interpretation is not yet known per /RATIONALE.md + v, _, _ := leb128.LoadInt32(expr.Data) + g.Val = uint64(uint32(v)) + case OpcodeI64Const: + // Treat constants as signed as their interpretation is not yet known per /RATIONALE.md + v, _, _ := leb128.LoadInt64(expr.Data) + g.Val = uint64(v) + case OpcodeF32Const: + g.Val = uint64(binary.LittleEndian.Uint32(expr.Data)) + case OpcodeF64Const: + g.Val = binary.LittleEndian.Uint64(expr.Data) + case OpcodeGlobalGet: + id, _, _ := leb128.LoadUint32(expr.Data) + importedG := importedGlobals[id] + switch importedG.Type.ValType { + case ValueTypeI32: + g.Val = uint64(uint32(importedG.Val)) + case ValueTypeI64: + g.Val = importedG.Val + case ValueTypeF32: + g.Val = importedG.Val + case ValueTypeF64: + g.Val = importedG.Val + case ValueTypeV128: + g.Val, g.ValHi = importedG.Val, importedG.ValHi + case ValueTypeFuncref, ValueTypeExternref: + g.Val = importedG.Val + } + case OpcodeRefNull: + switch expr.Data[0] { + case ValueTypeExternref, ValueTypeFuncref: + g.Val = 0 // Reference types are opaque 64bit pointer at runtime. + } + case OpcodeRefFunc: + v, _, _ := leb128.LoadUint32(expr.Data) + g.Val = uint64(funcRefResolver(v)) + case OpcodeVecV128Const: + g.Val, g.ValHi = binary.LittleEndian.Uint64(expr.Data[0:8]), binary.LittleEndian.Uint64(expr.Data[8:16]) + } +} + +// String implements api.Global. +func (g *GlobalInstance) String() string { + switch g.Type.ValType { + case ValueTypeI32, ValueTypeI64: + return fmt.Sprintf("global(%d)", g.Val) + case ValueTypeF32: + return fmt.Sprintf("global(%f)", api.DecodeF32(g.Val)) + case ValueTypeF64: + return fmt.Sprintf("global(%f)", api.DecodeF64(g.Val)) + default: + panic(fmt.Errorf("BUG: unknown value type %X", g.Type.ValType)) + } +} + +func (g *GlobalInstance) Value() (uint64, uint64) { + if g.Me != nil { + return g.Me.GetGlobalValue(g.Index) + } + return g.Val, g.ValHi +} + +func (g *GlobalInstance) SetValue(lo, hi uint64) { + if g.Me != nil { + g.Me.SetGlobalValue(g.Index, lo, hi) + } else { + g.Val, g.ValHi = lo, hi + } +} + +func (s *Store) GetFunctionTypeIDs(ts []FunctionType) ([]FunctionTypeID, error) { + ret := make([]FunctionTypeID, len(ts)) + for i := range ts { + t := &ts[i] + inst, err := s.GetFunctionTypeID(t) + if err != nil { + return nil, err + } + ret[i] = inst + } + return ret, nil +} + +func (s *Store) GetFunctionTypeID(t *FunctionType) (FunctionTypeID, error) { + s.mux.RLock() + key := t.key() + id, ok := s.typeIDs[key] + s.mux.RUnlock() + if !ok { + s.mux.Lock() + defer s.mux.Unlock() + // Check again in case another goroutine has already added the type. + if id, ok = s.typeIDs[key]; ok { + return id, nil + } + l := len(s.typeIDs) + if uint32(l) >= s.functionMaxTypes { + return 0, fmt.Errorf("too many function types in a store") + } + id = FunctionTypeID(l) + s.typeIDs[key] = id + } + return id, nil +} + +// CloseWithExitCode implements the same method as documented on wazero.Runtime. +func (s *Store) CloseWithExitCode(ctx context.Context, exitCode uint32) error { + s.mux.Lock() + defer s.mux.Unlock() + // Close modules in reverse initialization order. + var errs []error + for m := s.moduleList; m != nil; m = m.next { + // If closing this module errs, proceed anyway to close the others. + if err := m.closeWithExitCode(ctx, exitCode); err != nil { + errs = append(errs, err) + } + } + s.moduleList = nil + s.nameToModule = nil + s.nameToModuleCap = 0 + s.typeIDs = nil + return errors.Join(errs...) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/store_module_list.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/store_module_list.go new file mode 100644 index 00000000000..ede3047deb6 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/store_module_list.go @@ -0,0 +1,95 @@ +package wasm + +import ( + "errors" + "fmt" +) + +// deleteModule makes the moduleName available for instantiation again. +func (s *Store) deleteModule(m *ModuleInstance) error { + s.mux.Lock() + defer s.mux.Unlock() + + // Remove this module name. + if m.prev != nil { + m.prev.next = m.next + } + if m.next != nil { + m.next.prev = m.prev + } + if s.moduleList == m { + s.moduleList = m.next + } + // Clear the m state so it does not enter any other branch + // on subsequent calls to deleteModule. + m.prev = nil + m.next = nil + + if m.ModuleName != "" { + delete(s.nameToModule, m.ModuleName) + + // Shrink the map if it's allocated more than twice the size of the list + newCap := len(s.nameToModule) + if newCap < nameToModuleShrinkThreshold { + newCap = nameToModuleShrinkThreshold + } + if newCap*2 <= s.nameToModuleCap { + nameToModule := make(map[string]*ModuleInstance, newCap) + for k, v := range s.nameToModule { + nameToModule[k] = v + } + s.nameToModule = nameToModule + s.nameToModuleCap = newCap + } + } + return nil +} + +// module returns the module of the given name or error if not in this store +func (s *Store) module(moduleName string) (*ModuleInstance, error) { + s.mux.RLock() + defer s.mux.RUnlock() + m, ok := s.nameToModule[moduleName] + if !ok { + return nil, fmt.Errorf("module[%s] not instantiated", moduleName) + } + return m, nil +} + +// registerModule registers a ModuleInstance into the store. +// This makes the ModuleInstance visible for import if it's not anonymous, and ensures it is closed when the store is. +func (s *Store) registerModule(m *ModuleInstance) error { + s.mux.Lock() + defer s.mux.Unlock() + + if s.nameToModule == nil { + return errors.New("already closed") + } + + if m.ModuleName != "" { + if _, ok := s.nameToModule[m.ModuleName]; ok { + return fmt.Errorf("module[%s] has already been instantiated", m.ModuleName) + } + s.nameToModule[m.ModuleName] = m + if len(s.nameToModule) > s.nameToModuleCap { + s.nameToModuleCap = len(s.nameToModule) + } + } + + // Add the newest node to the moduleNamesList as the head. + m.next = s.moduleList + if m.next != nil { + m.next.prev = m + } + s.moduleList = m + return nil +} + +// Module implements wazero.Runtime Module +func (s *Store) Module(moduleName string) *ModuleInstance { + m, err := s.module(moduleName) + if err != nil { + return nil + } + return m +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasm/table.go b/vendor/github.com/tetratelabs/wazero/internal/wasm/table.go new file mode 100644 index 00000000000..1df1764df4f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasm/table.go @@ -0,0 +1,343 @@ +package wasm + +import ( + "fmt" + "math" + "sync" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/leb128" +) + +// Table describes the limits of elements and its type in a table. +type Table struct { + Min uint32 + Max *uint32 + Type RefType +} + +// RefType is either RefTypeFuncref or RefTypeExternref as of WebAssembly core 2.0. +type RefType = byte + +const ( + // RefTypeFuncref represents a reference to a function. + RefTypeFuncref = ValueTypeFuncref + // RefTypeExternref represents a reference to a host object, which is not currently supported in wazero. + RefTypeExternref = ValueTypeExternref +) + +func RefTypeName(t RefType) (ret string) { + switch t { + case RefTypeFuncref: + ret = "funcref" + case RefTypeExternref: + ret = "externref" + default: + ret = fmt.Sprintf("unknown(0x%x)", t) + } + return +} + +// ElementMode represents a mode of element segment which is either active, passive or declarative. +// +// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/syntax/modules.html#element-segments +type ElementMode = byte + +const ( + // ElementModeActive is the mode which requires the runtime to initialize table with the contents in .Init field combined with OffsetExpr. + ElementModeActive ElementMode = iota + // ElementModePassive is the mode which doesn't require the runtime to initialize table, and only used with OpcodeTableInitName. + ElementModePassive + // ElementModeDeclarative is introduced in reference-types proposal which can be used to declare function indexes used by OpcodeRefFunc. + ElementModeDeclarative +) + +// ElementSegment are initialization instructions for a TableInstance +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-elem +type ElementSegment struct { + // OffsetExpr returns the table element offset to apply to Init indices. + // Note: This can be validated prior to instantiation unless it includes OpcodeGlobalGet (an imported global). + OffsetExpr ConstantExpression + + // TableIndex is the table's index to which this element segment is applied. + // Note: This is used if and only if the Mode is active. + TableIndex Index + + // Followings are set/used regardless of the Mode. + + // Init indices are (nullable) table elements where each index is the function index by which the module initialize the table. + Init []Index + + // Type holds the type of this element segment, which is the RefType in WebAssembly 2.0. + Type RefType + + // Mode is the mode of this element segment. + Mode ElementMode +} + +const ( + // ElementInitNullReference represents the null reference in ElementSegment's Init. + // In Wasm spec, an init item represents either Function's Index or null reference, + // and in wazero, we limit the maximum number of functions available in a module to + // MaximumFunctionIndex. Therefore, it is safe to use 1 << 31 to represent the null + // reference in Element segments. + ElementInitNullReference Index = 1 << 31 + // elementInitImportedGlobalReferenceType represents an init item which is resolved via an imported global constexpr. + // The actual function reference stored at Global is only known at instantiation-time, so we set this flag + // to items of ElementSegment.Init at binary decoding, and unwrap this flag at instantiation to resolve the value. + // + // This might collide the init element resolved via ref.func instruction which is resolved with the func index at decoding, + // but in practice, that is not allowed in wazero thanks to our limit MaximumFunctionIndex. Thus, it is safe to set this flag + // in init element to indicate as such. + elementInitImportedGlobalReferenceType Index = 1 << 30 +) + +// unwrapElementInitGlobalReference takes an item of the init vector of an ElementSegment, +// and returns the Global index if it is supposed to get generated from a global. +// ok is true if the given init item is as such. +func unwrapElementInitGlobalReference(init Index) (_ Index, ok bool) { + if init&elementInitImportedGlobalReferenceType == elementInitImportedGlobalReferenceType { + return init &^ elementInitImportedGlobalReferenceType, true + } + return init, false +} + +// WrapGlobalIndexAsElementInit wraps the given index as an init item which is resolved via an imported global value. +// See the comments on elementInitImportedGlobalReferenceType for more details. +func WrapGlobalIndexAsElementInit(init Index) Index { + return init | elementInitImportedGlobalReferenceType +} + +// IsActive returns true if the element segment is "active" mode which requires the runtime to initialize table +// with the contents in .Init field. +func (e *ElementSegment) IsActive() bool { + return e.Mode == ElementModeActive +} + +// TableInstance represents a table of (RefTypeFuncref) elements in a module. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#table-instances%E2%91%A0 +type TableInstance struct { + // References holds references whose type is either RefTypeFuncref or RefTypeExternref (unsupported). + // + // Currently, only function references are supported. + References []Reference + + // Min is the minimum (function) elements in this table and cannot grow to accommodate ElementSegment. + Min uint32 + + // Max if present is the maximum (function) elements in this table, or nil if unbounded. + Max *uint32 + + // Type is either RefTypeFuncref or RefTypeExternRef. + Type RefType + + // The following is only used when the table is exported. + + // involvingModuleInstances is a set of module instances which are involved in the table instance. + // This is critical for safety purpose because once a table is imported, it can hold any reference to + // any function in the owner and importing module instances. Therefore, these module instance, + // transitively the compiled modules, must be alive as long as the table instance is alive. + involvingModuleInstances []*ModuleInstance + // involvingModuleInstancesMutex is a mutex to protect involvingModuleInstances. + involvingModuleInstancesMutex sync.RWMutex +} + +// ElementInstance represents an element instance in a module. +// +// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/runtime.html#element-instances +type ElementInstance = []Reference + +// Reference is the runtime representation of RefType which is either RefTypeFuncref or RefTypeExternref. +type Reference = uintptr + +// validateTable ensures any ElementSegment is valid. This caches results via Module.validatedActiveElementSegments. +// Note: limitsType are validated by decoders, so not re-validated here. +func (m *Module) validateTable(enabledFeatures api.CoreFeatures, tables []Table, maximumTableIndex uint32) error { + if len(tables) > int(maximumTableIndex) { + return fmt.Errorf("too many tables in a module: %d given with limit %d", len(tables), maximumTableIndex) + } + + importedTableCount := m.ImportTableCount + + // Create bounds checks as these can err prior to instantiation + funcCount := m.ImportFunctionCount + m.SectionElementCount(SectionIDFunction) + globalsCount := m.ImportGlobalCount + m.SectionElementCount(SectionIDGlobal) + + // Now, we have to figure out which table elements can be resolved before instantiation and also fail early if there + // are any imported globals that are known to be invalid by their declarations. + for i := range m.ElementSection { + elem := &m.ElementSection[i] + idx := Index(i) + initCount := uint32(len(elem.Init)) + + // Any offset applied is to the element, not the function index: validate here if the funcidx is sound. + for ei, init := range elem.Init { + if init == ElementInitNullReference { + continue + } + index, ok := unwrapElementInitGlobalReference(init) + if ok { + if index >= globalsCount { + return fmt.Errorf("%s[%d].init[%d] global index %d out of range", SectionIDName(SectionIDElement), idx, ei, index) + } + } else { + if elem.Type == RefTypeExternref { + return fmt.Errorf("%s[%d].init[%d] must be ref.null but was %d", SectionIDName(SectionIDElement), idx, ei, init) + } + if index >= funcCount { + return fmt.Errorf("%s[%d].init[%d] func index %d out of range", SectionIDName(SectionIDElement), idx, ei, index) + } + } + } + + if elem.IsActive() { + if len(tables) <= int(elem.TableIndex) { + return fmt.Errorf("unknown table %d as active element target", elem.TableIndex) + } + + t := tables[elem.TableIndex] + if t.Type != elem.Type { + return fmt.Errorf("element type mismatch: table has %s but element has %s", + RefTypeName(t.Type), RefTypeName(elem.Type), + ) + } + + // global.get needs to be discovered during initialization + oc := elem.OffsetExpr.Opcode + if oc == OpcodeGlobalGet { + globalIdx, _, err := leb128.LoadUint32(elem.OffsetExpr.Data) + if err != nil { + return fmt.Errorf("%s[%d] couldn't read global.get parameter: %w", SectionIDName(SectionIDElement), idx, err) + } else if err = m.verifyImportGlobalI32(SectionIDElement, idx, globalIdx); err != nil { + return err + } + } else if oc == OpcodeI32Const { + // Per https://github.com/WebAssembly/spec/blob/wg-1.0/test/core/elem.wast#L117 we must pass if imported + // table has set its min=0. Per https://github.com/WebAssembly/spec/blob/wg-1.0/test/core/elem.wast#L142, we + // have to do fail if module-defined min=0. + if !enabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes) && elem.TableIndex >= importedTableCount { + // Treat constants as signed as their interpretation is not yet known per /RATIONALE.md + o, _, err := leb128.LoadInt32(elem.OffsetExpr.Data) + if err != nil { + return fmt.Errorf("%s[%d] couldn't read i32.const parameter: %w", SectionIDName(SectionIDElement), idx, err) + } + offset := Index(o) + if err = checkSegmentBounds(t.Min, uint64(initCount)+uint64(offset), idx); err != nil { + return err + } + } + } else { + return fmt.Errorf("%s[%d] has an invalid const expression: %s", SectionIDName(SectionIDElement), idx, InstructionName(oc)) + } + } + } + return nil +} + +// buildTable returns TableInstances if the module defines or imports a table. +// - importedTables: returned as `tables` unmodified. +// - importedGlobals: include all instantiated, imported globals. +// +// If the result `init` is non-nil, it is the `tableInit` parameter of Engine.NewModuleEngine. +// +// Note: An error is only possible when an ElementSegment.OffsetExpr is out of range of the TableInstance.Min. +func (m *ModuleInstance) buildTables(module *Module, skipBoundCheck bool) (err error) { + idx := module.ImportTableCount + for i := range module.TableSection { + tsec := &module.TableSection[i] + // The module defining the table is the one that sets its Min/Max etc. + m.Tables[idx] = &TableInstance{ + References: make([]Reference, tsec.Min), Min: tsec.Min, Max: tsec.Max, + Type: tsec.Type, + } + idx++ + } + + if !skipBoundCheck { + for elemI := range module.ElementSection { // Do not loop over the value since elementSegments is a slice of value. + elem := &module.ElementSection[elemI] + table := m.Tables[elem.TableIndex] + var offset uint32 + if elem.OffsetExpr.Opcode == OpcodeGlobalGet { + // Ignore error as it's already validated. + globalIdx, _, _ := leb128.LoadUint32(elem.OffsetExpr.Data) + global := m.Globals[globalIdx] + offset = uint32(global.Val) + } else { // i32.const + // Ignore error as it's already validated. + o, _, _ := leb128.LoadInt32(elem.OffsetExpr.Data) + offset = uint32(o) + } + + // Check to see if we are out-of-bounds + initCount := uint64(len(elem.Init)) + if err = checkSegmentBounds(table.Min, uint64(offset)+initCount, Index(elemI)); err != nil { + return + } + } + } + return +} + +// checkSegmentBounds fails if the capacity needed for an ElementSegment.Init is larger than limitsType.Min +// +// WebAssembly 1.0 (20191205) doesn't forbid growing to accommodate element segments, and spectests are inconsistent. +// For example, the spectests enforce elements within Table limitsType.Min, but ignore Import.DescTable min. What this +// means is we have to delay offset checks on imported tables until we link to them. +// e.g. https://github.com/WebAssembly/spec/blob/wg-1.0/test/core/elem.wast#L117 wants pass on min=0 for import +// e.g. https://github.com/WebAssembly/spec/blob/wg-1.0/test/core/elem.wast#L142 wants fail on min=0 module-defined +func checkSegmentBounds(min uint32, requireMin uint64, idx Index) error { // uint64 in case offset was set to -1 + if requireMin > uint64(min) { + return fmt.Errorf("%s[%d].init exceeds min table size", SectionIDName(SectionIDElement), idx) + } + return nil +} + +func (m *Module) verifyImportGlobalI32(sectionID SectionID, sectionIdx Index, idx uint32) error { + ig := uint32(math.MaxUint32) // +1 == 0 + for i := range m.ImportSection { + imp := &m.ImportSection[i] + if imp.Type == ExternTypeGlobal { + ig++ + if ig == idx { + if imp.DescGlobal.ValType != ValueTypeI32 { + return fmt.Errorf("%s[%d] (global.get %d): import[%d].global.ValType != i32", SectionIDName(sectionID), sectionIdx, idx, i) + } + return nil + } + } + } + return fmt.Errorf("%s[%d] (global.get %d): out of range of imported globals", SectionIDName(sectionID), sectionIdx, idx) +} + +// Grow appends the `initialRef` by `delta` times into the References slice. +// Returns -1 if the operation is not valid, otherwise the old length of the table. +// +// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/instructions.html#xref-syntax-instructions-syntax-instr-table-mathsf-table-grow-x +func (t *TableInstance) Grow(delta uint32, initialRef Reference) (currentLen uint32) { + currentLen = uint32(len(t.References)) + if delta == 0 { + return + } + + if newLen := int64(currentLen) + int64(delta); // adding as 64bit ints to avoid overflow. + newLen >= math.MaxUint32 || (t.Max != nil && newLen > int64(*t.Max)) { + return 0xffffffff // = -1 in signed 32-bit integer. + } + + t.References = append(t.References, make([]uintptr, delta)...) + if initialRef == 0 { + return + } + + // Uses the copy trick for faster filling the new region with the initial value. + // https://github.com/golang/go/blob/go1.24.0/src/slices/slices.go#L514-L517 + newRegion := t.References[currentLen:] + newRegion[0] = initialRef + for i := 1; i < len(newRegion); i *= 2 { + copy(newRegion[i:], newRegion[:i]) + } + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasmdebug/debug.go b/vendor/github.com/tetratelabs/wazero/internal/wasmdebug/debug.go new file mode 100644 index 00000000000..ff0e0cccc95 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasmdebug/debug.go @@ -0,0 +1,170 @@ +// Package wasmdebug contains utilities used to give consistent search keys between stack traces and error messages. +// Note: This is named wasmdebug to avoid conflicts with the normal go module. +// Note: This only imports "api" as importing "wasm" would create a cyclic dependency. +package wasmdebug + +import ( + "fmt" + "runtime" + "runtime/debug" + "strconv" + "strings" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/wasmruntime" + "github.com/tetratelabs/wazero/sys" +) + +// FuncName returns the naming convention of "moduleName.funcName". +// +// - moduleName is the possibly empty name the module was instantiated with. +// - funcName is the name in the Custom Name section. +// - funcIdx is the position in the function index, prefixed with +// imported functions. +// +// Note: "moduleName.$funcIdx" is used when the funcName is empty, as commonly +// the case in TinyGo. +func FuncName(moduleName, funcName string, funcIdx uint32) string { + var ret strings.Builder + + // Start module.function + ret.WriteString(moduleName) + ret.WriteByte('.') + if funcName == "" { + ret.WriteByte('$') + ret.WriteString(strconv.Itoa(int(funcIdx))) + } else { + ret.WriteString(funcName) + } + + return ret.String() +} + +// signature returns a formatted signature similar to how it is defined in Go. +// +// * paramTypes should be from wasm.FunctionType +// * resultTypes should be from wasm.FunctionType +// TODO: add paramNames +func signature(funcName string, paramTypes []api.ValueType, resultTypes []api.ValueType) string { + var ret strings.Builder + ret.WriteString(funcName) + + // Start params + ret.WriteByte('(') + paramCount := len(paramTypes) + switch paramCount { + case 0: + case 1: + ret.WriteString(api.ValueTypeName(paramTypes[0])) + default: + ret.WriteString(api.ValueTypeName(paramTypes[0])) + for _, vt := range paramTypes[1:] { + ret.WriteByte(',') + ret.WriteString(api.ValueTypeName(vt)) + } + } + ret.WriteByte(')') + + // Start results + resultCount := len(resultTypes) + switch resultCount { + case 0: + case 1: + ret.WriteByte(' ') + ret.WriteString(api.ValueTypeName(resultTypes[0])) + default: // As this is used for errors, don't panic if there are multiple returns, even if that's invalid! + ret.WriteByte(' ') + ret.WriteByte('(') + ret.WriteString(api.ValueTypeName(resultTypes[0])) + for _, vt := range resultTypes[1:] { + ret.WriteByte(',') + ret.WriteString(api.ValueTypeName(vt)) + } + ret.WriteByte(')') + } + + return ret.String() +} + +// ErrorBuilder helps build consistent errors, particularly adding a WASM stack trace. +// +// AddFrame should be called beginning at the frame that panicked until no more frames exist. Once done, call Format. +type ErrorBuilder interface { + // AddFrame adds the next frame. + // + // * funcName should be from FuncName + // * paramTypes should be from wasm.FunctionType + // * resultTypes should be from wasm.FunctionType + // * sources is the source code information for this frame and can be empty. + // + // Note: paramTypes and resultTypes are present because signature misunderstanding, mismatch or overflow are common. + AddFrame(funcName string, paramTypes, resultTypes []api.ValueType, sources []string) + + // FromRecovered returns an error with the wasm stack trace appended to it. + FromRecovered(recovered interface{}) error +} + +func NewErrorBuilder() ErrorBuilder { + return &stackTrace{} +} + +type stackTrace struct { + // frameCount is the number of stack frame currently pushed into lines. + frameCount int + // lines contains the stack trace and possibly the inlined source code information. + lines []string +} + +// GoRuntimeErrorTracePrefix is the prefix coming before the Go runtime stack trace included in the face of runtime.Error. +// This is exported for testing purpose. +const GoRuntimeErrorTracePrefix = "Go runtime stack trace:" + +func (s *stackTrace) FromRecovered(recovered interface{}) error { + if false { + debug.PrintStack() + } + + if exitErr, ok := recovered.(*sys.ExitError); ok { // Don't wrap an exit error! + return exitErr + } + + stack := strings.Join(s.lines, "\n\t") + + // If the error was internal, don't mention it was recovered. + if wasmErr, ok := recovered.(*wasmruntime.Error); ok { + return fmt.Errorf("wasm error: %w\nwasm stack trace:\n\t%s", wasmErr, stack) + } + + // If we have a runtime.Error, something severe happened which should include the stack trace. This could be + // a nil pointer from wazero or a user-defined function from HostModuleBuilder. + if runtimeErr, ok := recovered.(runtime.Error); ok { + return fmt.Errorf("%w (recovered by wazero)\nwasm stack trace:\n\t%s\n\n%s\n%s", + runtimeErr, stack, GoRuntimeErrorTracePrefix, debug.Stack()) + } + + // At this point we expect the error was from a function defined by HostModuleBuilder that intentionally called panic. + if runtimeErr, ok := recovered.(error); ok { // e.g. panic(errors.New("whoops")) + return fmt.Errorf("%w (recovered by wazero)\nwasm stack trace:\n\t%s", runtimeErr, stack) + } else { // e.g. panic("whoops") + return fmt.Errorf("%v (recovered by wazero)\nwasm stack trace:\n\t%s", recovered, stack) + } +} + +// MaxFrames is the maximum number of frames to include in the stack trace. +const MaxFrames = 30 + +// AddFrame implements ErrorBuilder.AddFrame +func (s *stackTrace) AddFrame(funcName string, paramTypes, resultTypes []api.ValueType, sources []string) { + if s.frameCount == MaxFrames { + return + } + s.frameCount++ + sig := signature(funcName, paramTypes, resultTypes) + s.lines = append(s.lines, sig) + for _, source := range sources { + s.lines = append(s.lines, "\t"+source) + } + if s.frameCount == MaxFrames { + s.lines = append(s.lines, "... maybe followed by omitted frames") + } +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasmdebug/dwarf.go b/vendor/github.com/tetratelabs/wazero/internal/wasmdebug/dwarf.go new file mode 100644 index 00000000000..50ba7b2b3f9 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasmdebug/dwarf.go @@ -0,0 +1,225 @@ +package wasmdebug + +import ( + "debug/dwarf" + "errors" + "fmt" + "io" + "sort" + "strings" + "sync" +) + +// DWARFLines is used to retrieve source code line information from the DWARF data. +type DWARFLines struct { + // d is created by DWARF custom sections. + d *dwarf.Data + // linesPerEntry maps dwarf.Offset for dwarf.Entry to the list of lines contained by the entry. + // The value is sorted in the increasing order by the address. + linesPerEntry map[dwarf.Offset][]line + mux sync.Mutex +} + +type line struct { + addr uint64 + pos dwarf.LineReaderPos +} + +// NewDWARFLines returns DWARFLines for the given *dwarf.Data. +func NewDWARFLines(d *dwarf.Data) *DWARFLines { + if d == nil { + return nil + } + return &DWARFLines{d: d, linesPerEntry: map[dwarf.Offset][]line{}} +} + +// isTombstoneAddr returns true if the given address is invalid a.k.a tombstone address which was made no longer valid +// by linker. According to the DWARF spec[1], the value is encoded as 0xffffffff for Wasm (as 32-bit target), +// but some tools encode it either in -1, -2 [2] or 1<<32 (This might not be by tools, but by debug/dwarf package's bug). +// +// [1] https://dwarfstd.org/issues/200609.1.html +// [2] https://github.com/WebAssembly/binaryen/blob/97178d08d4a20d2a5e3a6be813fc6a7079ef86e1/src/wasm/wasm-debug.cpp#L651-L660 +// [3] https://reviews.llvm.org/D81784 +func isTombstoneAddr(addr uint64) bool { + addr32 := int32(addr) + return addr32 == -1 || addr32 == -2 || + addr32 == 0 // This covers 1 <<32. +} + +// Line returns the line information for the given instructionOffset which is an offset in +// the code section of the original Wasm binary. Returns empty string if the info is not found. +func (d *DWARFLines) Line(instructionOffset uint64) (ret []string) { + if d == nil { + return + } + + // DWARFLines is created per Wasm binary, so there's a possibility that multiple instances + // created from a same binary face runtime error at the same time, and that results in + // concurrent access to this function. + d.mux.Lock() + defer d.mux.Unlock() + + r := d.d.Reader() + + var inlinedRoutines []*dwarf.Entry + var cu *dwarf.Entry + var inlinedDone bool +entry: + for { + ent, err := r.Next() + if err != nil || ent == nil { + break + } + + // If we already found the compilation unit and relevant inlined routines, we can stop searching entries. + if cu != nil && inlinedDone { + break + } + + switch ent.Tag { + case dwarf.TagCompileUnit, dwarf.TagInlinedSubroutine: + default: + // Only CompileUnit and InlinedSubroutines are relevant. + continue + } + + // Check if the entry spans the range which contains the target instruction. + ranges, err := d.d.Ranges(ent) + if err != nil { + continue + } + for _, pcs := range ranges { + start, end := pcs[0], pcs[1] + if isTombstoneAddr(start) || isTombstoneAddr(end) { + continue + } + if start <= instructionOffset && instructionOffset < end { + switch ent.Tag { + case dwarf.TagCompileUnit: + cu = ent + case dwarf.TagInlinedSubroutine: + inlinedRoutines = append(inlinedRoutines, ent) + // Search inlined subroutines until all the children. + inlinedDone = !ent.Children + // Not that "children" in the DWARF spec is defined as the next entry to this entry. + // See "2.3 Relationship of Debugging Information Entries" in https://dwarfstd.org/doc/DWARF4.pdf + } + continue entry + } + } + } + + // If the relevant compilation unit is not found, nothing we can do with this DWARF info. + if cu == nil { + return + } + + lineReader, err := d.d.LineReader(cu) + if err != nil || lineReader == nil { + return + } + var lines []line + var ok bool + var le dwarf.LineEntry + // Get the lines inside the entry. + if lines, ok = d.linesPerEntry[cu.Offset]; !ok { + // If not found, we create the list of lines by reading all the LineEntries in the Entry. + // + // Note that the dwarf.LineEntry.SeekPC API shouldn't be used because the Go's dwarf package assumes that + // all the line entries in an Entry are sorted in increasing order which *might not* be true + // for some languages. Such order requirement is not a part of DWARF specification, + // and in fact Zig language tends to emit interleaved line information. + // + // Thus, here we read all line entries here, and sort them in the increasing order wrt addresses. + for { + pos := lineReader.Tell() + err = lineReader.Next(&le) + if errors.Is(err, io.EOF) { + break + } else if err != nil { + return + } + // TODO: Maybe we should ignore tombstone addresses by using isTombstoneAddr, + // but not sure if that would be an issue in practice. + lines = append(lines, line{addr: le.Address, pos: pos}) + } + sort.Slice(lines, func(i, j int) bool { return lines[i].addr < lines[j].addr }) + d.linesPerEntry[cu.Offset] = lines // Caches for the future inquiries for the same Entry. + } + + // Now we have the lines for this entry. We can find the corresponding source line for instructionOffset + // via binary search on the list. + n := len(lines) + index := sort.Search(n, func(i int) bool { return lines[i].addr >= instructionOffset }) + + if index == n { // This case the address is not found. See the doc sort.Search. + return + } + + ln := lines[index] + if ln.addr != instructionOffset { + // If the address doesn't match exactly, the previous entry is the one that contains the instruction. + // That can happen anytime as the DWARF spec allows it, and other tools can handle it in this way conventionally + // https://github.com/gimli-rs/addr2line/blob/3a2dbaf84551a06a429f26e9c96071bb409b371f/src/lib.rs#L236-L242 + // https://github.com/kateinoigakukun/wasminspect/blob/f29f052f1b03104da9f702508ac0c1bbc3530ae4/crates/debugger/src/dwarf/mod.rs#L453-L459 + if index-1 < 0 { + return + } + ln = lines[index-1] + } + + // Advance the line reader for the found position. + lineReader.Seek(ln.pos) + err = lineReader.Next(&le) + if err != nil { + // If we reach this block, that means there's a bug in the []line creation logic above. + panic("BUG: stored dwarf.LineReaderPos is invalid") + } + + // In the inlined case, the line info is the innermost inlined function call. + inlined := len(inlinedRoutines) != 0 + prefix := fmt.Sprintf("%#x: ", instructionOffset) + ret = append(ret, formatLine(prefix, le.File.Name, int64(le.Line), int64(le.Column), inlined)) + + if inlined { + prefix = strings.Repeat(" ", len(prefix)) + files := lineReader.Files() + // inlinedRoutines contain the inlined call information in the reverse order (children is higher than parent), + // so we traverse the reverse order and emit the inlined calls. + for i := len(inlinedRoutines) - 1; i >= 0; i-- { + inlined := inlinedRoutines[i] + fileIndex, ok := inlined.Val(dwarf.AttrCallFile).(int64) + if !ok { + return + } else if fileIndex >= int64(len(files)) { + // This in theory shouldn't happen according to the spec, but guard against ill-formed DWARF info. + return + } + fileName := files[fileIndex] + line, _ := inlined.Val(dwarf.AttrCallLine).(int64) + col, _ := inlined.Val(dwarf.AttrCallColumn).(int64) + ret = append(ret, formatLine(prefix, fileName.Name, line, col, + // Last one is the origin of the inlined function calls. + i != 0)) + } + } + return +} + +func formatLine(prefix, fileName string, line, col int64, inlined bool) string { + builder := strings.Builder{} + builder.WriteString(prefix) + builder.WriteString(fileName) + + if line != 0 { + builder.WriteString(fmt.Sprintf(":%d", line)) + if col != 0 { + builder.WriteString(fmt.Sprintf(":%d", col)) + } + } + + if inlined { + builder.WriteString(" (inlined)") + } + return builder.String() +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/wasmruntime/errors.go b/vendor/github.com/tetratelabs/wazero/internal/wasmruntime/errors.go new file mode 100644 index 00000000000..556e5de829f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/wasmruntime/errors.go @@ -0,0 +1,50 @@ +// Package wasmruntime contains internal symbols shared between modules for error handling. +// Note: This is named wasmruntime to avoid conflicts with the normal go module. +// Note: This only imports "api" as importing "wasm" would create a cyclic dependency. +package wasmruntime + +var ( + // ErrRuntimeStackOverflow indicates that there are too many function calls, + // and the Engine terminated the execution. + ErrRuntimeStackOverflow = New("stack overflow") + // ErrRuntimeInvalidConversionToInteger indicates the Wasm function tries to + // convert NaN floating point value to integers during trunc variant instructions. + ErrRuntimeInvalidConversionToInteger = New("invalid conversion to integer") + // ErrRuntimeIntegerOverflow indicates that an integer arithmetic resulted in + // overflow value. For example, when the program tried to truncate a float value + // which doesn't fit in the range of target integer. + ErrRuntimeIntegerOverflow = New("integer overflow") + // ErrRuntimeIntegerDivideByZero indicates that an integer div or rem instructions + // was executed with 0 as the divisor. + ErrRuntimeIntegerDivideByZero = New("integer divide by zero") + // ErrRuntimeUnreachable means "unreachable" instruction was executed by the program. + ErrRuntimeUnreachable = New("unreachable") + // ErrRuntimeOutOfBoundsMemoryAccess indicates that the program tried to access the + // region beyond the linear memory. + ErrRuntimeOutOfBoundsMemoryAccess = New("out of bounds memory access") + // ErrRuntimeInvalidTableAccess means either offset to the table was out of bounds of table, or + // the target element in the table was uninitialized during call_indirect instruction. + ErrRuntimeInvalidTableAccess = New("invalid table access") + // ErrRuntimeIndirectCallTypeMismatch indicates that the type check failed during call_indirect. + ErrRuntimeIndirectCallTypeMismatch = New("indirect call type mismatch") + // ErrRuntimeUnalignedAtomic indicates that an atomic operation was made with incorrect memory alignment. + ErrRuntimeUnalignedAtomic = New("unaligned atomic") + // ErrRuntimeExpectedSharedMemory indicates that an operation was made against unshared memory when not allowed. + ErrRuntimeExpectedSharedMemory = New("expected shared memory") + // ErrRuntimeTooManyWaiters indicates that atomic.wait was called with too many waiters. + ErrRuntimeTooManyWaiters = New("too many waiters") +) + +// Error is returned by a wasm.Engine during the execution of Wasm functions, and they indicate that the Wasm runtime +// state is unrecoverable. +type Error struct { + s string +} + +func New(text string) *Error { + return &Error{s: text} +} + +func (e *Error) Error() string { + return e.s +} diff --git a/vendor/github.com/tetratelabs/wazero/netlify.toml b/vendor/github.com/tetratelabs/wazero/netlify.toml new file mode 100644 index 00000000000..1ba638bfe92 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/netlify.toml @@ -0,0 +1,15 @@ +[build] + base = "site" + publish = "public" + +[build.environment] + HUGO_VERSION = "0.115.2" + +[context.production] + command = "git submodule update --init && hugo --gc --minify" + +[context.deploy-preview] + command = "git submodule update --init && hugo --gc --minify -b $DEPLOY_PRIME_URL" + +[context.branch-deploy] + command = "git submodule update --init && hugo --gc --minify -b $DEPLOY_PRIME_URL" diff --git a/vendor/github.com/tetratelabs/wazero/runtime.go b/vendor/github.com/tetratelabs/wazero/runtime.go new file mode 100644 index 00000000000..7072f3f68d2 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/runtime.go @@ -0,0 +1,399 @@ +package wazero + +import ( + "context" + "fmt" + "sync/atomic" + + "github.com/tetratelabs/wazero/api" + experimentalapi "github.com/tetratelabs/wazero/experimental" + "github.com/tetratelabs/wazero/internal/engine/interpreter" + "github.com/tetratelabs/wazero/internal/engine/wazevo" + "github.com/tetratelabs/wazero/internal/expctxkeys" + "github.com/tetratelabs/wazero/internal/platform" + internalsock "github.com/tetratelabs/wazero/internal/sock" + internalsys "github.com/tetratelabs/wazero/internal/sys" + "github.com/tetratelabs/wazero/internal/wasm" + binaryformat "github.com/tetratelabs/wazero/internal/wasm/binary" + "github.com/tetratelabs/wazero/sys" +) + +// Runtime allows embedding of WebAssembly modules. +// +// The below is an example of basic initialization: +// +// ctx := context.Background() +// r := wazero.NewRuntime(ctx) +// defer r.Close(ctx) // This closes everything this Runtime created. +// +// mod, _ := r.Instantiate(ctx, wasm) +// +// # Notes +// +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. +// - Closing this closes any CompiledModule or Module it instantiated. +type Runtime interface { + // Instantiate instantiates a module from the WebAssembly binary (%.wasm) + // with default configuration, which notably calls the "_start" function, + // if it exists. + // + // Here's an example: + // ctx := context.Background() + // r := wazero.NewRuntime(ctx) + // defer r.Close(ctx) // This closes everything this Runtime created. + // + // mod, _ := r.Instantiate(ctx, wasm) + // + // # Notes + // + // - See notes on InstantiateModule for error scenarios. + // - See InstantiateWithConfig for configuration overrides. + Instantiate(ctx context.Context, source []byte) (api.Module, error) + + // InstantiateWithConfig instantiates a module from the WebAssembly binary + // (%.wasm) or errs for reasons including exit or validation. + // + // Here's an example: + // ctx := context.Background() + // r := wazero.NewRuntime(ctx) + // defer r.Close(ctx) // This closes everything this Runtime created. + // + // mod, _ := r.InstantiateWithConfig(ctx, wasm, + // wazero.NewModuleConfig().WithName("rotate")) + // + // # Notes + // + // - See notes on InstantiateModule for error scenarios. + // - If you aren't overriding defaults, use Instantiate. + // - This is a convenience utility that chains CompileModule with + // InstantiateModule. To instantiate the same source multiple times, + // use CompileModule as InstantiateModule avoids redundant decoding + // and/or compilation. + InstantiateWithConfig(ctx context.Context, source []byte, config ModuleConfig) (api.Module, error) + + // NewHostModuleBuilder lets you create modules out of functions defined in Go. + // + // Below defines and instantiates a module named "env" with one function: + // + // ctx := context.Background() + // hello := func() { + // fmt.Fprintln(stdout, "hello!") + // } + // _, err := r.NewHostModuleBuilder("env"). + // NewFunctionBuilder().WithFunc(hello).Export("hello"). + // Instantiate(ctx, r) + // + // Note: empty `moduleName` is not allowed. + NewHostModuleBuilder(moduleName string) HostModuleBuilder + + // CompileModule decodes the WebAssembly binary (%.wasm) or errs if invalid. + // Any pre-compilation done after decoding wasm is dependent on RuntimeConfig. + // + // There are two main reasons to use CompileModule instead of Instantiate: + // - Improve performance when the same module is instantiated multiple times under different names + // - Reduce the amount of errors that can occur during InstantiateModule. + // + // # Notes + // + // - The resulting module name defaults to what was binary from the custom name section. + // - Any pre-compilation done after decoding the source is dependent on RuntimeConfig. + // + // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0 + CompileModule(ctx context.Context, binary []byte) (CompiledModule, error) + + // InstantiateModule instantiates the module or errs for reasons including + // exit or validation. + // + // Here's an example: + // mod, _ := n.InstantiateModule(ctx, compiled, wazero.NewModuleConfig(). + // WithName("prod")) + // + // # Errors + // + // While CompiledModule is pre-validated, there are a few situations which + // can cause an error: + // - The module name is already in use. + // - The module has a table element initializer that resolves to an index + // outside the Table minimum size. + // - The module has a start function, and it failed to execute. + // - The module was compiled to WASI and exited with a non-zero exit + // code, you'll receive a sys.ExitError. + // - RuntimeConfig.WithCloseOnContextDone was enabled and a context + // cancellation or deadline triggered before a start function returned. + InstantiateModule(ctx context.Context, compiled CompiledModule, config ModuleConfig) (api.Module, error) + + // CloseWithExitCode closes all the modules that have been initialized in this Runtime with the provided exit code. + // An error is returned if any module returns an error when closed. + // + // Here's an example: + // ctx := context.Background() + // r := wazero.NewRuntime(ctx) + // defer r.CloseWithExitCode(ctx, 2) // This closes everything this Runtime created. + // + // // Everything below here can be closed, but will anyway due to above. + // _, _ = wasi_snapshot_preview1.InstantiateSnapshotPreview1(ctx, r) + // mod, _ := r.Instantiate(ctx, wasm) + CloseWithExitCode(ctx context.Context, exitCode uint32) error + + // Module returns an instantiated module in this runtime or nil if there aren't any. + Module(moduleName string) api.Module + + // Closer closes all compiled code by delegating to CloseWithExitCode with an exit code of zero. + api.Closer +} + +// NewRuntime returns a runtime with a configuration assigned by NewRuntimeConfig. +func NewRuntime(ctx context.Context) Runtime { + return NewRuntimeWithConfig(ctx, NewRuntimeConfig()) +} + +// NewRuntimeWithConfig returns a runtime with the given configuration. +func NewRuntimeWithConfig(ctx context.Context, rConfig RuntimeConfig) Runtime { + config := rConfig.(*runtimeConfig) + configKind := config.engineKind + configEngine := config.newEngine + if configKind == engineKindAuto { + if platform.CompilerSupports(config.enabledFeatures) { + configKind = engineKindCompiler + } else { + configKind = engineKindInterpreter + } + } + if configEngine == nil { + if configKind == engineKindCompiler { + configEngine = wazevo.NewEngine + } else { + configEngine = interpreter.NewEngine + } + } + var engine wasm.Engine + var cacheImpl *cache + if c := config.cache; c != nil { + // If the Cache is configured, we share the engine. + cacheImpl = c.(*cache) + engine = cacheImpl.initEngine(configKind, configEngine, ctx, config.enabledFeatures) + } else { + // Otherwise, we create a new engine. + engine = configEngine(ctx, config.enabledFeatures, nil) + } + store := wasm.NewStore(config.enabledFeatures, engine) + return &runtime{ + cache: cacheImpl, + store: store, + enabledFeatures: config.enabledFeatures, + memoryLimitPages: config.memoryLimitPages, + memoryCapacityFromMax: config.memoryCapacityFromMax, + dwarfDisabled: config.dwarfDisabled, + storeCustomSections: config.storeCustomSections, + ensureTermination: config.ensureTermination, + } +} + +// runtime allows decoupling of public interfaces from internal representation. +type runtime struct { + store *wasm.Store + cache *cache + enabledFeatures api.CoreFeatures + memoryLimitPages uint32 + memoryCapacityFromMax bool + dwarfDisabled bool + storeCustomSections bool + + // closed is the pointer used both to guard moduleEngine.CloseWithExitCode and to store the exit code. + // + // The update value is 1 + exitCode << 32. This ensures an exit code of zero isn't mistaken for never closed. + // + // Note: Exclusively reading and updating this with atomics guarantees cross-goroutine observations. + // See /RATIONALE.md + closed atomic.Uint64 + + ensureTermination bool +} + +// Module implements Runtime.Module. +func (r *runtime) Module(moduleName string) api.Module { + if len(moduleName) == 0 { + return nil + } + m := r.store.Module(moduleName) + if m == nil { + return nil + } else if m.Source.IsHostModule { + return hostModuleInstance{m} + } + return m +} + +// CompileModule implements Runtime.CompileModule +func (r *runtime) CompileModule(ctx context.Context, binary []byte) (CompiledModule, error) { + if err := r.failIfClosed(); err != nil { + return nil, err + } + + internal, err := binaryformat.DecodeModule(binary, r.enabledFeatures, + r.memoryLimitPages, r.memoryCapacityFromMax, !r.dwarfDisabled, r.storeCustomSections) + if err != nil { + return nil, err + } else if err = internal.Validate(r.enabledFeatures); err != nil { + // TODO: decoders should validate before returning, as that allows + // them to err with the correct position in the wasm binary. + return nil, err + } + + // Now that the module is validated, cache the memory definitions. + // TODO: lazy initialization of memory definition. + internal.BuildMemoryDefinitions() + + c := &compiledModule{module: internal, compiledEngine: r.store.Engine} + + // typeIDs are static and compile-time known. + typeIDs, err := r.store.GetFunctionTypeIDs(internal.TypeSection) + if err != nil { + return nil, err + } + c.typeIDs = typeIDs + + listeners, err := buildFunctionListeners(ctx, internal) + if err != nil { + return nil, err + } + internal.AssignModuleID(binary, listeners, r.ensureTermination) + if err = r.store.Engine.CompileModule(ctx, internal, listeners, r.ensureTermination); err != nil { + return nil, err + } + return c, nil +} + +func buildFunctionListeners(ctx context.Context, internal *wasm.Module) ([]experimentalapi.FunctionListener, error) { + // Test to see if internal code are using an experimental feature. + fnlf := ctx.Value(expctxkeys.FunctionListenerFactoryKey{}) + if fnlf == nil { + return nil, nil + } + factory := fnlf.(experimentalapi.FunctionListenerFactory) + importCount := internal.ImportFunctionCount + listeners := make([]experimentalapi.FunctionListener, len(internal.FunctionSection)) + for i := 0; i < len(listeners); i++ { + listeners[i] = factory.NewFunctionListener(internal.FunctionDefinition(uint32(i) + importCount)) + } + return listeners, nil +} + +// failIfClosed returns an error if CloseWithExitCode was called implicitly (by Close) or explicitly. +func (r *runtime) failIfClosed() error { + if closed := r.closed.Load(); closed != 0 { + return fmt.Errorf("runtime closed with exit_code(%d)", uint32(closed>>32)) + } + return nil +} + +// Instantiate implements Runtime.Instantiate +func (r *runtime) Instantiate(ctx context.Context, binary []byte) (api.Module, error) { + return r.InstantiateWithConfig(ctx, binary, NewModuleConfig()) +} + +// InstantiateWithConfig implements Runtime.InstantiateWithConfig +func (r *runtime) InstantiateWithConfig(ctx context.Context, binary []byte, config ModuleConfig) (api.Module, error) { + if compiled, err := r.CompileModule(ctx, binary); err != nil { + return nil, err + } else { + compiled.(*compiledModule).closeWithModule = true + return r.InstantiateModule(ctx, compiled, config) + } +} + +// InstantiateModule implements Runtime.InstantiateModule. +func (r *runtime) InstantiateModule( + ctx context.Context, + compiled CompiledModule, + mConfig ModuleConfig, +) (mod api.Module, err error) { + if err = r.failIfClosed(); err != nil { + return nil, err + } + + code := compiled.(*compiledModule) + config := mConfig.(*moduleConfig) + + // Only add guest module configuration to guests. + if !code.module.IsHostModule { + if sockConfig, ok := ctx.Value(internalsock.ConfigKey{}).(*internalsock.Config); ok { + config.sockConfig = sockConfig + } + } + + var sysCtx *internalsys.Context + if sysCtx, err = config.toSysContext(); err != nil { + return nil, err + } + + name := config.name + if !config.nameSet && code.module.NameSection != nil && code.module.NameSection.ModuleName != "" { + name = code.module.NameSection.ModuleName + } + + // Instantiate the module. + mod, err = r.store.Instantiate(ctx, code.module, name, sysCtx, code.typeIDs) + if err != nil { + // If there was an error, don't leak the compiled module. + if code.closeWithModule { + _ = code.Close(ctx) // don't overwrite the error + } + return nil, err + } + + if closeNotifier, ok := ctx.Value(expctxkeys.CloseNotifierKey{}).(experimentalapi.CloseNotifier); ok { + mod.(*wasm.ModuleInstance).CloseNotifier = closeNotifier + } + + // Attach the code closer so that anything afterward closes the compiled + // code when closing the module. + if code.closeWithModule { + mod.(*wasm.ModuleInstance).CodeCloser = code + } + + // Now, invoke any start functions, failing at first error. + for _, fn := range config.startFunctions { + start := mod.ExportedFunction(fn) + if start == nil { + continue + } + if _, err = start.Call(ctx); err != nil { + _ = mod.Close(ctx) // Don't leak the module on error. + + if se, ok := err.(*sys.ExitError); ok { + if se.ExitCode() == 0 { // Don't err on success. + err = nil + } + return // Don't wrap an exit error + } + err = fmt.Errorf("module[%s] function[%s] failed: %w", name, fn, err) + return + } + } + return +} + +// Close implements api.Closer embedded in Runtime. +func (r *runtime) Close(ctx context.Context) error { + return r.CloseWithExitCode(ctx, 0) +} + +// CloseWithExitCode implements Runtime.CloseWithExitCode +// +// Note: it also marks the internal `closed` field +func (r *runtime) CloseWithExitCode(ctx context.Context, exitCode uint32) error { + closed := uint64(1) + uint64(exitCode)<<32 // Store exitCode as high-order bits. + if !r.closed.CompareAndSwap(0, closed) { + return nil + } + err := r.store.CloseWithExitCode(ctx, exitCode) + if r.cache == nil { + // Close the engine if the cache is not configured, which means that this engine is scoped in this runtime. + if errCloseEngine := r.store.Engine.Close(); errCloseEngine != nil { + return errCloseEngine + } + } + return err +} diff --git a/vendor/github.com/tetratelabs/wazero/sys/clock.go b/vendor/github.com/tetratelabs/wazero/sys/clock.go new file mode 100644 index 00000000000..1c91ce246ca --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/sys/clock.go @@ -0,0 +1,26 @@ +package sys + +// ClockResolution is a positive granularity of clock precision in +// nanoseconds. For example, if the resolution is 1us, this returns 1000. +// +// Note: Some implementations return arbitrary resolution because there's +// no perfect alternative. For example, according to the source in time.go, +// windows monotonic resolution can be 15ms. See /RATIONALE.md. +type ClockResolution uint32 + +// Walltime returns the current unix/epoch time, seconds since midnight UTC +// 1 January 1970, with a nanosecond fraction. +type Walltime func() (sec int64, nsec int32) + +// Nanotime returns nanoseconds since an arbitrary start point, used to measure +// elapsed time. This is sometimes referred to as a tick or monotonic time. +// +// Note: There are no constraints on the value return except that it +// increments. For example, -1 is a valid if the next value is >= 0. +type Nanotime func() int64 + +// Nanosleep puts the current goroutine to sleep for at least ns nanoseconds. +type Nanosleep func(ns int64) + +// Osyield yields the processor, typically to implement spin-wait loops. +type Osyield func() diff --git a/vendor/github.com/tetratelabs/wazero/sys/error.go b/vendor/github.com/tetratelabs/wazero/sys/error.go new file mode 100644 index 00000000000..c3efbad964e --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/sys/error.go @@ -0,0 +1,83 @@ +// Package sys includes constants and types used by both public and internal APIs. +package sys + +import ( + "context" + "fmt" +) + +// These two special exit codes are reserved by wazero for context Cancel and Timeout integrations. +// The assumption here is that well-behaving Wasm programs won't use these two exit codes. +const ( + // ExitCodeContextCanceled corresponds to context.Canceled and returned by ExitError.ExitCode in that case. + ExitCodeContextCanceled uint32 = 0xffffffff + // ExitCodeDeadlineExceeded corresponds to context.DeadlineExceeded and returned by ExitError.ExitCode in that case. + ExitCodeDeadlineExceeded uint32 = 0xefffffff +) + +// ExitError is returned to a caller of api.Function when api.Module CloseWithExitCode was invoked, +// or context.Context passed to api.Function Call was canceled or reached the Timeout. +// +// ExitCode zero value means success while any other value is an error. +// +// Here's an example of how to get the exit code: +// +// main := module.ExportedFunction("main") +// if err := main(ctx); err != nil { +// if exitErr, ok := err.(*sys.ExitError); ok { +// // This means your module exited with non-zero code! +// } +// --snip-- +// +// Note: While possible the reason of this was "proc_exit" from "wasi_snapshot_preview1", it could be from other host +// functions, for example an AssemblyScript's abort handler, or any arbitrary caller of CloseWithExitCode. +// +// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit and +// https://www.assemblyscript.org/concepts.html#special-imports +// +// Note: In the case of context cancellation or timeout, the api.Module from which the api.Function created is closed. +type ExitError struct { + // Note: this is a struct not a uint32 type as it was originally one and + // we don't want to break call-sites that cast into it. + exitCode uint32 +} + +var exitZero = &ExitError{} + +func NewExitError(exitCode uint32) *ExitError { + if exitCode == 0 { + return exitZero + } + return &ExitError{exitCode: exitCode} +} + +// ExitCode returns zero on success, and an arbitrary value otherwise. +func (e *ExitError) ExitCode() uint32 { + return e.exitCode +} + +// Error implements the error interface. +func (e *ExitError) Error() string { + switch e.exitCode { + case ExitCodeContextCanceled: + return fmt.Sprintf("module closed with %s", context.Canceled) + case ExitCodeDeadlineExceeded: + return fmt.Sprintf("module closed with %s", context.DeadlineExceeded) + default: + return fmt.Sprintf("module closed with exit_code(%d)", e.exitCode) + } +} + +// Is allows use via errors.Is +func (e *ExitError) Is(err error) bool { + if target, ok := err.(*ExitError); ok { + return e.exitCode == target.exitCode + } + if e.exitCode == ExitCodeContextCanceled && err == context.Canceled { + return true + } + if e.exitCode == ExitCodeDeadlineExceeded && err == context.DeadlineExceeded { + return true + } + return false +} diff --git a/vendor/github.com/tetratelabs/wazero/sys/stat.go b/vendor/github.com/tetratelabs/wazero/sys/stat.go new file mode 100644 index 00000000000..32888554532 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/sys/stat.go @@ -0,0 +1,105 @@ +package sys + +import "io/fs" + +// Inode is the file serial number, or zero if unknown. +// +// Any constant value will invalidate functions that use this for +// equivalence, such as os.SameFile (Stat_t.Ino). +// +// When zero is returned by a `readdir`, some compilers will attempt to +// get a non-zero value with `lstat`. Those using this for darwin's definition +// of `getdirentries` conflate zero `d_fileno` with a deleted file, so skip the +// entry. See /RATIONALE.md for more on this. +type Inode = uint64 + +// ^-- Inode is a type alias to consolidate documentation and aid in reference +// searches. While only Stat_t is exposed publicly at the moment, this is used +// internally for Dirent and several function return values. + +// EpochNanos is a timestamp in epoch nanoseconds, or zero if unknown. +// +// This defines epoch time the same way as Walltime, except this value is +// packed into an int64. Common conversions are detailed in the examples. +type EpochNanos = int64 + +// Stat_t is similar to syscall.Stat_t, except available on all operating +// systems, including Windows. +// +// # Notes +// +// - This is used for WebAssembly ABI emulating the POSIX `stat` system call. +// See https://pubs.opengroup.org/onlinepubs/9699919799/functions/stat.html +// - Fields here are required for WebAssembly ABI including wasip1 +// (a.k.a. wasix) and wasi-filesystem (a.k.a. wasip2). +// - This isn't the same as syscall.Stat_t because wazero supports Windows, +// which doesn't have that type. runtime.GOOS that has this already also +// have inconsistent field lengths, which complicates wasm binding. +// - Use NewStat_t to create this from an existing fs.FileInfo. +// - For portability, numeric fields are 64-bit when at least one platform +// defines it that large. +type Stat_t struct { + // Dev is the device ID of device containing the file. + Dev uint64 + + // Ino is the file serial number, or zero if not available. See Inode for + // more details including impact returning a zero value. + Ino Inode + + // Mode is the same as Mode on fs.FileInfo containing bits to identify the + // type of the file (fs.ModeType) and its permissions (fs.ModePerm). + Mode fs.FileMode + + // Nlink is the number of hard links to the file. + // + // Note: This value is platform-specific and often at least one. Linux will + // return 1+N for a directory, where BSD (like Darwin) return 2+N, which + // includes the dot entry. + Nlink uint64 + + // Size is the length in bytes for regular files. For symbolic links, this + // is length in bytes of the pathname contained in the symbolic link. + Size int64 + + // Atim is the last data access timestamp in epoch nanoseconds. + Atim EpochNanos + + // Mtim is the last data modification timestamp in epoch nanoseconds. + Mtim EpochNanos + + // Ctim is the last file status change timestamp in epoch nanoseconds. + Ctim EpochNanos +} + +// NewStat_t fills a new Stat_t from `info`, including any runtime.GOOS-specific +// details from fs.FileInfo `Sys`. When `Sys` is already a *Stat_t, it is +// returned as-is. +// +// # Notes +// +// - When already in fs.FileInfo `Sys`, Stat_t must be a pointer. +// - When runtime.GOOS is "windows" Stat_t.Ino will be zero. +// - When fs.FileInfo `Sys` is nil or unknown, some fields not in fs.FileInfo +// are defaulted: Stat_t.Atim and Stat_t.Ctim are set to `ModTime`, and +// are set to ModTime and Stat_t.Nlink is set to 1. +func NewStat_t(info fs.FileInfo) Stat_t { + // Note: Pointer, not val, for parity with Go, which sets *syscall.Stat_t + if st, ok := info.Sys().(*Stat_t); ok { + return *st + } + return statFromFileInfo(info) +} + +func defaultStatFromFileInfo(info fs.FileInfo) Stat_t { + st := Stat_t{} + st.Ino = 0 + st.Dev = 0 + st.Mode = info.Mode() + st.Nlink = 1 + st.Size = info.Size() + mtim := info.ModTime().UnixNano() // Set all times to the mod time + st.Atim = mtim + st.Mtim = mtim + st.Ctim = mtim + return st +} diff --git a/vendor/github.com/tetratelabs/wazero/sys/stat_bsd.go b/vendor/github.com/tetratelabs/wazero/sys/stat_bsd.go new file mode 100644 index 00000000000..bd392aaea19 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/sys/stat_bsd.go @@ -0,0 +1,29 @@ +//go:build darwin || freebsd || netbsd + +package sys + +import ( + "io/fs" + "syscall" +) + +const sysParseable = true + +func statFromFileInfo(info fs.FileInfo) Stat_t { + if d, ok := info.Sys().(*syscall.Stat_t); ok { + st := Stat_t{} + st.Dev = uint64(d.Dev) + st.Ino = Inode(d.Ino) + st.Mode = info.Mode() + st.Nlink = uint64(d.Nlink) + st.Size = int64(d.Size) + atime := d.Atimespec + st.Atim = EpochNanos(atime.Sec)*1e9 + EpochNanos(atime.Nsec) + mtime := d.Mtimespec + st.Mtim = EpochNanos(mtime.Sec)*1e9 + EpochNanos(mtime.Nsec) + ctime := d.Ctimespec + st.Ctim = EpochNanos(ctime.Sec)*1e9 + EpochNanos(ctime.Nsec) + return st + } + return defaultStatFromFileInfo(info) +} diff --git a/vendor/github.com/tetratelabs/wazero/sys/stat_posix.go b/vendor/github.com/tetratelabs/wazero/sys/stat_posix.go new file mode 100644 index 00000000000..0e0f47e5ab1 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/sys/stat_posix.go @@ -0,0 +1,29 @@ +//go:build linux || openbsd || dragonfly || solaris + +package sys + +import ( + "io/fs" + "syscall" +) + +const sysParseable = true + +func statFromFileInfo(info fs.FileInfo) Stat_t { + if d, ok := info.Sys().(*syscall.Stat_t); ok { + st := Stat_t{} + st.Dev = uint64(d.Dev) + st.Ino = Inode(d.Ino) + st.Mode = info.Mode() + st.Nlink = uint64(d.Nlink) + st.Size = int64(d.Size) + atime := d.Atim + st.Atim = EpochNanos(atime.Sec)*1e9 + EpochNanos(atime.Nsec) + mtime := d.Mtim + st.Mtim = EpochNanos(mtime.Sec)*1e9 + EpochNanos(mtime.Nsec) + ctime := d.Ctim + st.Ctim = EpochNanos(ctime.Sec)*1e9 + EpochNanos(ctime.Nsec) + return st + } + return defaultStatFromFileInfo(info) +} diff --git a/vendor/github.com/tetratelabs/wazero/sys/stat_unsupported.go b/vendor/github.com/tetratelabs/wazero/sys/stat_unsupported.go new file mode 100644 index 00000000000..6fb9b80961c --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/sys/stat_unsupported.go @@ -0,0 +1,14 @@ +//go:build !(linux || darwin || freebsd || netbsd || openbsd || dragonfly || solaris || windows) + +package sys + +import "io/fs" + +// sysParseable is only used here as we define "supported" as being able to +// parse `info.Sys()`. The above `go:build` constraints exclude 32-bit until +// that's requested. +const sysParseable = false + +func statFromFileInfo(info fs.FileInfo) Stat_t { + return defaultStatFromFileInfo(info) +} diff --git a/vendor/github.com/tetratelabs/wazero/sys/stat_windows.go b/vendor/github.com/tetratelabs/wazero/sys/stat_windows.go new file mode 100644 index 00000000000..91450ffdf5f --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/sys/stat_windows.go @@ -0,0 +1,24 @@ +package sys + +import ( + "io/fs" + "syscall" +) + +const sysParseable = true + +func statFromFileInfo(info fs.FileInfo) Stat_t { + if d, ok := info.Sys().(*syscall.Win32FileAttributeData); ok { + st := Stat_t{} + st.Ino = 0 // not in Win32FileAttributeData + st.Dev = 0 // not in Win32FileAttributeData + st.Mode = info.Mode() + st.Nlink = 1 // not in Win32FileAttributeData + st.Size = info.Size() + st.Atim = d.LastAccessTime.Nanoseconds() + st.Mtim = d.LastWriteTime.Nanoseconds() + st.Ctim = d.CreationTime.Nanoseconds() + return st + } + return defaultStatFromFileInfo(info) +} diff --git a/vendor/go.opentelemetry.io/proto/otlp/LICENSE b/vendor/go.opentelemetry.io/proto/otlp/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/vendor/go.opentelemetry.io/proto/otlp/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/go.opentelemetry.io/proto/otlp/common/v1/common.pb.go b/vendor/go.opentelemetry.io/proto/otlp/common/v1/common.pb.go new file mode 100644 index 00000000000..304f6476374 --- /dev/null +++ b/vendor/go.opentelemetry.io/proto/otlp/common/v1/common.pb.go @@ -0,0 +1,808 @@ +// Copyright 2019, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.21.6 +// source: opentelemetry/proto/common/v1/common.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Represents any type of attribute value. AnyValue may contain a +// primitive value such as a string or integer or it may contain an arbitrary nested +// object containing arrays, key-value lists and primitives. +type AnyValue struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The value is one of the listed fields. It is valid for all values to be unspecified + // in which case this AnyValue is considered to be "empty". + // + // Types that are assignable to Value: + // *AnyValue_StringValue + // *AnyValue_BoolValue + // *AnyValue_IntValue + // *AnyValue_DoubleValue + // *AnyValue_ArrayValue + // *AnyValue_KvlistValue + // *AnyValue_BytesValue + // *AnyValue_StringValueStrindex + Value isAnyValue_Value `protobuf_oneof:"value"` +} + +func (x *AnyValue) Reset() { + *x = AnyValue{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_common_v1_common_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AnyValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AnyValue) ProtoMessage() {} + +func (x *AnyValue) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_common_v1_common_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AnyValue.ProtoReflect.Descriptor instead. +func (*AnyValue) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_common_v1_common_proto_rawDescGZIP(), []int{0} +} + +func (m *AnyValue) GetValue() isAnyValue_Value { + if m != nil { + return m.Value + } + return nil +} + +func (x *AnyValue) GetStringValue() string { + if x, ok := x.GetValue().(*AnyValue_StringValue); ok { + return x.StringValue + } + return "" +} + +func (x *AnyValue) GetBoolValue() bool { + if x, ok := x.GetValue().(*AnyValue_BoolValue); ok { + return x.BoolValue + } + return false +} + +func (x *AnyValue) GetIntValue() int64 { + if x, ok := x.GetValue().(*AnyValue_IntValue); ok { + return x.IntValue + } + return 0 +} + +func (x *AnyValue) GetDoubleValue() float64 { + if x, ok := x.GetValue().(*AnyValue_DoubleValue); ok { + return x.DoubleValue + } + return 0 +} + +func (x *AnyValue) GetArrayValue() *ArrayValue { + if x, ok := x.GetValue().(*AnyValue_ArrayValue); ok { + return x.ArrayValue + } + return nil +} + +func (x *AnyValue) GetKvlistValue() *KeyValueList { + if x, ok := x.GetValue().(*AnyValue_KvlistValue); ok { + return x.KvlistValue + } + return nil +} + +func (x *AnyValue) GetBytesValue() []byte { + if x, ok := x.GetValue().(*AnyValue_BytesValue); ok { + return x.BytesValue + } + return nil +} + +func (x *AnyValue) GetStringValueStrindex() int32 { + if x, ok := x.GetValue().(*AnyValue_StringValueStrindex); ok { + return x.StringValueStrindex + } + return 0 +} + +type isAnyValue_Value interface { + isAnyValue_Value() +} + +type AnyValue_StringValue struct { + StringValue string `protobuf:"bytes,1,opt,name=string_value,json=stringValue,proto3,oneof"` +} + +type AnyValue_BoolValue struct { + BoolValue bool `protobuf:"varint,2,opt,name=bool_value,json=boolValue,proto3,oneof"` +} + +type AnyValue_IntValue struct { + IntValue int64 `protobuf:"varint,3,opt,name=int_value,json=intValue,proto3,oneof"` +} + +type AnyValue_DoubleValue struct { + DoubleValue float64 `protobuf:"fixed64,4,opt,name=double_value,json=doubleValue,proto3,oneof"` +} + +type AnyValue_ArrayValue struct { + ArrayValue *ArrayValue `protobuf:"bytes,5,opt,name=array_value,json=arrayValue,proto3,oneof"` +} + +type AnyValue_KvlistValue struct { + KvlistValue *KeyValueList `protobuf:"bytes,6,opt,name=kvlist_value,json=kvlistValue,proto3,oneof"` +} + +type AnyValue_BytesValue struct { + BytesValue []byte `protobuf:"bytes,7,opt,name=bytes_value,json=bytesValue,proto3,oneof"` +} + +type AnyValue_StringValueStrindex struct { + // Reference to the string value in ProfilesDictionary.string_table. + // + // Note: This is currently used exclusively in the Profiling signal. + // Implementers of OTLP receivers for signals other than Profiling should + // treat the presence of this value as a non-fatal issue. + // Log an error or warning indicating an unexpected field intended for the + // Profiling signal and process the data as if this value were absent or + // empty, ignoring its semantic content for the non-Profiling signal. + // + // Status: [Development] + StringValueStrindex int32 `protobuf:"varint,8,opt,name=string_value_strindex,json=stringValueStrindex,proto3,oneof"` +} + +func (*AnyValue_StringValue) isAnyValue_Value() {} + +func (*AnyValue_BoolValue) isAnyValue_Value() {} + +func (*AnyValue_IntValue) isAnyValue_Value() {} + +func (*AnyValue_DoubleValue) isAnyValue_Value() {} + +func (*AnyValue_ArrayValue) isAnyValue_Value() {} + +func (*AnyValue_KvlistValue) isAnyValue_Value() {} + +func (*AnyValue_BytesValue) isAnyValue_Value() {} + +func (*AnyValue_StringValueStrindex) isAnyValue_Value() {} + +// ArrayValue is a list of AnyValue messages. We need ArrayValue as a message +// since oneof in AnyValue does not allow repeated fields. +type ArrayValue struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Array of values. The array may be empty (contain 0 elements). + Values []*AnyValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` +} + +func (x *ArrayValue) Reset() { + *x = ArrayValue{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_common_v1_common_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ArrayValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ArrayValue) ProtoMessage() {} + +func (x *ArrayValue) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_common_v1_common_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ArrayValue.ProtoReflect.Descriptor instead. +func (*ArrayValue) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_common_v1_common_proto_rawDescGZIP(), []int{1} +} + +func (x *ArrayValue) GetValues() []*AnyValue { + if x != nil { + return x.Values + } + return nil +} + +// KeyValueList is a list of KeyValue messages. We need KeyValueList as a message +// since `oneof` in AnyValue does not allow repeated fields. Everywhere else where we need +// a list of KeyValue messages (e.g. in Span) we use `repeated KeyValue` directly to +// avoid unnecessary extra wrapping (which slows down the protocol). The 2 approaches +// are semantically equivalent. +type KeyValueList struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // A collection of key/value pairs of key-value pairs. The list may be empty (may + // contain 0 elements). + // + // The keys MUST be unique (it is not allowed to have more than one + // value with the same key). + // The behavior of software that receives duplicated keys can be unpredictable. + Values []*KeyValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` +} + +func (x *KeyValueList) Reset() { + *x = KeyValueList{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_common_v1_common_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *KeyValueList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*KeyValueList) ProtoMessage() {} + +func (x *KeyValueList) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_common_v1_common_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use KeyValueList.ProtoReflect.Descriptor instead. +func (*KeyValueList) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_common_v1_common_proto_rawDescGZIP(), []int{2} +} + +func (x *KeyValueList) GetValues() []*KeyValue { + if x != nil { + return x.Values + } + return nil +} + +// Represents a key-value pair that is used to store Span attributes, Link +// attributes, etc. +type KeyValue struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The key name of the pair. + // key_ref MUST NOT be set if key is used. + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // The value of the pair. + Value *AnyValue `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + // Reference to the string key in ProfilesDictionary.string_table. + // key MUST NOT be set if key_strindex is used. + // + // Note: This is currently used exclusively in the Profiling signal. + // Implementers of OTLP receivers for signals other than Profiling should + // treat the presence of this key as a non-fatal issue. + // Log an error or warning indicating an unexpected field intended for the + // Profiling signal and process the data as if this value were absent or + // empty, ignoring its semantic content for the non-Profiling signal. + // + // Status: [Development] + KeyStrindex int32 `protobuf:"varint,3,opt,name=key_strindex,json=keyStrindex,proto3" json:"key_strindex,omitempty"` +} + +func (x *KeyValue) Reset() { + *x = KeyValue{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_common_v1_common_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *KeyValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*KeyValue) ProtoMessage() {} + +func (x *KeyValue) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_common_v1_common_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use KeyValue.ProtoReflect.Descriptor instead. +func (*KeyValue) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_common_v1_common_proto_rawDescGZIP(), []int{3} +} + +func (x *KeyValue) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *KeyValue) GetValue() *AnyValue { + if x != nil { + return x.Value + } + return nil +} + +func (x *KeyValue) GetKeyStrindex() int32 { + if x != nil { + return x.KeyStrindex + } + return 0 +} + +// InstrumentationScope is a message representing the instrumentation scope information +// such as the fully qualified name and version. +type InstrumentationScope struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // A name denoting the Instrumentation scope. + // An empty instrumentation scope name means the name is unknown. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Defines the version of the instrumentation scope. + // An empty instrumentation scope version means the version is unknown. + Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` + // Additional attributes that describe the scope. [Optional]. + // Attribute keys MUST be unique (it is not allowed to have more than one + // attribute with the same key). + // The behavior of software that receives duplicated keys can be unpredictable. + Attributes []*KeyValue `protobuf:"bytes,3,rep,name=attributes,proto3" json:"attributes,omitempty"` + // The number of attributes that were discarded. Attributes + // can be discarded because their keys are too long or because there are too many + // attributes. If this value is 0, then no attributes were dropped. + DroppedAttributesCount uint32 `protobuf:"varint,4,opt,name=dropped_attributes_count,json=droppedAttributesCount,proto3" json:"dropped_attributes_count,omitempty"` +} + +func (x *InstrumentationScope) Reset() { + *x = InstrumentationScope{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_common_v1_common_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *InstrumentationScope) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InstrumentationScope) ProtoMessage() {} + +func (x *InstrumentationScope) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_common_v1_common_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InstrumentationScope.ProtoReflect.Descriptor instead. +func (*InstrumentationScope) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_common_v1_common_proto_rawDescGZIP(), []int{4} +} + +func (x *InstrumentationScope) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *InstrumentationScope) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *InstrumentationScope) GetAttributes() []*KeyValue { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *InstrumentationScope) GetDroppedAttributesCount() uint32 { + if x != nil { + return x.DroppedAttributesCount + } + return 0 +} + +// A reference to an Entity. +// Entity represents an object of interest associated with produced telemetry: e.g spans, metrics, profiles, or logs. +// +// Status: [Development] +type EntityRef struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The Schema URL, if known. This is the identifier of the Schema that the entity data + // is recorded in. To learn more about Schema URL see + // https://opentelemetry.io/docs/specs/otel/schemas/#schema-url + // + // This schema_url applies to the data in this message and to the Resource attributes + // referenced by id_keys and description_keys. + // TODO: discuss if we are happy with this somewhat complicated definition of what + // the schema_url applies to. + // + // This field obsoletes the schema_url field in ResourceMetrics/ResourceSpans/ResourceLogs. + SchemaUrl string `protobuf:"bytes,1,opt,name=schema_url,json=schemaUrl,proto3" json:"schema_url,omitempty"` + // Defines the type of the entity. MUST not change during the lifetime of the entity. + // For example: "service" or "host". This field is required and MUST not be empty + // for valid entities. + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + // Attribute Keys that identify the entity. + // MUST not change during the lifetime of the entity. The Id must contain at least one attribute. + // These keys MUST exist in the containing {message}.attributes. + IdKeys []string `protobuf:"bytes,3,rep,name=id_keys,json=idKeys,proto3" json:"id_keys,omitempty"` + // Descriptive (non-identifying) attribute keys of the entity. + // MAY change over the lifetime of the entity. MAY be empty. + // These attribute keys are not part of entity's identity. + // These keys MUST exist in the containing {message}.attributes. + DescriptionKeys []string `protobuf:"bytes,4,rep,name=description_keys,json=descriptionKeys,proto3" json:"description_keys,omitempty"` +} + +func (x *EntityRef) Reset() { + *x = EntityRef{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_common_v1_common_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EntityRef) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EntityRef) ProtoMessage() {} + +func (x *EntityRef) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_common_v1_common_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EntityRef.ProtoReflect.Descriptor instead. +func (*EntityRef) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_common_v1_common_proto_rawDescGZIP(), []int{5} +} + +func (x *EntityRef) GetSchemaUrl() string { + if x != nil { + return x.SchemaUrl + } + return "" +} + +func (x *EntityRef) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *EntityRef) GetIdKeys() []string { + if x != nil { + return x.IdKeys + } + return nil +} + +func (x *EntityRef) GetDescriptionKeys() []string { + if x != nil { + return x.DescriptionKeys + } + return nil +} + +var File_opentelemetry_proto_common_v1_common_proto protoreflect.FileDescriptor + +var file_opentelemetry_proto_common_v1_common_proto_rawDesc = []byte{ + 0x0a, 0x2a, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1d, 0x6f, 0x70, + 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x22, 0x96, 0x03, 0x0a, 0x08, + 0x41, 0x6e, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x23, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, + 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1f, 0x0a, + 0x0a, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x48, 0x00, 0x52, 0x09, 0x62, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1d, + 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x23, 0x0a, + 0x0c, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x0b, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x61, 0x72, 0x72, 0x61, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, + 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x72, 0x72, 0x61, 0x79, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x61, 0x72, 0x72, 0x61, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x12, 0x50, 0x0a, 0x0c, 0x6b, 0x76, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, + 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4c, + 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x6b, 0x76, 0x6c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x12, 0x21, 0x0a, 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x34, 0x0a, 0x15, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x13, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x07, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x22, 0x4d, 0x0a, 0x0a, 0x41, 0x72, 0x72, 0x61, 0x79, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, + 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x76, 0x31, 0x2e, 0x41, 0x6e, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x73, 0x22, 0x4f, 0x0a, 0x0c, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4c, + 0x69, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, + 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x73, 0x22, 0x7e, 0x0a, 0x08, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x3d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, + 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x41, 0x6e, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x64, 0x65, + 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6b, 0x65, 0x79, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x22, 0xc7, 0x01, 0x0a, 0x14, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x6d, + 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x47, 0x0a, 0x0a, 0x61, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, + 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x16, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x41, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x82, + 0x01, 0x0a, 0x09, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x66, 0x12, 0x1d, 0x0a, 0x0a, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x55, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, + 0x17, 0x0a, 0x07, 0x69, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x06, 0x69, 0x64, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4b, + 0x65, 0x79, 0x73, 0x42, 0x7b, 0x0a, 0x20, 0x69, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, + 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x42, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x28, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, + 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x69, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2f, 0x6f, 0x74, 0x6c, 0x70, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, + 0xaa, 0x02, 0x1d, 0x4f, 0x70, 0x65, 0x6e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, + 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x31, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_opentelemetry_proto_common_v1_common_proto_rawDescOnce sync.Once + file_opentelemetry_proto_common_v1_common_proto_rawDescData = file_opentelemetry_proto_common_v1_common_proto_rawDesc +) + +func file_opentelemetry_proto_common_v1_common_proto_rawDescGZIP() []byte { + file_opentelemetry_proto_common_v1_common_proto_rawDescOnce.Do(func() { + file_opentelemetry_proto_common_v1_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_opentelemetry_proto_common_v1_common_proto_rawDescData) + }) + return file_opentelemetry_proto_common_v1_common_proto_rawDescData +} + +var file_opentelemetry_proto_common_v1_common_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_opentelemetry_proto_common_v1_common_proto_goTypes = []interface{}{ + (*AnyValue)(nil), // 0: opentelemetry.proto.common.v1.AnyValue + (*ArrayValue)(nil), // 1: opentelemetry.proto.common.v1.ArrayValue + (*KeyValueList)(nil), // 2: opentelemetry.proto.common.v1.KeyValueList + (*KeyValue)(nil), // 3: opentelemetry.proto.common.v1.KeyValue + (*InstrumentationScope)(nil), // 4: opentelemetry.proto.common.v1.InstrumentationScope + (*EntityRef)(nil), // 5: opentelemetry.proto.common.v1.EntityRef +} +var file_opentelemetry_proto_common_v1_common_proto_depIdxs = []int32{ + 1, // 0: opentelemetry.proto.common.v1.AnyValue.array_value:type_name -> opentelemetry.proto.common.v1.ArrayValue + 2, // 1: opentelemetry.proto.common.v1.AnyValue.kvlist_value:type_name -> opentelemetry.proto.common.v1.KeyValueList + 0, // 2: opentelemetry.proto.common.v1.ArrayValue.values:type_name -> opentelemetry.proto.common.v1.AnyValue + 3, // 3: opentelemetry.proto.common.v1.KeyValueList.values:type_name -> opentelemetry.proto.common.v1.KeyValue + 0, // 4: opentelemetry.proto.common.v1.KeyValue.value:type_name -> opentelemetry.proto.common.v1.AnyValue + 3, // 5: opentelemetry.proto.common.v1.InstrumentationScope.attributes:type_name -> opentelemetry.proto.common.v1.KeyValue + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_opentelemetry_proto_common_v1_common_proto_init() } +func file_opentelemetry_proto_common_v1_common_proto_init() { + if File_opentelemetry_proto_common_v1_common_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_opentelemetry_proto_common_v1_common_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AnyValue); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_opentelemetry_proto_common_v1_common_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ArrayValue); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_opentelemetry_proto_common_v1_common_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KeyValueList); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_opentelemetry_proto_common_v1_common_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KeyValue); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_opentelemetry_proto_common_v1_common_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InstrumentationScope); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_opentelemetry_proto_common_v1_common_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EntityRef); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_opentelemetry_proto_common_v1_common_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*AnyValue_StringValue)(nil), + (*AnyValue_BoolValue)(nil), + (*AnyValue_IntValue)(nil), + (*AnyValue_DoubleValue)(nil), + (*AnyValue_ArrayValue)(nil), + (*AnyValue_KvlistValue)(nil), + (*AnyValue_BytesValue)(nil), + (*AnyValue_StringValueStrindex)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_opentelemetry_proto_common_v1_common_proto_rawDesc, + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_opentelemetry_proto_common_v1_common_proto_goTypes, + DependencyIndexes: file_opentelemetry_proto_common_v1_common_proto_depIdxs, + MessageInfos: file_opentelemetry_proto_common_v1_common_proto_msgTypes, + }.Build() + File_opentelemetry_proto_common_v1_common_proto = out.File + file_opentelemetry_proto_common_v1_common_proto_rawDesc = nil + file_opentelemetry_proto_common_v1_common_proto_goTypes = nil + file_opentelemetry_proto_common_v1_common_proto_depIdxs = nil +} diff --git a/vendor/go.opentelemetry.io/proto/otlp/resource/v1/resource.pb.go b/vendor/go.opentelemetry.io/proto/otlp/resource/v1/resource.pb.go new file mode 100644 index 00000000000..301247ddfee --- /dev/null +++ b/vendor/go.opentelemetry.io/proto/otlp/resource/v1/resource.pb.go @@ -0,0 +1,214 @@ +// Copyright 2019, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.21.6 +// source: opentelemetry/proto/resource/v1/resource.proto + +package v1 + +import ( + v1 "go.opentelemetry.io/proto/otlp/common/v1" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Resource information. +type Resource struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Set of attributes that describe the resource. + // Attribute keys MUST be unique (it is not allowed to have more than one + // attribute with the same key). + // The behavior of software that receives duplicated keys can be unpredictable. + Attributes []*v1.KeyValue `protobuf:"bytes,1,rep,name=attributes,proto3" json:"attributes,omitempty"` + // The number of dropped attributes. If the value is 0, then + // no attributes were dropped. + DroppedAttributesCount uint32 `protobuf:"varint,2,opt,name=dropped_attributes_count,json=droppedAttributesCount,proto3" json:"dropped_attributes_count,omitempty"` + // Set of entities that participate in this Resource. + // + // Note: keys in the references MUST exist in attributes of this message. + // + // Status: [Development] + EntityRefs []*v1.EntityRef `protobuf:"bytes,3,rep,name=entity_refs,json=entityRefs,proto3" json:"entity_refs,omitempty"` +} + +func (x *Resource) Reset() { + *x = Resource{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_resource_v1_resource_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Resource) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Resource) ProtoMessage() {} + +func (x *Resource) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_resource_v1_resource_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Resource.ProtoReflect.Descriptor instead. +func (*Resource) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_resource_v1_resource_proto_rawDescGZIP(), []int{0} +} + +func (x *Resource) GetAttributes() []*v1.KeyValue { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *Resource) GetDroppedAttributesCount() uint32 { + if x != nil { + return x.DroppedAttributesCount + } + return 0 +} + +func (x *Resource) GetEntityRefs() []*v1.EntityRef { + if x != nil { + return x.EntityRefs + } + return nil +} + +var File_opentelemetry_proto_resource_v1_resource_proto protoreflect.FileDescriptor + +var file_opentelemetry_proto_resource_v1_resource_proto_rawDesc = []byte{ + 0x0a, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2f, 0x76, + 0x31, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x1f, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x76, + 0x31, 0x1a, 0x2a, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, + 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd8, 0x01, + 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, + 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b, + 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x16, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x49, 0x0a, + 0x0b, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x72, 0x65, 0x66, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, + 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x66, 0x52, 0x0a, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x66, 0x73, 0x42, 0x83, 0x01, 0x0a, 0x22, 0x69, 0x6f, 0x2e, + 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x42, + 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x2a, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, + 0x72, 0x79, 0x2e, 0x69, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6f, 0x74, 0x6c, 0x70, + 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2f, 0x76, 0x31, 0xaa, 0x02, 0x1f, 0x4f, + 0x70, 0x65, 0x6e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x56, 0x31, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_opentelemetry_proto_resource_v1_resource_proto_rawDescOnce sync.Once + file_opentelemetry_proto_resource_v1_resource_proto_rawDescData = file_opentelemetry_proto_resource_v1_resource_proto_rawDesc +) + +func file_opentelemetry_proto_resource_v1_resource_proto_rawDescGZIP() []byte { + file_opentelemetry_proto_resource_v1_resource_proto_rawDescOnce.Do(func() { + file_opentelemetry_proto_resource_v1_resource_proto_rawDescData = protoimpl.X.CompressGZIP(file_opentelemetry_proto_resource_v1_resource_proto_rawDescData) + }) + return file_opentelemetry_proto_resource_v1_resource_proto_rawDescData +} + +var file_opentelemetry_proto_resource_v1_resource_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_opentelemetry_proto_resource_v1_resource_proto_goTypes = []interface{}{ + (*Resource)(nil), // 0: opentelemetry.proto.resource.v1.Resource + (*v1.KeyValue)(nil), // 1: opentelemetry.proto.common.v1.KeyValue + (*v1.EntityRef)(nil), // 2: opentelemetry.proto.common.v1.EntityRef +} +var file_opentelemetry_proto_resource_v1_resource_proto_depIdxs = []int32{ + 1, // 0: opentelemetry.proto.resource.v1.Resource.attributes:type_name -> opentelemetry.proto.common.v1.KeyValue + 2, // 1: opentelemetry.proto.resource.v1.Resource.entity_refs:type_name -> opentelemetry.proto.common.v1.EntityRef + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_opentelemetry_proto_resource_v1_resource_proto_init() } +func file_opentelemetry_proto_resource_v1_resource_proto_init() { + if File_opentelemetry_proto_resource_v1_resource_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_opentelemetry_proto_resource_v1_resource_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Resource); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_opentelemetry_proto_resource_v1_resource_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_opentelemetry_proto_resource_v1_resource_proto_goTypes, + DependencyIndexes: file_opentelemetry_proto_resource_v1_resource_proto_depIdxs, + MessageInfos: file_opentelemetry_proto_resource_v1_resource_proto_msgTypes, + }.Build() + File_opentelemetry_proto_resource_v1_resource_proto = out.File + file_opentelemetry_proto_resource_v1_resource_proto_rawDesc = nil + file_opentelemetry_proto_resource_v1_resource_proto_goTypes = nil + file_opentelemetry_proto_resource_v1_resource_proto_depIdxs = nil +} diff --git a/vendor/go.opentelemetry.io/proto/otlp/trace/v1/trace.pb.go b/vendor/go.opentelemetry.io/proto/otlp/trace/v1/trace.pb.go new file mode 100644 index 00000000000..d7bfca90299 --- /dev/null +++ b/vendor/go.opentelemetry.io/proto/otlp/trace/v1/trace.pb.go @@ -0,0 +1,1285 @@ +// Copyright 2019, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.21.6 +// source: opentelemetry/proto/trace/v1/trace.proto + +package v1 + +import ( + v11 "go.opentelemetry.io/proto/otlp/common/v1" + v1 "go.opentelemetry.io/proto/otlp/resource/v1" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// SpanFlags represents constants used to interpret the +// Span.flags field, which is protobuf 'fixed32' type and is to +// be used as bit-fields. Each non-zero value defined in this enum is +// a bit-mask. To extract the bit-field, for example, use an +// expression like: +// +// (span.flags & SPAN_FLAGS_TRACE_FLAGS_MASK) +// +// See https://www.w3.org/TR/trace-context-2/#trace-flags for the flag definitions. +// +// Note that Span flags were introduced in version 1.1 of the +// OpenTelemetry protocol. Older Span producers do not set this +// field, consequently consumers should not rely on the absence of a +// particular flag bit to indicate the presence of a particular feature. +type SpanFlags int32 + +const ( + // The zero value for the enum. Should not be used for comparisons. + // Instead use bitwise "and" with the appropriate mask as shown above. + SpanFlags_SPAN_FLAGS_DO_NOT_USE SpanFlags = 0 + // Bits 0-7 are used for trace flags. + SpanFlags_SPAN_FLAGS_TRACE_FLAGS_MASK SpanFlags = 255 + // Bits 8 and 9 are used to indicate that the parent span or link span is remote. + // Bit 8 (`HAS_IS_REMOTE`) indicates whether the value is known. + // Bit 9 (`IS_REMOTE`) indicates whether the span or link is remote. + SpanFlags_SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK SpanFlags = 256 + SpanFlags_SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK SpanFlags = 512 +) + +// Enum value maps for SpanFlags. +var ( + SpanFlags_name = map[int32]string{ + 0: "SPAN_FLAGS_DO_NOT_USE", + 255: "SPAN_FLAGS_TRACE_FLAGS_MASK", + 256: "SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK", + 512: "SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK", + } + SpanFlags_value = map[string]int32{ + "SPAN_FLAGS_DO_NOT_USE": 0, + "SPAN_FLAGS_TRACE_FLAGS_MASK": 255, + "SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK": 256, + "SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK": 512, + } +) + +func (x SpanFlags) Enum() *SpanFlags { + p := new(SpanFlags) + *p = x + return p +} + +func (x SpanFlags) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SpanFlags) Descriptor() protoreflect.EnumDescriptor { + return file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[0].Descriptor() +} + +func (SpanFlags) Type() protoreflect.EnumType { + return &file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[0] +} + +func (x SpanFlags) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SpanFlags.Descriptor instead. +func (SpanFlags) EnumDescriptor() ([]byte, []int) { + return file_opentelemetry_proto_trace_v1_trace_proto_rawDescGZIP(), []int{0} +} + +// SpanKind is the type of span. Can be used to specify additional relationships between spans +// in addition to a parent/child relationship. +type Span_SpanKind int32 + +const ( + // Unspecified. Do NOT use as default. + // Implementations MAY assume SpanKind to be INTERNAL when receiving UNSPECIFIED. + Span_SPAN_KIND_UNSPECIFIED Span_SpanKind = 0 + // Indicates that the span represents an internal operation within an application, + // as opposed to an operation happening at the boundaries. Default value. + Span_SPAN_KIND_INTERNAL Span_SpanKind = 1 + // Indicates that the span covers server-side handling of an RPC or other + // remote network request. + Span_SPAN_KIND_SERVER Span_SpanKind = 2 + // Indicates that the span describes a request to some remote service. + Span_SPAN_KIND_CLIENT Span_SpanKind = 3 + // Indicates that the span describes a producer sending a message to a broker. + // Unlike CLIENT and SERVER, there is often no direct critical path latency relationship + // between producer and consumer spans. A PRODUCER span ends when the message was accepted + // by the broker while the logical processing of the message might span a much longer time. + Span_SPAN_KIND_PRODUCER Span_SpanKind = 4 + // Indicates that the span describes consumer receiving a message from a broker. + // Like the PRODUCER kind, there is often no direct critical path latency relationship + // between producer and consumer spans. + Span_SPAN_KIND_CONSUMER Span_SpanKind = 5 +) + +// Enum value maps for Span_SpanKind. +var ( + Span_SpanKind_name = map[int32]string{ + 0: "SPAN_KIND_UNSPECIFIED", + 1: "SPAN_KIND_INTERNAL", + 2: "SPAN_KIND_SERVER", + 3: "SPAN_KIND_CLIENT", + 4: "SPAN_KIND_PRODUCER", + 5: "SPAN_KIND_CONSUMER", + } + Span_SpanKind_value = map[string]int32{ + "SPAN_KIND_UNSPECIFIED": 0, + "SPAN_KIND_INTERNAL": 1, + "SPAN_KIND_SERVER": 2, + "SPAN_KIND_CLIENT": 3, + "SPAN_KIND_PRODUCER": 4, + "SPAN_KIND_CONSUMER": 5, + } +) + +func (x Span_SpanKind) Enum() *Span_SpanKind { + p := new(Span_SpanKind) + *p = x + return p +} + +func (x Span_SpanKind) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Span_SpanKind) Descriptor() protoreflect.EnumDescriptor { + return file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[1].Descriptor() +} + +func (Span_SpanKind) Type() protoreflect.EnumType { + return &file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[1] +} + +func (x Span_SpanKind) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Span_SpanKind.Descriptor instead. +func (Span_SpanKind) EnumDescriptor() ([]byte, []int) { + return file_opentelemetry_proto_trace_v1_trace_proto_rawDescGZIP(), []int{3, 0} +} + +// For the semantics of status codes see +// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status +type Status_StatusCode int32 + +const ( + // The default status. + Status_STATUS_CODE_UNSET Status_StatusCode = 0 + // The Span has been validated by an Application developer or Operator to + // have completed successfully. + Status_STATUS_CODE_OK Status_StatusCode = 1 + // The Span contains an error. + Status_STATUS_CODE_ERROR Status_StatusCode = 2 +) + +// Enum value maps for Status_StatusCode. +var ( + Status_StatusCode_name = map[int32]string{ + 0: "STATUS_CODE_UNSET", + 1: "STATUS_CODE_OK", + 2: "STATUS_CODE_ERROR", + } + Status_StatusCode_value = map[string]int32{ + "STATUS_CODE_UNSET": 0, + "STATUS_CODE_OK": 1, + "STATUS_CODE_ERROR": 2, + } +) + +func (x Status_StatusCode) Enum() *Status_StatusCode { + p := new(Status_StatusCode) + *p = x + return p +} + +func (x Status_StatusCode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Status_StatusCode) Descriptor() protoreflect.EnumDescriptor { + return file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[2].Descriptor() +} + +func (Status_StatusCode) Type() protoreflect.EnumType { + return &file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[2] +} + +func (x Status_StatusCode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Status_StatusCode.Descriptor instead. +func (Status_StatusCode) EnumDescriptor() ([]byte, []int) { + return file_opentelemetry_proto_trace_v1_trace_proto_rawDescGZIP(), []int{4, 0} +} + +// TracesData represents the traces data that can be stored in a persistent storage, +// OR can be embedded by other protocols that transfer OTLP traces data but do +// not implement the OTLP protocol. +// +// The main difference between this message and collector protocol is that +// in this message there will not be any "control" or "metadata" specific to +// OTLP protocol. +// +// When new fields are added into this message, the OTLP request MUST be updated +// as well. +type TracesData struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // An array of ResourceSpans. + // For data coming from a single resource this array will typically contain + // one element. Intermediary nodes that receive data from multiple origins + // typically batch the data before forwarding further and in that case this + // array will contain multiple elements. + ResourceSpans []*ResourceSpans `protobuf:"bytes,1,rep,name=resource_spans,json=resourceSpans,proto3" json:"resource_spans,omitempty"` +} + +func (x *TracesData) Reset() { + *x = TracesData{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TracesData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TracesData) ProtoMessage() {} + +func (x *TracesData) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TracesData.ProtoReflect.Descriptor instead. +func (*TracesData) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_trace_v1_trace_proto_rawDescGZIP(), []int{0} +} + +func (x *TracesData) GetResourceSpans() []*ResourceSpans { + if x != nil { + return x.ResourceSpans + } + return nil +} + +// A collection of ScopeSpans from a Resource. +type ResourceSpans struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The resource for the spans in this message. + // If this field is not set then no resource info is known. + Resource *v1.Resource `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"` + // A list of ScopeSpans that originate from a resource. + ScopeSpans []*ScopeSpans `protobuf:"bytes,2,rep,name=scope_spans,json=scopeSpans,proto3" json:"scope_spans,omitempty"` + // The Schema URL, if known. This is the identifier of the Schema that the resource data + // is recorded in. Notably, the last part of the URL path is the version number of the + // schema: http[s]://server[:port]/path/. To learn more about Schema URL see + // https://opentelemetry.io/docs/specs/otel/schemas/#schema-url + // This schema_url applies to the data in the "resource" field. It does not apply + // to the data in the "scope_spans" field which have their own schema_url field. + SchemaUrl string `protobuf:"bytes,3,opt,name=schema_url,json=schemaUrl,proto3" json:"schema_url,omitempty"` +} + +func (x *ResourceSpans) Reset() { + *x = ResourceSpans{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResourceSpans) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceSpans) ProtoMessage() {} + +func (x *ResourceSpans) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceSpans.ProtoReflect.Descriptor instead. +func (*ResourceSpans) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_trace_v1_trace_proto_rawDescGZIP(), []int{1} +} + +func (x *ResourceSpans) GetResource() *v1.Resource { + if x != nil { + return x.Resource + } + return nil +} + +func (x *ResourceSpans) GetScopeSpans() []*ScopeSpans { + if x != nil { + return x.ScopeSpans + } + return nil +} + +func (x *ResourceSpans) GetSchemaUrl() string { + if x != nil { + return x.SchemaUrl + } + return "" +} + +// A collection of Spans produced by an InstrumentationScope. +type ScopeSpans struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The instrumentation scope information for the spans in this message. + // Semantically when InstrumentationScope isn't set, it is equivalent with + // an empty instrumentation scope name (unknown). + Scope *v11.InstrumentationScope `protobuf:"bytes,1,opt,name=scope,proto3" json:"scope,omitempty"` + // A list of Spans that originate from an instrumentation scope. + Spans []*Span `protobuf:"bytes,2,rep,name=spans,proto3" json:"spans,omitempty"` + // The Schema URL, if known. This is the identifier of the Schema that the span data + // is recorded in. Notably, the last part of the URL path is the version number of the + // schema: http[s]://server[:port]/path/. To learn more about Schema URL see + // https://opentelemetry.io/docs/specs/otel/schemas/#schema-url + // This schema_url applies to the data in the "scope" field and all spans and span + // events in the "spans" field. + SchemaUrl string `protobuf:"bytes,3,opt,name=schema_url,json=schemaUrl,proto3" json:"schema_url,omitempty"` +} + +func (x *ScopeSpans) Reset() { + *x = ScopeSpans{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ScopeSpans) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ScopeSpans) ProtoMessage() {} + +func (x *ScopeSpans) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ScopeSpans.ProtoReflect.Descriptor instead. +func (*ScopeSpans) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_trace_v1_trace_proto_rawDescGZIP(), []int{2} +} + +func (x *ScopeSpans) GetScope() *v11.InstrumentationScope { + if x != nil { + return x.Scope + } + return nil +} + +func (x *ScopeSpans) GetSpans() []*Span { + if x != nil { + return x.Spans + } + return nil +} + +func (x *ScopeSpans) GetSchemaUrl() string { + if x != nil { + return x.SchemaUrl + } + return "" +} + +// A Span represents a single operation performed by a single component of the system. +// +// The next available field id is 17. +type Span struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // A unique identifier for a trace. All spans from the same trace share + // the same `trace_id`. The ID is a 16-byte array. An ID with all zeroes OR + // of length other than 16 bytes is considered invalid (empty string in OTLP/JSON + // is zero-length and thus is also invalid). + // + // This field is required. + TraceId []byte `protobuf:"bytes,1,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty"` + // A unique identifier for a span within a trace, assigned when the span + // is created. The ID is an 8-byte array. An ID with all zeroes OR of length + // other than 8 bytes is considered invalid (empty string in OTLP/JSON + // is zero-length and thus is also invalid). + // + // This field is required. + SpanId []byte `protobuf:"bytes,2,opt,name=span_id,json=spanId,proto3" json:"span_id,omitempty"` + // trace_state conveys information about request position in multiple distributed tracing graphs. + // It is a trace_state in w3c-trace-context format: https://www.w3.org/TR/trace-context/#tracestate-header + // See also https://github.com/w3c/distributed-tracing for more details about this field. + TraceState string `protobuf:"bytes,3,opt,name=trace_state,json=traceState,proto3" json:"trace_state,omitempty"` + // The `span_id` of this span's parent span. If this is a root span, then this + // field must be empty. The ID is an 8-byte array. + ParentSpanId []byte `protobuf:"bytes,4,opt,name=parent_span_id,json=parentSpanId,proto3" json:"parent_span_id,omitempty"` + // Flags, a bit field. + // + // Bits 0-7 (8 least significant bits) are the trace flags as defined in W3C Trace + // Context specification. To read the 8-bit W3C trace flag, use + // `flags & SPAN_FLAGS_TRACE_FLAGS_MASK`. + // + // See https://www.w3.org/TR/trace-context-2/#trace-flags for the flag definitions. + // + // Bits 8 and 9 represent the 3 states of whether a span's parent + // is remote. The states are (unknown, is not remote, is remote). + // To read whether the value is known, use `(flags & SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK) != 0`. + // To read whether the span is remote, use `(flags & SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) != 0`. + // + // When creating span messages, if the message is logically forwarded from another source + // with an equivalent flags fields (i.e., usually another OTLP span message), the field SHOULD + // be copied as-is. If creating from a source that does not have an equivalent flags field + // (such as a runtime representation of an OpenTelemetry span), the high 22 bits MUST + // be set to zero. + // Readers MUST NOT assume that bits 10-31 (22 most significant bits) will be zero. + // + // [Optional]. + Flags uint32 `protobuf:"fixed32,16,opt,name=flags,proto3" json:"flags,omitempty"` + // A description of the span's operation. + // + // For example, the name can be a qualified method name or a file name + // and a line number where the operation is called. A best practice is to use + // the same display name at the same call point in an application. + // This makes it easier to correlate spans in different traces. + // + // This field is semantically required to be set to non-empty string. + // Empty value is equivalent to an unknown span name. + // + // This field is required. + Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty"` + // Distinguishes between spans generated in a particular context. For example, + // two spans with the same name may be distinguished using `CLIENT` (caller) + // and `SERVER` (callee) to identify queueing latency associated with the span. + Kind Span_SpanKind `protobuf:"varint,6,opt,name=kind,proto3,enum=opentelemetry.proto.trace.v1.Span_SpanKind" json:"kind,omitempty"` + // The start time of the span. On the client side, this is the time + // kept by the local machine where the span execution starts. On the server side, this + // is the time when the server's application handler starts running. + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. + // + // This field is semantically required and it is expected that end_time >= start_time. + StartTimeUnixNano uint64 `protobuf:"fixed64,7,opt,name=start_time_unix_nano,json=startTimeUnixNano,proto3" json:"start_time_unix_nano,omitempty"` + // The end time of the span. On the client side, this is the time + // kept by the local machine where the span execution ends. On the server side, this + // is the time when the server application handler stops running. + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. + // + // This field is semantically required and it is expected that end_time >= start_time. + EndTimeUnixNano uint64 `protobuf:"fixed64,8,opt,name=end_time_unix_nano,json=endTimeUnixNano,proto3" json:"end_time_unix_nano,omitempty"` + // A collection of key/value pairs. Note, global attributes + // like server name can be set using the resource API. Examples of attributes: + // + // "/http/user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36" + // "/http/server_latency": 300 + // "example.com/myattribute": true + // "example.com/score": 10.239 + // + // Attribute keys MUST be unique (it is not allowed to have more than one + // attribute with the same key). + // The behavior of software that receives duplicated keys can be unpredictable. + Attributes []*v11.KeyValue `protobuf:"bytes,9,rep,name=attributes,proto3" json:"attributes,omitempty"` + // The number of attributes that were discarded. Attributes + // can be discarded because their keys are too long or because there are too many + // attributes. If this value is 0, then no attributes were dropped. + DroppedAttributesCount uint32 `protobuf:"varint,10,opt,name=dropped_attributes_count,json=droppedAttributesCount,proto3" json:"dropped_attributes_count,omitempty"` + // A collection of Event items. + Events []*Span_Event `protobuf:"bytes,11,rep,name=events,proto3" json:"events,omitempty"` + // The number of dropped events. If the value is 0, then no + // events were dropped. + DroppedEventsCount uint32 `protobuf:"varint,12,opt,name=dropped_events_count,json=droppedEventsCount,proto3" json:"dropped_events_count,omitempty"` + // A collection of Links, which are references from this span to a span + // in the same or different trace. + Links []*Span_Link `protobuf:"bytes,13,rep,name=links,proto3" json:"links,omitempty"` + // The number of dropped links after the maximum size was + // enforced. If this value is 0, then no links were dropped. + DroppedLinksCount uint32 `protobuf:"varint,14,opt,name=dropped_links_count,json=droppedLinksCount,proto3" json:"dropped_links_count,omitempty"` + // An optional final status for this span. Semantically when Status isn't set, it means + // span's status code is unset, i.e. assume STATUS_CODE_UNSET (code = 0). + Status *Status `protobuf:"bytes,15,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *Span) Reset() { + *x = Span{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Span) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Span) ProtoMessage() {} + +func (x *Span) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Span.ProtoReflect.Descriptor instead. +func (*Span) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_trace_v1_trace_proto_rawDescGZIP(), []int{3} +} + +func (x *Span) GetTraceId() []byte { + if x != nil { + return x.TraceId + } + return nil +} + +func (x *Span) GetSpanId() []byte { + if x != nil { + return x.SpanId + } + return nil +} + +func (x *Span) GetTraceState() string { + if x != nil { + return x.TraceState + } + return "" +} + +func (x *Span) GetParentSpanId() []byte { + if x != nil { + return x.ParentSpanId + } + return nil +} + +func (x *Span) GetFlags() uint32 { + if x != nil { + return x.Flags + } + return 0 +} + +func (x *Span) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Span) GetKind() Span_SpanKind { + if x != nil { + return x.Kind + } + return Span_SPAN_KIND_UNSPECIFIED +} + +func (x *Span) GetStartTimeUnixNano() uint64 { + if x != nil { + return x.StartTimeUnixNano + } + return 0 +} + +func (x *Span) GetEndTimeUnixNano() uint64 { + if x != nil { + return x.EndTimeUnixNano + } + return 0 +} + +func (x *Span) GetAttributes() []*v11.KeyValue { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *Span) GetDroppedAttributesCount() uint32 { + if x != nil { + return x.DroppedAttributesCount + } + return 0 +} + +func (x *Span) GetEvents() []*Span_Event { + if x != nil { + return x.Events + } + return nil +} + +func (x *Span) GetDroppedEventsCount() uint32 { + if x != nil { + return x.DroppedEventsCount + } + return 0 +} + +func (x *Span) GetLinks() []*Span_Link { + if x != nil { + return x.Links + } + return nil +} + +func (x *Span) GetDroppedLinksCount() uint32 { + if x != nil { + return x.DroppedLinksCount + } + return 0 +} + +func (x *Span) GetStatus() *Status { + if x != nil { + return x.Status + } + return nil +} + +// The Status type defines a logical error model that is suitable for different +// programming environments, including REST APIs and RPC APIs. +type Status struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // A developer-facing human readable error message. + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + // The status code. + Code Status_StatusCode `protobuf:"varint,3,opt,name=code,proto3,enum=opentelemetry.proto.trace.v1.Status_StatusCode" json:"code,omitempty"` +} + +func (x *Status) Reset() { + *x = Status{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Status) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Status) ProtoMessage() {} + +func (x *Status) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Status.ProtoReflect.Descriptor instead. +func (*Status) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_trace_v1_trace_proto_rawDescGZIP(), []int{4} +} + +func (x *Status) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *Status) GetCode() Status_StatusCode { + if x != nil { + return x.Code + } + return Status_STATUS_CODE_UNSET +} + +// Event is a time-stamped annotation of the span, consisting of user-supplied +// text description and key-value pairs. +type Span_Event struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The time the event occurred. + TimeUnixNano uint64 `protobuf:"fixed64,1,opt,name=time_unix_nano,json=timeUnixNano,proto3" json:"time_unix_nano,omitempty"` + // The name of the event. + // This field is semantically required to be set to non-empty string. + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // A collection of attribute key/value pairs on the event. + // Attribute keys MUST be unique (it is not allowed to have more than one + // attribute with the same key). + // The behavior of software that receives duplicated keys can be unpredictable. + Attributes []*v11.KeyValue `protobuf:"bytes,3,rep,name=attributes,proto3" json:"attributes,omitempty"` + // The number of dropped attributes. If the value is 0, + // then no attributes were dropped. + DroppedAttributesCount uint32 `protobuf:"varint,4,opt,name=dropped_attributes_count,json=droppedAttributesCount,proto3" json:"dropped_attributes_count,omitempty"` +} + +func (x *Span_Event) Reset() { + *x = Span_Event{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Span_Event) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Span_Event) ProtoMessage() {} + +func (x *Span_Event) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Span_Event.ProtoReflect.Descriptor instead. +func (*Span_Event) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_trace_v1_trace_proto_rawDescGZIP(), []int{3, 0} +} + +func (x *Span_Event) GetTimeUnixNano() uint64 { + if x != nil { + return x.TimeUnixNano + } + return 0 +} + +func (x *Span_Event) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Span_Event) GetAttributes() []*v11.KeyValue { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *Span_Event) GetDroppedAttributesCount() uint32 { + if x != nil { + return x.DroppedAttributesCount + } + return 0 +} + +// A pointer from the current span to another span in the same trace or in a +// different trace. For example, this can be used in batching operations, +// where a single batch handler processes multiple requests from different +// traces or when the handler receives a request from a different project. +type Span_Link struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // A unique identifier of a trace that this linked span is part of. The ID is a + // 16-byte array. + TraceId []byte `protobuf:"bytes,1,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty"` + // A unique identifier for the linked span. The ID is an 8-byte array. + SpanId []byte `protobuf:"bytes,2,opt,name=span_id,json=spanId,proto3" json:"span_id,omitempty"` + // The trace_state associated with the link. + TraceState string `protobuf:"bytes,3,opt,name=trace_state,json=traceState,proto3" json:"trace_state,omitempty"` + // A collection of attribute key/value pairs on the link. + // Attribute keys MUST be unique (it is not allowed to have more than one + // attribute with the same key). + // The behavior of software that receives duplicated keys can be unpredictable. + Attributes []*v11.KeyValue `protobuf:"bytes,4,rep,name=attributes,proto3" json:"attributes,omitempty"` + // The number of dropped attributes. If the value is 0, + // then no attributes were dropped. + DroppedAttributesCount uint32 `protobuf:"varint,5,opt,name=dropped_attributes_count,json=droppedAttributesCount,proto3" json:"dropped_attributes_count,omitempty"` + // Flags, a bit field. + // + // Bits 0-7 (8 least significant bits) are the trace flags as defined in W3C Trace + // Context specification. To read the 8-bit W3C trace flag, use + // `flags & SPAN_FLAGS_TRACE_FLAGS_MASK`. + // + // See https://www.w3.org/TR/trace-context-2/#trace-flags for the flag definitions. + // + // Bits 8 and 9 represent the 3 states of whether the link is remote. + // The states are (unknown, is not remote, is remote). + // To read whether the value is known, use `(flags & SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK) != 0`. + // To read whether the link is remote, use `(flags & SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) != 0`. + // + // Readers MUST NOT assume that bits 10-31 (22 most significant bits) will be zero. + // When creating new spans, bits 10-31 (most-significant 22-bits) MUST be zero. + // + // [Optional]. + Flags uint32 `protobuf:"fixed32,6,opt,name=flags,proto3" json:"flags,omitempty"` +} + +func (x *Span_Link) Reset() { + *x = Span_Link{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Span_Link) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Span_Link) ProtoMessage() {} + +func (x *Span_Link) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Span_Link.ProtoReflect.Descriptor instead. +func (*Span_Link) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_trace_v1_trace_proto_rawDescGZIP(), []int{3, 1} +} + +func (x *Span_Link) GetTraceId() []byte { + if x != nil { + return x.TraceId + } + return nil +} + +func (x *Span_Link) GetSpanId() []byte { + if x != nil { + return x.SpanId + } + return nil +} + +func (x *Span_Link) GetTraceState() string { + if x != nil { + return x.TraceState + } + return "" +} + +func (x *Span_Link) GetAttributes() []*v11.KeyValue { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *Span_Link) GetDroppedAttributesCount() uint32 { + if x != nil { + return x.DroppedAttributesCount + } + return 0 +} + +func (x *Span_Link) GetFlags() uint32 { + if x != nil { + return x.Flags + } + return 0 +} + +var File_opentelemetry_proto_trace_v1_trace_proto protoreflect.FileDescriptor + +var file_opentelemetry_proto_trace_v1_trace_proto_rawDesc = []byte{ + 0x0a, 0x28, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x74, + 0x72, 0x61, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1c, 0x6f, 0x70, 0x65, 0x6e, + 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x2a, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, + 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, + 0x74, 0x72, 0x79, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x60, 0x0a, 0x0a, 0x54, 0x72, 0x61, 0x63, 0x65, 0x73, 0x44, 0x61, + 0x74, 0x61, 0x12, 0x52, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, + 0x70, 0x61, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6f, 0x70, 0x65, + 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x73, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x53, 0x70, 0x61, 0x6e, 0x73, 0x22, 0xc8, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x73, 0x12, 0x45, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6f, 0x70, 0x65, + 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0x49, 0x0a, 0x0b, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x73, 0x70, 0x61, 0x6e, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, + 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x73, 0x52, 0x0a, + 0x73, 0x63, 0x6f, 0x70, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x55, 0x72, 0x6c, 0x4a, 0x06, 0x08, 0xe8, 0x07, 0x10, 0xe9, + 0x07, 0x22, 0xb0, 0x01, 0x0a, 0x0a, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x73, + 0x12, 0x49, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x33, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, + 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x63, 0x6f, 0x70, 0x65, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x38, 0x0a, 0x05, 0x73, + 0x70, 0x61, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6f, 0x70, 0x65, + 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x52, 0x05, + 0x73, 0x70, 0x61, 0x6e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, + 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x55, 0x72, 0x6c, 0x22, 0xc8, 0x0a, 0x0a, 0x04, 0x53, 0x70, 0x61, 0x6e, 0x12, 0x19, 0x0a, + 0x08, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x70, 0x61, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x70, 0x61, 0x6e, 0x49, + 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x70, 0x61, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x70, 0x61, 0x72, 0x65, + 0x6e, 0x74, 0x53, 0x70, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, + 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x07, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x2b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x70, 0x61, 0x6e, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, + 0x69, 0x6e, 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x06, 0x52, 0x11, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, + 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x2b, 0x0a, 0x12, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x08, 0x20, 0x01, 0x28, 0x06, + 0x52, 0x0f, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, + 0x6f, 0x12, 0x47, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, + 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, + 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x64, 0x72, + 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x16, 0x64, 0x72, + 0x6f, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x40, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x0b, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, + 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, + 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3d, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x6b, + 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, + 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, + 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, + 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x72, 0x6f, 0x70, 0x70, + 0x65, 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x4c, 0x69, 0x6e, + 0x6b, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, + 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, + 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0xc4, 0x01, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, + 0x24, 0x0a, 0x0e, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x6e, 0x61, 0x6e, + 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x06, 0x52, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, + 0x78, 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x61, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, + 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, + 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, + 0x65, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x16, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0xf4, 0x01, 0x0a, + 0x04, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, + 0x12, 0x17, 0x0a, 0x07, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x06, 0x73, 0x70, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, 0x61, + 0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x74, 0x72, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, + 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b, + 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x16, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, 0x0a, + 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x07, 0x52, 0x05, 0x66, 0x6c, + 0x61, 0x67, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x08, 0x53, 0x70, 0x61, 0x6e, 0x4b, 0x69, 0x6e, 0x64, + 0x12, 0x19, 0x0a, 0x15, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, + 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x53, + 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, + 0x4c, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, + 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x10, 0x02, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x50, 0x41, + 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54, 0x10, 0x03, 0x12, + 0x16, 0x0a, 0x12, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x50, 0x52, 0x4f, + 0x44, 0x55, 0x43, 0x45, 0x52, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x50, 0x41, 0x4e, 0x5f, + 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x43, 0x4f, 0x4e, 0x53, 0x55, 0x4d, 0x45, 0x52, 0x10, 0x05, 0x22, + 0xbd, 0x01, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x12, 0x43, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, + 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, + 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x4e, 0x0a, 0x0a, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x55, + 0x53, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x45, 0x54, 0x10, 0x00, 0x12, 0x12, + 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x4f, 0x4b, + 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x4f, 0x44, + 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x2a, + 0x9c, 0x01, 0x0a, 0x09, 0x53, 0x70, 0x61, 0x6e, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x19, 0x0a, + 0x15, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x46, 0x4c, 0x41, 0x47, 0x53, 0x5f, 0x44, 0x4f, 0x5f, 0x4e, + 0x4f, 0x54, 0x5f, 0x55, 0x53, 0x45, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1b, 0x53, 0x50, 0x41, 0x4e, + 0x5f, 0x46, 0x4c, 0x41, 0x47, 0x53, 0x5f, 0x54, 0x52, 0x41, 0x43, 0x45, 0x5f, 0x46, 0x4c, 0x41, + 0x47, 0x53, 0x5f, 0x4d, 0x41, 0x53, 0x4b, 0x10, 0xff, 0x01, 0x12, 0x2a, 0x0a, 0x25, 0x53, 0x50, + 0x41, 0x4e, 0x5f, 0x46, 0x4c, 0x41, 0x47, 0x53, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x45, 0x58, 0x54, + 0x5f, 0x48, 0x41, 0x53, 0x5f, 0x49, 0x53, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4d, + 0x41, 0x53, 0x4b, 0x10, 0x80, 0x02, 0x12, 0x26, 0x0a, 0x21, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x46, + 0x4c, 0x41, 0x47, 0x53, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x45, 0x58, 0x54, 0x5f, 0x49, 0x53, 0x5f, + 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4d, 0x41, 0x53, 0x4b, 0x10, 0x80, 0x04, 0x42, 0x77, + 0x0a, 0x1f, 0x69, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, + 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, + 0x31, 0x42, 0x0a, 0x54, 0x72, 0x61, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, + 0x27, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, + 0x79, 0x2e, 0x69, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6f, 0x74, 0x6c, 0x70, 0x2f, + 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x76, 0x31, 0xaa, 0x02, 0x1c, 0x4f, 0x70, 0x65, 0x6e, 0x54, + 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, + 0x72, 0x61, 0x63, 0x65, 0x2e, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_opentelemetry_proto_trace_v1_trace_proto_rawDescOnce sync.Once + file_opentelemetry_proto_trace_v1_trace_proto_rawDescData = file_opentelemetry_proto_trace_v1_trace_proto_rawDesc +) + +func file_opentelemetry_proto_trace_v1_trace_proto_rawDescGZIP() []byte { + file_opentelemetry_proto_trace_v1_trace_proto_rawDescOnce.Do(func() { + file_opentelemetry_proto_trace_v1_trace_proto_rawDescData = protoimpl.X.CompressGZIP(file_opentelemetry_proto_trace_v1_trace_proto_rawDescData) + }) + return file_opentelemetry_proto_trace_v1_trace_proto_rawDescData +} + +var file_opentelemetry_proto_trace_v1_trace_proto_enumTypes = make([]protoimpl.EnumInfo, 3) +var file_opentelemetry_proto_trace_v1_trace_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_opentelemetry_proto_trace_v1_trace_proto_goTypes = []interface{}{ + (SpanFlags)(0), // 0: opentelemetry.proto.trace.v1.SpanFlags + (Span_SpanKind)(0), // 1: opentelemetry.proto.trace.v1.Span.SpanKind + (Status_StatusCode)(0), // 2: opentelemetry.proto.trace.v1.Status.StatusCode + (*TracesData)(nil), // 3: opentelemetry.proto.trace.v1.TracesData + (*ResourceSpans)(nil), // 4: opentelemetry.proto.trace.v1.ResourceSpans + (*ScopeSpans)(nil), // 5: opentelemetry.proto.trace.v1.ScopeSpans + (*Span)(nil), // 6: opentelemetry.proto.trace.v1.Span + (*Status)(nil), // 7: opentelemetry.proto.trace.v1.Status + (*Span_Event)(nil), // 8: opentelemetry.proto.trace.v1.Span.Event + (*Span_Link)(nil), // 9: opentelemetry.proto.trace.v1.Span.Link + (*v1.Resource)(nil), // 10: opentelemetry.proto.resource.v1.Resource + (*v11.InstrumentationScope)(nil), // 11: opentelemetry.proto.common.v1.InstrumentationScope + (*v11.KeyValue)(nil), // 12: opentelemetry.proto.common.v1.KeyValue +} +var file_opentelemetry_proto_trace_v1_trace_proto_depIdxs = []int32{ + 4, // 0: opentelemetry.proto.trace.v1.TracesData.resource_spans:type_name -> opentelemetry.proto.trace.v1.ResourceSpans + 10, // 1: opentelemetry.proto.trace.v1.ResourceSpans.resource:type_name -> opentelemetry.proto.resource.v1.Resource + 5, // 2: opentelemetry.proto.trace.v1.ResourceSpans.scope_spans:type_name -> opentelemetry.proto.trace.v1.ScopeSpans + 11, // 3: opentelemetry.proto.trace.v1.ScopeSpans.scope:type_name -> opentelemetry.proto.common.v1.InstrumentationScope + 6, // 4: opentelemetry.proto.trace.v1.ScopeSpans.spans:type_name -> opentelemetry.proto.trace.v1.Span + 1, // 5: opentelemetry.proto.trace.v1.Span.kind:type_name -> opentelemetry.proto.trace.v1.Span.SpanKind + 12, // 6: opentelemetry.proto.trace.v1.Span.attributes:type_name -> opentelemetry.proto.common.v1.KeyValue + 8, // 7: opentelemetry.proto.trace.v1.Span.events:type_name -> opentelemetry.proto.trace.v1.Span.Event + 9, // 8: opentelemetry.proto.trace.v1.Span.links:type_name -> opentelemetry.proto.trace.v1.Span.Link + 7, // 9: opentelemetry.proto.trace.v1.Span.status:type_name -> opentelemetry.proto.trace.v1.Status + 2, // 10: opentelemetry.proto.trace.v1.Status.code:type_name -> opentelemetry.proto.trace.v1.Status.StatusCode + 12, // 11: opentelemetry.proto.trace.v1.Span.Event.attributes:type_name -> opentelemetry.proto.common.v1.KeyValue + 12, // 12: opentelemetry.proto.trace.v1.Span.Link.attributes:type_name -> opentelemetry.proto.common.v1.KeyValue + 13, // [13:13] is the sub-list for method output_type + 13, // [13:13] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name +} + +func init() { file_opentelemetry_proto_trace_v1_trace_proto_init() } +func file_opentelemetry_proto_trace_v1_trace_proto_init() { + if File_opentelemetry_proto_trace_v1_trace_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TracesData); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResourceSpans); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ScopeSpans); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Span); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Status); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Span_Event); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_opentelemetry_proto_trace_v1_trace_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Span_Link); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_opentelemetry_proto_trace_v1_trace_proto_rawDesc, + NumEnums: 3, + NumMessages: 7, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_opentelemetry_proto_trace_v1_trace_proto_goTypes, + DependencyIndexes: file_opentelemetry_proto_trace_v1_trace_proto_depIdxs, + EnumInfos: file_opentelemetry_proto_trace_v1_trace_proto_enumTypes, + MessageInfos: file_opentelemetry_proto_trace_v1_trace_proto_msgTypes, + }.Build() + File_opentelemetry_proto_trace_v1_trace_proto = out.File + file_opentelemetry_proto_trace_v1_trace_proto_rawDesc = nil + file_opentelemetry_proto_trace_v1_trace_proto_goTypes = nil + file_opentelemetry_proto_trace_v1_trace_proto_depIdxs = nil +} diff --git a/vendor/gomodules.xyz/jsonpatch/v2/jsonpatch.go b/vendor/gomodules.xyz/jsonpatch/v2/jsonpatch.go index 0d7823b3cd6..d88162ff578 100644 --- a/vendor/gomodules.xyz/jsonpatch/v2/jsonpatch.go +++ b/vendor/gomodules.xyz/jsonpatch/v2/jsonpatch.go @@ -70,12 +70,14 @@ func CreatePatch(a, b []byte) ([]Operation, error) { } var aI interface{} var bI interface{} - err := json.Unmarshal(a, &aI) - if err != nil { + aDec := json.NewDecoder(bytes.NewReader(a)) + aDec.UseNumber() + if err := aDec.Decode(&aI); err != nil { return nil, errBadJSONDoc } - err = json.Unmarshal(b, &bI) - if err != nil { + bDec := json.NewDecoder(bytes.NewReader(b)) + bDec.UseNumber() + if err := bDec.Decode(&bI); err != nil { return nil, errBadJSONDoc } return handleValues(aI, bI, "", []Operation{}) @@ -94,6 +96,11 @@ func matchesValue(av, bv interface{}) bool { if ok && bt == at { return true } + case json.Number: + bt, ok := bv.(json.Number) + if ok && bt == at { + return true + } case float64: bt, ok := bv.(float64) if ok && bt == at { @@ -212,7 +219,7 @@ func handleValues(av, bv interface{}, p string, patch []Operation) ([]Operation, if err != nil { return nil, err } - case string, float64, bool: + case string, float64, bool, json.Number: if !matchesValue(av, bv) { patch = append(patch, NewOperation("replace", p, bv)) } diff --git a/vendor/helm.sh/helm/v3/internal/third_party/dep/fs/fs.go b/vendor/helm.sh/helm/v3/internal/third_party/dep/fs/fs.go index 7cbe8af84ff..d29bb5f8719 100644 --- a/vendor/helm.sh/helm/v3/internal/third_party/dep/fs/fs.go +++ b/vendor/helm.sh/helm/v3/internal/third_party/dep/fs/fs.go @@ -172,28 +172,28 @@ func copyFile(src, dst string) (err error) { in, err := os.Open(src) if err != nil { - return //nolint:nakedret + return } defer in.Close() out, err := os.Create(dst) if err != nil { - return //nolint:nakedret + return } if _, err = io.Copy(out, in); err != nil { out.Close() - return //nolint:nakedret + return } // Check for write errors on Close if err = out.Close(); err != nil { - return //nolint:nakedret + return } si, err := os.Stat(src) if err != nil { - return //nolint:nakedret + return } // Temporary fix for Go < 1.9 @@ -205,7 +205,7 @@ func copyFile(src, dst string) (err error) { } err = os.Chmod(dst, si.Mode()) - return //nolint:nakedret + return } // cloneSymlink will create a new symlink that points to the resolved path of sl. diff --git a/vendor/helm.sh/helm/v3/internal/third_party/k8s.io/kubernetes/deployment/util/deploymentutil.go b/vendor/helm.sh/helm/v3/internal/third_party/k8s.io/kubernetes/deployment/util/deploymentutil.go index 8f8b28fcbe8..ae62d0e6f89 100644 --- a/vendor/helm.sh/helm/v3/internal/third_party/k8s.io/kubernetes/deployment/util/deploymentutil.go +++ b/vendor/helm.sh/helm/v3/internal/third_party/k8s.io/kubernetes/deployment/util/deploymentutil.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util //nolint:revive +package util import ( "context" diff --git a/vendor/helm.sh/helm/v3/internal/version/version.go b/vendor/helm.sh/helm/v3/internal/version/version.go index c704a01491a..4124100ae12 100644 --- a/vendor/helm.sh/helm/v3/internal/version/version.go +++ b/vendor/helm.sh/helm/v3/internal/version/version.go @@ -29,7 +29,7 @@ var ( // // Increment major number for new feature additions and behavioral changes. // Increment minor number for bug fixes and performance enhancements. - version = "v3.20" + version = "v3.17" // metadata is extra build time data metadata = "" diff --git a/vendor/helm.sh/helm/v3/pkg/action/action.go b/vendor/helm.sh/helm/v3/pkg/action/action.go index 9aaf64ca466..fe91de04809 100644 --- a/vendor/helm.sh/helm/v3/pkg/action/action.go +++ b/vendor/helm.sh/helm/v3/pkg/action/action.go @@ -19,7 +19,6 @@ package action import ( "bytes" "fmt" - "io" "os" "path" "path/filepath" @@ -96,9 +95,6 @@ type Configuration struct { Capabilities *chartutil.Capabilities Log func(string, ...interface{}) - - // HookOutputFunc called with container name and returns and expects writer that will receive the log output. - HookOutputFunc func(namespace, pod, container string) io.Writer } // renderResources renders the templates in a chart @@ -126,7 +122,7 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu var err2 error // A `helm template` should not talk to the remote cluster. However, commands with the flag - // `--dry-run` with the value of `false`, `none`, or `server` should try to interact with the cluster. + //`--dry-run` with the value of `false`, `none`, or `server` should try to interact with the cluster. // It may break in interesting and exotic ways because other data (e.g. discovery) is mocked. if interactWithRemote && cfg.RESTClientGetter != nil { restConfig, err := cfg.RESTClientGetter.ToRESTConfig() @@ -426,12 +422,6 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp cfg.KubeClient = kc cfg.Releases = store cfg.Log = log - cfg.HookOutputFunc = func(_, _, _ string) io.Writer { return io.Discard } return nil } - -// SetHookOutputFunc sets the HookOutputFunc on the Configuration. -func (cfg *Configuration) SetHookOutputFunc(hookOutputFunc func(_, _, _ string) io.Writer) { - cfg.HookOutputFunc = hookOutputFunc -} diff --git a/vendor/helm.sh/helm/v3/pkg/action/hooks.go b/vendor/helm.sh/helm/v3/pkg/action/hooks.go index 2aad58593d2..e1cbab5e128 100644 --- a/vendor/helm.sh/helm/v3/pkg/action/hooks.go +++ b/vendor/helm.sh/helm/v3/pkg/action/hooks.go @@ -17,16 +17,10 @@ package action import ( "bytes" - "fmt" - "log" - "slices" "sort" "time" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/pkg/errors" - "gopkg.in/yaml.v3" "helm.sh/helm/v3/pkg/kube" "helm.sh/helm/v3/pkg/release" @@ -48,7 +42,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, // hooke are pre-ordered by kind, so keep order stable sort.Stable(hookByWeight(executingHooks)) - for i, h := range executingHooks { + for _, h := range executingHooks { // Set default delete policy to before-hook-creation if len(h.DeletePolicies) == 0 { // TODO(jlegrone): Only apply before-hook-creation delete policy to run to completion @@ -93,37 +87,20 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, // Mark hook as succeeded or failed if err != nil { h.LastRun.Phase = release.HookPhaseFailed - // If a hook is failed, check the annotation of the hook to determine if we should copy the logs client side - if errOutputting := cfg.outputLogsByPolicy(h, rl.Namespace, release.HookOutputOnFailed); errOutputting != nil { - // We log the error here as we want to propagate the hook failure upwards to the release object. - log.Printf("error outputting logs for hook failure: %v", errOutputting) - } // If a hook is failed, check the annotation of the hook to determine whether the hook should be deleted // under failed condition. If so, then clear the corresponding resource object in the hook - if errDeleting := cfg.deleteHookByPolicy(h, release.HookFailed, timeout); errDeleting != nil { - // We log the error here as we want to propagate the hook failure upwards to the release object. - log.Printf("error deleting the hook resource on hook failure: %v", errDeleting) - } - - // If a hook is failed, check the annotation of the previous successful hooks to determine whether the hooks - // should be deleted under succeeded condition. - if err := cfg.deleteHooksByPolicy(executingHooks[0:i], release.HookSucceeded, timeout); err != nil { + if err := cfg.deleteHookByPolicy(h, release.HookFailed, timeout); err != nil { return err } - return err } h.LastRun.Phase = release.HookPhaseSucceeded } // If all hooks are successful, check the annotation of each hook to determine whether the hook should be deleted - // or output should be logged under succeeded condition. If so, then clear the corresponding resource object in each hook + // under succeeded condition. If so, then clear the corresponding resource object in each hook for i := len(executingHooks) - 1; i >= 0; i-- { h := executingHooks[i] - if err := cfg.outputLogsByPolicy(h, rl.Namespace, release.HookOutputOnSucceeded); err != nil { - // We log here as we still want to attempt hook resource deletion even if output logging fails. - log.Printf("error outputting logs for hook failure: %v", err) - } if err := cfg.deleteHookByPolicy(h, release.HookSucceeded, timeout); err != nil { return err } @@ -161,7 +138,7 @@ func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.Hoo return errors.New(joinErrors(errs)) } - // wait for resources until they are deleted to avoid conflicts + //wait for resources until they are deleted to avoid conflicts if kubeClient, ok := cfg.KubeClient.(kube.InterfaceExt); ok { if err := kubeClient.WaitForDelete(resources, timeout); err != nil { return err @@ -171,17 +148,6 @@ func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.Hoo return nil } -// deleteHooksByPolicy deletes all hooks if the hook policy instructs it to -func (cfg *Configuration) deleteHooksByPolicy(hooks []*release.Hook, policy release.HookDeletePolicy, timeout time.Duration) error { - for _, h := range hooks { - if err := cfg.deleteHookByPolicy(h, policy, timeout); err != nil { - return err - } - } - - return nil -} - // hookHasDeletePolicy determines whether the defined hook deletion policy matches the hook deletion polices // supported by helm. If so, mark the hook as one should be deleted. func hookHasDeletePolicy(h *release.Hook, policy release.HookDeletePolicy) bool { @@ -192,57 +158,3 @@ func hookHasDeletePolicy(h *release.Hook, policy release.HookDeletePolicy) bool } return false } - -// outputLogsByPolicy outputs a pods logs if the hook policy instructs it to -func (cfg *Configuration) outputLogsByPolicy(h *release.Hook, releaseNamespace string, policy release.HookOutputLogPolicy) error { - if !hookHasOutputLogPolicy(h, policy) { - return nil - } - namespace, err := cfg.deriveNamespace(h, releaseNamespace) - if err != nil { - return err - } - switch h.Kind { - case "Job": - return cfg.outputContainerLogsForListOptions(namespace, metav1.ListOptions{LabelSelector: fmt.Sprintf("job-name=%s", h.Name)}) - case "Pod": - return cfg.outputContainerLogsForListOptions(namespace, metav1.ListOptions{FieldSelector: fmt.Sprintf("metadata.name=%s", h.Name)}) - default: - return nil - } -} - -func (cfg *Configuration) outputContainerLogsForListOptions(namespace string, listOptions metav1.ListOptions) error { - // TODO Helm 4: Remove this check when GetPodList and OutputContainerLogsForPodList are moved from InterfaceLogs to Interface - if kubeClient, ok := cfg.KubeClient.(kube.InterfaceLogs); ok { - podList, err := kubeClient.GetPodList(namespace, listOptions) - if err != nil { - return err - } - err = kubeClient.OutputContainerLogsForPodList(podList, namespace, cfg.HookOutputFunc) - return err - } - return nil -} - -func (cfg *Configuration) deriveNamespace(h *release.Hook, namespace string) (string, error) { - tmp := struct { - Metadata struct { - Namespace string - } - }{} - err := yaml.Unmarshal([]byte(h.Manifest), &tmp) - if err != nil { - return "", errors.Wrapf(err, "unable to parse metadata.namespace from kubernetes manifest for output logs hook %s", h.Path) - } - if tmp.Metadata.Namespace == "" { - return namespace, nil - } - return tmp.Metadata.Namespace, nil -} - -// hookHasOutputLogPolicy determines whether the defined hook output log policy matches the hook output log policies -// supported by helm. -func hookHasOutputLogPolicy(h *release.Hook, policy release.HookOutputLogPolicy) bool { - return slices.Contains(h.OutputLogPolicies, policy) -} diff --git a/vendor/helm.sh/helm/v3/pkg/action/install.go b/vendor/helm.sh/helm/v3/pkg/action/install.go index f8f7400054a..ae732f4fd7c 100644 --- a/vendor/helm.sh/helm/v3/pkg/action/install.go +++ b/vendor/helm.sh/helm/v3/pkg/action/install.go @@ -21,7 +21,6 @@ import ( "context" "fmt" "io" - "log/slog" "net/url" "os" "path" @@ -456,11 +455,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource if len(toBeAdopted) == 0 && len(resources) > 0 { _, err = i.cfg.KubeClient.Create(resources) } else if len(resources) > 0 { - if i.TakeOwnership { - _, err = i.cfg.KubeClient.(kube.InterfaceThreeWayMerge).UpdateThreeWayMerge(toBeAdopted, resources, i.Force) - } else { - _, err = i.cfg.KubeClient.Update(toBeAdopted, resources, i.Force) - } + _, err = i.cfg.KubeClient.Update(toBeAdopted, resources, i.Force) } if err != nil { return rel, err @@ -619,7 +614,7 @@ func writeToFile(outputDir string, name string, data string, append bool) error defer f.Close() - _, err = fmt.Fprintf(f, "---\n# Source: %s\n%s\n", name, data) + _, err = f.WriteString(fmt.Sprintf("---\n# Source: %s\n%s\n", name, data)) if err != nil { return err @@ -751,12 +746,6 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) ( version := strings.TrimSpace(c.Version) if _, err := os.Stat(name); err == nil { - // Issue #7862: Helm prioritizes local charts over repository URL. - // This behavior is maintained for backwards compatibility but with a warning. - if c.RepoURL != "" { - slog.Warn("local chart found in current working directory. repository url ignored", "chart", name, "repository", c.RepoURL) - } - abs, err := filepath.Abs(name) if err != nil { return abs, err diff --git a/vendor/helm.sh/helm/v3/pkg/action/pull.go b/vendor/helm.sh/helm/v3/pkg/action/pull.go index a16c9bde5ed..78755312553 100644 --- a/vendor/helm.sh/helm/v3/pkg/action/pull.go +++ b/vendor/helm.sh/helm/v3/pkg/action/pull.go @@ -121,16 +121,15 @@ func (p *Pull) Run(chartRef string) (string, error) { defer os.RemoveAll(dest) } - downloadSourceRef := chartRef if p.RepoURL != "" { chartURL, err := repo.FindChartInAuthAndTLSAndPassRepoURL(p.RepoURL, p.Username, p.Password, chartRef, p.Version, p.CertFile, p.KeyFile, p.CaFile, p.InsecureSkipTLSverify, p.PassCredentialsAll, getter.All(p.Settings)) if err != nil { return out.String(), err } - downloadSourceRef = chartURL + chartRef = chartURL } - saved, v, err := c.DownloadTo(downloadSourceRef, p.Version, dest) + saved, v, err := c.DownloadTo(chartRef, p.Version, dest) if err != nil { return out.String(), err } diff --git a/vendor/helm.sh/helm/v3/pkg/action/registry_login.go b/vendor/helm.sh/helm/v3/pkg/action/registry_login.go index b4e038123a4..cd144e1e706 100644 --- a/vendor/helm.sh/helm/v3/pkg/action/registry_login.go +++ b/vendor/helm.sh/helm/v3/pkg/action/registry_login.go @@ -24,12 +24,11 @@ import ( // RegistryLogin performs a registry login operation. type RegistryLogin struct { - cfg *Configuration - certFile string - keyFile string - caFile string - insecure bool - plainHTTP bool + cfg *Configuration + certFile string + keyFile string + caFile string + insecure bool } type RegistryLoginOpt func(*RegistryLogin) error @@ -42,7 +41,7 @@ func WithCertFile(certFile string) RegistryLoginOpt { } } -// WithInsecure specifies whether to verify certificates. +// WithKeyFile specifies whether to very certificates when communicating. func WithInsecure(insecure bool) RegistryLoginOpt { return func(r *RegistryLogin) error { r.insecure = insecure @@ -66,14 +65,6 @@ func WithCAFile(caFile string) RegistryLoginOpt { } } -// WithPlainHTTPLogin use http rather than https for login. -func WithPlainHTTPLogin(isPlain bool) RegistryLoginOpt { - return func(r *RegistryLogin) error { - r.plainHTTP = isPlain - return nil - } -} - // NewRegistryLogin creates a new RegistryLogin object with the given configuration. func NewRegistryLogin(cfg *Configuration) *RegistryLogin { return &RegistryLogin{ @@ -93,7 +84,5 @@ func (a *RegistryLogin) Run(_ io.Writer, hostname string, username string, passw hostname, registry.LoginOptBasicAuth(username, password), registry.LoginOptInsecure(a.insecure), - registry.LoginOptTLSClientConfig(a.certFile, a.keyFile, a.caFile), - registry.LoginOptPlainText(a.plainHTTP), - ) + registry.LoginOptTLSClientConfig(a.certFile, a.keyFile, a.caFile)) } diff --git a/vendor/helm.sh/helm/v3/pkg/action/rollback.go b/vendor/helm.sh/helm/v3/pkg/action/rollback.go index fcaaab1777c..b0be17d1305 100644 --- a/vendor/helm.sh/helm/v3/pkg/action/rollback.go +++ b/vendor/helm.sh/helm/v3/pkg/action/rollback.go @@ -26,7 +26,6 @@ import ( "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage/driver" helmtime "helm.sh/helm/v3/pkg/time" ) @@ -250,7 +249,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas } deployed, err := r.cfg.Releases.DeployedAll(currentRelease.Name) - if err != nil && !errors.Is(err, driver.ErrNoDeployedReleases) { + if err != nil && !strings.Contains(err.Error(), "has no deployed releases") { return nil, err } // Supersede all previous deployments, see issue #2941. diff --git a/vendor/helm.sh/helm/v3/pkg/action/uninstall.go b/vendor/helm.sh/helm/v3/pkg/action/uninstall.go index c30cb65e297..ac0c4fee8cc 100644 --- a/vendor/helm.sh/helm/v3/pkg/action/uninstall.go +++ b/vendor/helm.sh/helm/v3/pkg/action/uninstall.go @@ -28,7 +28,6 @@ import ( "helm.sh/helm/v3/pkg/kube" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/releaseutil" - "helm.sh/helm/v3/pkg/storage/driver" helmtime "helm.sh/helm/v3/pkg/time" ) @@ -171,19 +170,6 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) u.cfg.Log("uninstall: Failed to store updated release: %s", err) } - // Supersede all previous deployments, see issue #12556 (which is a - // variation on #2941). - deployed, err := u.cfg.Releases.DeployedAll(name) - if err != nil && !errors.Is(err, driver.ErrNoDeployedReleases) { - return nil, err - } - for _, reli := range deployed { - reli.Info.Status = release.StatusSuperseded - if err = u.cfg.Releases.Update(reli); err != nil { - u.cfg.Log("uninstall: Failed to store updated release: %s", err) - } - } - if len(errs) > 0 { return res, errors.Errorf("uninstallation completed with %d error(s): %s", len(errs), joinErrors(errs)) } diff --git a/vendor/helm.sh/helm/v3/pkg/chart/loader/archive.go b/vendor/helm.sh/helm/v3/pkg/chart/loader/archive.go index 6272a564f8d..8bb5493465c 100644 --- a/vendor/helm.sh/helm/v3/pkg/chart/loader/archive.go +++ b/vendor/helm.sh/helm/v3/pkg/chart/loader/archive.go @@ -33,15 +33,6 @@ import ( "helm.sh/helm/v3/pkg/chart" ) -// MaxDecompressedChartSize is the maximum size of a chart archive that will be -// decompressed. This is the decompressed size of all the files. -// The default value is 100 MiB. -var MaxDecompressedChartSize int64 = 100 * 1024 * 1024 // Default 100 MiB - -// MaxDecompressedFileSize is the size of the largest file that Helm will attempt to load. -// The size of the file is the decompressed version of it when it is stored in an archive. -var MaxDecompressedFileSize int64 = 5 * 1024 * 1024 // Default 5 MiB - var drivePathPattern = regexp.MustCompile(`^[a-zA-Z]:/`) // FileLoader loads a chart from a file @@ -128,7 +119,6 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) { files := []*BufferedFile{} tr := tar.NewReader(unzipped) - remainingSize := MaxDecompressedChartSize for { b := bytes.NewBuffer(nil) hd, err := tr.Next() @@ -188,30 +178,10 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) { return nil, errors.New("chart yaml not in base directory") } - if hd.Size > remainingSize { - return nil, fmt.Errorf("decompressed chart is larger than the maximum file size %d", MaxDecompressedChartSize) - } - - if hd.Size > MaxDecompressedFileSize { - return nil, fmt.Errorf("decompressed chart file %q is larger than the maximum file size %d", hd.Name, MaxDecompressedFileSize) - } - - limitedReader := io.LimitReader(tr, remainingSize) - - bytesWritten, err := io.Copy(b, limitedReader) - if err != nil { + if _, err := io.Copy(b, tr); err != nil { return nil, err } - remainingSize -= bytesWritten - // When the bytesWritten are less than the file size it means the limit reader ended - // copying early. Here we report that error. This is important if the last file extracted - // is the one that goes over the limit. It assumes the Size stored in the tar header - // is correct, something many applications do. - if bytesWritten < hd.Size || remainingSize <= 0 { - return nil, fmt.Errorf("decompressed chart is larger than the maximum file size %d", MaxDecompressedChartSize) - } - data := bytes.TrimPrefix(b.Bytes(), utf8bom) files = append(files, &BufferedFile{Name: n, Data: data}) diff --git a/vendor/helm.sh/helm/v3/pkg/chart/loader/directory.go b/vendor/helm.sh/helm/v3/pkg/chart/loader/directory.go index fd8e02e1ac9..9bcbee60cf5 100644 --- a/vendor/helm.sh/helm/v3/pkg/chart/loader/directory.go +++ b/vendor/helm.sh/helm/v3/pkg/chart/loader/directory.go @@ -101,10 +101,6 @@ func LoadDir(dir string) (*chart.Chart, error) { return fmt.Errorf("cannot load irregular file %s as it has file mode type bits set", name) } - if fi.Size() > MaxDecompressedFileSize { - return fmt.Errorf("chart file %q is larger than the maximum file size %d", fi.Name(), MaxDecompressedFileSize) - } - data, err := os.ReadFile(name) if err != nil { return errors.Wrapf(err, "error reading %s", n) diff --git a/vendor/helm.sh/helm/v3/pkg/chartutil/capabilities.go b/vendor/helm.sh/helm/v3/pkg/chartutil/capabilities.go index 4563b094798..48fab0ea40d 100644 --- a/vendor/helm.sh/helm/v3/pkg/chartutil/capabilities.go +++ b/vendor/helm.sh/helm/v3/pkg/chartutil/capabilities.go @@ -19,11 +19,11 @@ import ( "fmt" "strconv" + "github.com/Masterminds/semver/v3" "k8s.io/client-go/kubernetes/scheme" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - k8sversion "k8s.io/apimachinery/pkg/util/version" helmversion "helm.sh/helm/v3/internal/version" ) @@ -84,16 +84,14 @@ func (kv *KubeVersion) GitVersion() string { return kv.Version } // ParseKubeVersion parses kubernetes version from string func ParseKubeVersion(version string) (*KubeVersion, error) { - // Based on the original k8s version parser. - // https://github.com/kubernetes/kubernetes/blob/b266ac2c3e42c2c4843f81e20213d2b2f43e450a/staging/src/k8s.io/apimachinery/pkg/util/version/version.go#L137 - sv, err := k8sversion.ParseGeneric(version) + sv, err := semver.NewVersion(version) if err != nil { return nil, err } return &KubeVersion{ Version: "v" + sv.String(), - Major: strconv.FormatUint(uint64(sv.Major()), 10), - Minor: strconv.FormatUint(uint64(sv.Minor()), 10), + Major: strconv.FormatUint(sv.Major(), 10), + Minor: strconv.FormatUint(sv.Minor(), 10), }, nil } diff --git a/vendor/helm.sh/helm/v3/pkg/chartutil/coalesce.go b/vendor/helm.sh/helm/v3/pkg/chartutil/coalesce.go index df83007fc18..40bce2a68e4 100644 --- a/vendor/helm.sh/helm/v3/pkg/chartutil/coalesce.go +++ b/vendor/helm.sh/helm/v3/pkg/chartutil/coalesce.go @@ -283,31 +283,13 @@ func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, pref if dst == nil { return src } - // Track original non-nil src keys before modifying src - // This lets us distinguish between user nullifying a chart default vs - // user setting nil for a key not in chart defaults. - srcOriginalNonNil := make(map[string]bool) - for key, val := range src { - if val != nil { - srcOriginalNonNil[key] = true - } - } - for key, val := range dst { - if val == nil { - src[key] = nil - } - } // Because dest has higher precedence than src, dest values override src // values. for key, val := range src { fullkey := concatPrefix(prefix, key) - if dv, ok := dst[key]; ok && !merge && dv == nil && srcOriginalNonNil[key] { - // When coalescing (not merging), if dst has nil and src has a non-nil - // value, the user is nullifying a chart default - remove the key. - // But if src also has nil (or key not in src), preserve the nil + if dv, ok := dst[key]; ok && !merge && dv == nil { delete(dst, key) } else if !ok { - // key not in user values, preserve src value (including nil) dst[key] = val } else if istable(val) { if istable(dv) { diff --git a/vendor/helm.sh/helm/v3/pkg/chartutil/create.go b/vendor/helm.sh/helm/v3/pkg/chartutil/create.go index 7adabf7a7c3..321d3d2c047 100644 --- a/vendor/helm.sh/helm/v3/pkg/chartutil/create.go +++ b/vendor/helm.sh/helm/v3/pkg/chartutil/create.go @@ -54,8 +54,6 @@ const ( IgnorefileName = ".helmignore" // IngressFileName is the name of the example ingress file. IngressFileName = TemplatesDir + sep + "ingress.yaml" - // HTTPRouteFileName is the name of the example HTTPRoute file. - HTTPRouteFileName = TemplatesDir + sep + "httproute.yaml" // DeploymentName is the name of the example deployment file. DeploymentName = TemplatesDir + sep + "deployment.yaml" // ServiceName is the name of the example service file. @@ -179,44 +177,6 @@ ingress: # hosts: # - chart-example.local -# -- Expose the service via gateway-api HTTPRoute -# Requires Gateway API resources and suitable controller installed within the cluster -# (see: https://gateway-api.sigs.k8s.io/guides/) -httpRoute: - # HTTPRoute enabled. - enabled: false - # HTTPRoute annotations. - annotations: {} - # Which Gateways this Route is attached to. - parentRefs: - - name: gateway - sectionName: http - # namespace: default - # Hostnames matching HTTP header. - hostnames: - - chart-example.local - # List of rules and filters applied. - rules: - - matches: - - path: - type: PathPrefix - value: /headers - # filters: - # - type: RequestHeaderModifier - # requestHeaderModifier: - # set: - # - name: My-Overwrite-Header - # value: this-is-the-only-value - # remove: - # - User-Agent - # - matches: - # - path: - # type: PathPrefix - # value: /echo - # headers: - # - name: version - # value: v2 - resources: {} # We usually recommend not to specify default resources and to leave this as a conscious # choice for the user. This also increases chances charts run on environments with little @@ -337,46 +297,6 @@ spec: {{- end }} ` -const defaultHTTPRoute = `{{- if .Values.httpRoute.enabled -}} -{{- $fullName := include ".fullname" . -}} -{{- $svcPort := .Values.service.port -}} -apiVersion: gateway.networking.k8s.io/v1 -kind: HTTPRoute -metadata: - name: {{ $fullName }} - labels: - {{- include ".labels" . | nindent 4 }} - {{- with .Values.httpRoute.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - parentRefs: - {{- with .Values.httpRoute.parentRefs }} - {{- toYaml . | nindent 4 }} - {{- end }} - {{- with .Values.httpRoute.hostnames }} - hostnames: - {{- toYaml . | nindent 4 }} - {{- end }} - rules: - {{- range .Values.httpRoute.rules }} - {{- with .matches }} - - matches: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .filters }} - filters: - {{- toYaml . | nindent 8 }} - {{- end }} - backendRefs: - - name: {{ $fullName }} - port: {{ $svcPort }} - weight: 1 - {{- end }} -{{- end }} -` - const defaultDeployment = `apiVersion: apps/v1 kind: Deployment metadata: @@ -524,20 +444,7 @@ spec: ` const defaultNotes = `1. Get the application URL by running these commands: -{{- if .Values.httpRoute.enabled }} -{{- if .Values.httpRoute.hostnames }} - export APP_HOSTNAME={{ .Values.httpRoute.hostnames | first }} -{{- else }} - export APP_HOSTNAME=$(kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o jsonpath="{.spec.listeners[0].hostname}") - {{- end }} -{{- if and .Values.httpRoute.rules (first .Values.httpRoute.rules).matches (first (first .Values.httpRoute.rules).matches).path.value }} - echo "Visit http://$APP_HOSTNAME{{ (first (first .Values.httpRoute.rules).matches).path.value }} to use your application" - - NOTE: Your HTTPRoute depends on the listener configuration of your gateway and your HTTPRoute rules. - The rules can be set for path, method, header and query parameters. - You can check the gateway configuration with 'kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o yaml' -{{- end }} -{{- else if .Values.ingress.enabled }} +{{- if .Values.ingress.enabled }} {{- range $host := .Values.ingress.hosts }} {{- range .paths }} http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} @@ -751,11 +658,6 @@ func Create(name, dir string) (string, error) { path: filepath.Join(cdir, IngressFileName), content: transform(defaultIngress, name), }, - { - // httproute.yaml - path: filepath.Join(cdir, HTTPRouteFileName), - content: transform(defaultHTTPRoute, name), - }, { // deployment.yaml path: filepath.Join(cdir, DeploymentName), diff --git a/vendor/helm.sh/helm/v3/pkg/chartutil/dependencies.go b/vendor/helm.sh/helm/v3/pkg/chartutil/dependencies.go index 37452cec7a3..36a34192728 100644 --- a/vendor/helm.sh/helm/v3/pkg/chartutil/dependencies.go +++ b/vendor/helm.sh/helm/v3/pkg/chartutil/dependencies.go @@ -16,7 +16,6 @@ limitations under the License. package chartutil import ( - "fmt" "log" "strings" @@ -256,8 +255,8 @@ func processImportValues(c *chart.Chart, merge bool) error { for _, riv := range r.ImportValues { switch iv := riv.(type) { case map[string]interface{}: - child := fmt.Sprintf("%v", iv["child"]) - parent := fmt.Sprintf("%v", iv["parent"]) + child := iv["child"].(string) + parent := iv["parent"].(string) outiv = append(outiv, map[string]string{ "child": child, diff --git a/vendor/helm.sh/helm/v3/pkg/chartutil/expand.go b/vendor/helm.sh/helm/v3/pkg/chartutil/expand.go index ac59f257533..7ae1ae6fab8 100644 --- a/vendor/helm.sh/helm/v3/pkg/chartutil/expand.go +++ b/vendor/helm.sh/helm/v3/pkg/chartutil/expand.go @@ -52,9 +52,6 @@ func Expand(dir string, r io.Reader) error { } // Find the base directory - // The directory needs to be cleaned prior to passing to SecureJoin or the location may end up - // being wrong or returning an error. This was introduced in v0.4.0. - dir = filepath.Clean(dir) chartdir, err := securejoin.SecureJoin(dir, chartName) if err != nil { return err diff --git a/vendor/helm.sh/helm/v3/pkg/chartutil/jsonschema.go b/vendor/helm.sh/helm/v3/pkg/chartutil/jsonschema.go index 632e6dfd191..7b9768fd3cc 100644 --- a/vendor/helm.sh/helm/v3/pkg/chartutil/jsonschema.go +++ b/vendor/helm.sh/helm/v3/pkg/chartutil/jsonschema.go @@ -18,64 +18,20 @@ package chartutil import ( "bytes" - "crypto/tls" - "errors" "fmt" - "log" "strings" - "sync" - "time" - "github.com/santhosh-tekuri/jsonschema/v6" + "github.com/pkg/errors" + "github.com/xeipuuv/gojsonschema" + "sigs.k8s.io/yaml" - "net/http" - - "helm.sh/helm/v3/internal/version" "helm.sh/helm/v3/pkg/chart" ) -// HTTPURLLoader implements a loader for HTTP/HTTPS URLs -type HTTPURLLoader http.Client - -func (l *HTTPURLLoader) Load(urlStr string) (any, error) { - client := (*http.Client)(l) - - req, err := http.NewRequest(http.MethodGet, urlStr, nil) - if err != nil { - return nil, fmt.Errorf("failed to create HTTP request for %s: %w", urlStr, err) - } - req.Header.Set("User-Agent", version.GetUserAgent()) - - resp, err := client.Do(req) - if err != nil { - return nil, fmt.Errorf("HTTP request failed for %s: %w", urlStr, err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("HTTP request to %s returned status %d (%s)", urlStr, resp.StatusCode, http.StatusText(resp.StatusCode)) - } - - return jsonschema.UnmarshalJSON(resp.Body) -} - -// newHTTPURLLoader creates a HTTP URL loader with proxy support. -func newHTTPURLLoader() *HTTPURLLoader { - httpLoader := HTTPURLLoader(http.Client{ - Timeout: 15 * time.Second, - Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - TLSClientConfig: &tls.Config{}, - }, - }) - return &httpLoader -} - // ValidateAgainstSchema checks that values does not violate the structure laid out in schema func ValidateAgainstSchema(chrt *chart.Chart, values map[string]interface{}) error { var sb strings.Builder if chrt.Schema != nil { - err := ValidateAgainstSingleSchema(values, chrt.Schema) if err != nil { sb.WriteString(fmt.Sprintf("%s:\n", chrt.Name())) @@ -83,21 +39,9 @@ func ValidateAgainstSchema(chrt *chart.Chart, values map[string]interface{}) err } } + // For each dependency, recursively call this function with the coalesced values for _, subchart := range chrt.Dependencies() { - raw, exists := values[subchart.Name()] - if !exists || raw == nil { - // No values provided for this subchart; nothing to validate - continue - } - - subchartValues, ok := raw.(map[string]any) - if !ok { - sb.WriteString(fmt.Sprintf( - "%s:\ninvalid type for values: expected object (map), got %T\n", - subchart.Name(), raw, - )) - continue - } + subchartValues := values[subchart.Name()].(map[string]interface{}) if err := ValidateAgainstSchema(subchart, subchartValues); err != nil { sb.WriteString(err.Error()) } @@ -118,78 +62,32 @@ func ValidateAgainstSingleSchema(values Values, schemaJSON []byte) (reterr error } }() - // This unmarshal function leverages UseNumber() for number precision. The parser - // used for values does this as well. - schema, err := jsonschema.UnmarshalJSON(bytes.NewReader(schemaJSON)) + valuesData, err := yaml.Marshal(values) if err != nil { return err } - - // Configure compiler with loaders for different URL schemes - loader := jsonschema.SchemeURLLoader{ - "file": jsonschema.FileLoader{}, - "http": newHTTPURLLoader(), - "https": newHTTPURLLoader(), - "urn": urnLoader{}, - } - - compiler := jsonschema.NewCompiler() - compiler.UseLoader(loader) - err = compiler.AddResource("file:///values.schema.json", schema) + valuesJSON, err := yaml.YAMLToJSON(valuesData) if err != nil { return err } + if bytes.Equal(valuesJSON, []byte("null")) { + valuesJSON = []byte("{}") + } + schemaLoader := gojsonschema.NewBytesLoader(schemaJSON) + valuesLoader := gojsonschema.NewBytesLoader(valuesJSON) - validator, err := compiler.Compile("file:///values.schema.json") + result, err := gojsonschema.Validate(schemaLoader, valuesLoader) if err != nil { return err } - err = validator.Validate(values.AsMap()) - if err != nil { - return JSONSchemaValidationError{err} + if !result.Valid() { + var sb strings.Builder + for _, desc := range result.Errors() { + sb.WriteString(fmt.Sprintf("- %s\n", desc)) + } + return errors.New(sb.String()) } return nil } - -type JSONSchemaValidationError struct { - embeddedErr error -} - -func (e JSONSchemaValidationError) Error() string { - errStr := e.embeddedErr.Error() - - errStr = strings.TrimPrefix(errStr, "jsonschema validation failed with 'file:///values.schema.json#'\n") - - return errStr + "\n" -} - -// URNResolverFunc allows SDK to plug a URN resolver. It must return a -// schema document compatible with the validator (e.g., result of -// jsonschema.UnmarshalJSON). -type URNResolverFunc func(urn string) (any, error) - -// URNResolver is the default resolver used by the URN loader. By default it -// returns a clear error. -var URNResolver URNResolverFunc = func(urn string) (any, error) { - return nil, fmt.Errorf("URN not resolved: %s", urn) -} - -// urnLoader implements resolution for the urn: scheme by delegating to -// URNResolver. If unresolved, it logs a warning and returns a permissive -// boolean-true schema to avoid hard failures (back-compat behavior). -type urnLoader struct{} - -// warnedURNs ensures we log the unresolved-URN warning only once per URN. -var warnedURNs sync.Map - -func (l urnLoader) Load(urlStr string) (any, error) { - if doc, err := URNResolver(urlStr); err == nil && doc != nil { - return doc, nil - } - if _, loaded := warnedURNs.LoadOrStore(urlStr, struct{}{}); !loaded { - log.Printf("WARNING: unresolved URN reference ignored; using permissive schema: %s", urlStr) - } - return jsonschema.UnmarshalJSON(strings.NewReader("true")) -} diff --git a/vendor/helm.sh/helm/v3/pkg/downloader/manager.go b/vendor/helm.sh/helm/v3/pkg/downloader/manager.go index cc7850aae4b..ec4056d2753 100644 --- a/vendor/helm.sh/helm/v3/pkg/downloader/manager.go +++ b/vendor/helm.sh/helm/v3/pkg/downloader/manager.go @@ -852,20 +852,6 @@ func writeLock(chartpath string, lock *chart.Lock, legacyLockfile bool) error { lockfileName = "requirements.lock" } dest := filepath.Join(chartpath, lockfileName) - - info, err := os.Lstat(dest) - if err != nil && !os.IsNotExist(err) { - return fmt.Errorf("error getting info for %q: %w", dest, err) - } else if err == nil { - if info.Mode()&os.ModeSymlink != 0 { - link, err := os.Readlink(dest) - if err != nil { - return fmt.Errorf("error reading symlink for %q: %w", dest, err) - } - return fmt.Errorf("the %s file is a symlink to %q", lockfileName, link) - } - } - return os.WriteFile(dest, data, 0644) } diff --git a/vendor/helm.sh/helm/v3/pkg/engine/lookup_func.go b/vendor/helm.sh/helm/v3/pkg/engine/lookup_func.go index 3a859e8ffb0..75e85098d16 100644 --- a/vendor/helm.sh/helm/v3/pkg/engine/lookup_func.go +++ b/vendor/helm.sh/helm/v3/pkg/engine/lookup_func.go @@ -38,7 +38,7 @@ type lookupFunc = func(apiversion string, resource string, namespace string, nam // // This function is considered deprecated, and will be renamed in Helm 4. It will no // longer be a public function. -func NewLookupFunction(config *rest.Config) lookupFunc { //nolint:revive +func NewLookupFunction(config *rest.Config) lookupFunc { return newLookupFunction(clientProviderFromConfig{config: config}) } diff --git a/vendor/helm.sh/helm/v3/pkg/getter/getter.go b/vendor/helm.sh/helm/v3/pkg/getter/getter.go index 7c73a46a95d..1acb2093dc4 100644 --- a/vendor/helm.sh/helm/v3/pkg/getter/getter.go +++ b/vendor/helm.sh/helm/v3/pkg/getter/getter.go @@ -196,32 +196,24 @@ const ( var defaultOptions = []Option{WithTimeout(time.Second * DefaultHTTPTimeout)} -func Getters(extraOpts ...Option) Providers { - return Providers{ - Provider{ - Schemes: []string{"http", "https"}, - New: func(options ...Option) (Getter, error) { - options = append(options, defaultOptions...) - options = append(options, extraOpts...) - return NewHTTPGetter(options...) - }, - }, - Provider{ - Schemes: []string{registry.OCIScheme}, - New: func(options ...Option) (Getter, error) { - options = append(options, defaultOptions...) - options = append(options, extraOpts...) - return NewOCIGetter(options...) - }, - }, - } +var httpProvider = Provider{ + Schemes: []string{"http", "https"}, + New: func(options ...Option) (Getter, error) { + options = append(options, defaultOptions...) + return NewHTTPGetter(options...) + }, +} + +var ociProvider = Provider{ + Schemes: []string{registry.OCIScheme}, + New: NewOCIGetter, } // All finds all of the registered getters as a list of Provider instances. // Currently, the built-in getters and the discovered plugins with downloader // notations are collected. -func All(settings *cli.EnvSettings, opts ...Option) Providers { - result := Getters(opts...) +func All(settings *cli.EnvSettings) Providers { + result := Providers{httpProvider, ociProvider} pluginDownloaders, _ := collectPlugins(settings) result = append(result, pluginDownloaders...) return result diff --git a/vendor/helm.sh/helm/v3/pkg/getter/httpgetter.go b/vendor/helm.sh/helm/v3/pkg/getter/httpgetter.go index d9ca90759e7..df3dcd91093 100644 --- a/vendor/helm.sh/helm/v3/pkg/getter/httpgetter.go +++ b/vendor/helm.sh/helm/v3/pkg/getter/httpgetter.go @@ -26,6 +26,7 @@ import ( "github.com/pkg/errors" "helm.sh/helm/v3/internal/tlsutil" + "helm.sh/helm/v3/internal/urlutil" "helm.sh/helm/v3/internal/version" ) @@ -132,6 +133,12 @@ func (g *HTTPGetter) httpClient() (*http.Client, error) { return nil, errors.Wrap(err, "can't create TLS config for client") } + sni, err := urlutil.ExtractHostname(g.opts.url) + if err != nil { + return nil, err + } + tlsConf.ServerName = sni + g.transport.TLSClientConfig = tlsConf } diff --git a/vendor/helm.sh/helm/v3/pkg/kube/client.go b/vendor/helm.sh/helm/v3/pkg/kube/client.go index 6e71421199e..5f16f2bfb6d 100644 --- a/vendor/helm.sh/helm/v3/pkg/kube/client.go +++ b/vendor/helm.sh/helm/v3/pkg/kube/client.go @@ -46,8 +46,6 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/jsonmergepatch" - "k8s.io/apimachinery/pkg/util/mergepatch" "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/watch" "k8s.io/cli-runtime/pkg/genericclioptions" @@ -85,7 +83,7 @@ type Client struct { // Namespace allows to bypass the kubeconfig file for the choice of the namespace Namespace string - kubeClient kubernetes.Interface + kubeClient *kubernetes.Clientset } func init() { @@ -113,7 +111,7 @@ func New(getter genericclioptions.RESTClientGetter) *Client { var nopLogger = func(_ string, _ ...interface{}) {} // getKubeClient get or create a new KubernetesClientSet -func (c *Client) getKubeClient() (kubernetes.Interface, error) { +func (c *Client) getKubeClient() (*kubernetes.Clientset, error) { var err error if c.kubeClient == nil { c.kubeClient, err = c.Factory.KubernetesClientSet() @@ -133,7 +131,7 @@ func (c *Client) IsReachable() error { if err != nil { return errors.Wrap(err, "Kubernetes cluster unreachable") } - if _, err := client.Discovery().ServerVersion(); err != nil { + if _, err := client.ServerVersion(); err != nil { return errors.Wrap(err, "Kubernetes cluster unreachable") } return nil @@ -381,7 +379,14 @@ func (c *Client) BuildTable(reader io.Reader, validate bool) (ResourceList, erro return result, scrubValidationError(err) } -func (c *Client) update(original, target ResourceList, force, threeWayMerge bool) (*Result, error) { +// Update takes the current list of objects and target list of objects and +// creates resources that don't already exist, updates resources that have been +// modified in the target configuration, and deletes resources from the current +// configuration that are not present in the target configuration. If an error +// occurs, a Result will still be returned with the error, containing all +// resource updates, creations, and deletions that were attempted. These can be +// used for cleanup or other logging purposes. +func (c *Client) Update(original, target ResourceList, force bool) (*Result, error) { updateErrors := []string{} res := &Result{} @@ -416,7 +421,7 @@ func (c *Client) update(original, target ResourceList, force, threeWayMerge bool return errors.Errorf("no %s with the name %q found", kind, info.Name) } - if err := updateResource(c, info, originalInfo.Object, force, threeWayMerge); err != nil { + if err := updateResource(c, info, originalInfo.Object, force); err != nil { c.Log("error updating the resource %q:\n\t %v", info.Name, err) updateErrors = append(updateErrors, err.Error()) } @@ -457,31 +462,6 @@ func (c *Client) update(original, target ResourceList, force, threeWayMerge bool return res, nil } -// Update takes the current list of objects and target list of objects and -// creates resources that don't already exist, updates resources that have been -// modified in the target configuration, and deletes resources from the current -// configuration that are not present in the target configuration. If an error -// occurs, a Result will still be returned with the error, containing all -// resource updates, creations, and deletions that were attempted. These can be -// used for cleanup or other logging purposes. -// -// The difference to Update is that UpdateThreeWayMerge does a three-way-merge -// for unstructured objects. -func (c *Client) UpdateThreeWayMerge(original, target ResourceList, force bool) (*Result, error) { - return c.update(original, target, force, true) -} - -// Update takes the current list of objects and target list of objects and -// creates resources that don't already exist, updates resources that have been -// modified in the target configuration, and deletes resources from the current -// configuration that are not present in the target configuration. If an error -// occurs, a Result will still be returned with the error, containing all -// resource updates, creations, and deletions that were attempted. These can be -// used for cleanup or other logging purposes. -func (c *Client) Update(original, target ResourceList, force bool) (*Result, error) { - return c.update(original, target, force, false) -} - // Delete deletes Kubernetes resources specified in the resources list with // background cascade deletion. It will attempt to delete all resources even // if one or more fail and collect any errors. All successfully deleted items @@ -637,7 +617,7 @@ func deleteResource(info *resource.Info, policy metav1.DeletionPropagation) erro }) } -func createPatch(target *resource.Info, current runtime.Object, threeWayMergeForUnstructured bool) ([]byte, types.PatchType, error) { +func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.PatchType, error) { oldData, err := json.Marshal(current) if err != nil { return nil, types.StrategicMergePatchType, errors.Wrap(err, "serializing current configuration") @@ -665,7 +645,7 @@ func createPatch(target *resource.Info, current runtime.Object, threeWayMergeFor // Unstructured objects, such as CRDs, may not have a not registered error // returned from ConvertToVersion. Anything that's unstructured should - // use generic JSON merge patch. Strategic Merge Patch is not supported + // use the jsonpatch.CreateMergePatch. Strategic Merge Patch is not supported // on objects like CRDs. _, isUnstructured := versionedObject.(runtime.Unstructured) @@ -673,19 +653,6 @@ func createPatch(target *resource.Info, current runtime.Object, threeWayMergeFor _, isCRD := versionedObject.(*apiextv1beta1.CustomResourceDefinition) if isUnstructured || isCRD { - if threeWayMergeForUnstructured { - // from https://github.com/kubernetes/kubectl/blob/b83b2ec7d15f286720bccf7872b5c72372cb8e80/pkg/cmd/apply/patcher.go#L129 - preconditions := []mergepatch.PreconditionFunc{ - mergepatch.RequireKeyUnchanged("apiVersion"), - mergepatch.RequireKeyUnchanged("kind"), - mergepatch.RequireMetadataKeyUnchanged("name"), - } - patch, err := jsonmergepatch.CreateThreeWayJSONMergePatch(oldData, newData, currentData, preconditions...) - if err != nil && mergepatch.IsPreconditionFailed(err) { - err = fmt.Errorf("%w: at least one field was changed: apiVersion, kind or name", err) - } - return patch, types.MergePatchType, err - } // fall back to generic JSON merge patch patch, err := jsonpatch.CreateMergePatch(oldData, newData) return patch, types.MergePatchType, err @@ -700,7 +667,7 @@ func createPatch(target *resource.Info, current runtime.Object, threeWayMergeFor return patch, types.StrategicMergePatchType, err } -func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force, threeWayMergeForUnstructured bool) error { +func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool) error { var ( obj runtime.Object helper = resource.NewHelper(target.Client, target.Mapping).WithFieldManager(getManagedFieldsManager()) @@ -716,7 +683,7 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, } c.Log("Replaced %q with kind %s for kind %s", target.Name, currentObj.GetObjectKind().GroupVersionKind().Kind, kind) } else { - patch, patchType, err := createPatch(target, currentObj, threeWayMergeForUnstructured) + patch, patchType, err := createPatch(target, currentObj) if err != nil { return errors.Wrap(err, "failed to create patch") } @@ -845,48 +812,6 @@ func (c *Client) waitForPodSuccess(obj runtime.Object, name string) (bool, error return false, nil } -// GetPodList uses the kubernetes interface to get the list of pods filtered by listOptions -func (c *Client) GetPodList(namespace string, listOptions metav1.ListOptions) (*v1.PodList, error) { - podList, err := c.kubeClient.CoreV1().Pods(namespace).List(context.Background(), listOptions) - if err != nil { - return nil, fmt.Errorf("failed to get pod list with options: %+v with error: %v", listOptions, err) - } - return podList, nil -} - -// OutputContainerLogsForPodList is a helper that outputs logs for a list of pods -func (c *Client) OutputContainerLogsForPodList(podList *v1.PodList, namespace string, writerFunc func(namespace, pod, container string) io.Writer) error { - for _, pod := range podList.Items { - for _, container := range pod.Spec.Containers { - options := &v1.PodLogOptions{ - Container: container.Name, - } - request := c.kubeClient.CoreV1().Pods(namespace).GetLogs(pod.Name, options) - err2 := copyRequestStreamToWriter(request, pod.Name, container.Name, writerFunc(namespace, pod.Name, container.Name)) - if err2 != nil { - return err2 - } - } - } - return nil -} - -func copyRequestStreamToWriter(request *rest.Request, podName, containerName string, writer io.Writer) error { - readCloser, err := request.Stream(context.Background()) - if err != nil { - return errors.Errorf("Failed to stream pod logs for pod: %s, container: %s", podName, containerName) - } - defer readCloser.Close() - _, err = io.Copy(writer, readCloser) - if err != nil { - return errors.Errorf("Failed to copy IO from logs for pod: %s, container: %s", podName, containerName) - } - if err != nil { - return errors.Errorf("Failed to close reader for pod: %s, container: %s", podName, containerName) - } - return nil -} - // scrubValidationError removes kubectl info from the message. func scrubValidationError(err error) error { if err == nil { diff --git a/vendor/helm.sh/helm/v3/pkg/kube/fake/fake.go b/vendor/helm.sh/helm/v3/pkg/kube/fake/fake.go index 852a3e015df..267020d5724 100644 --- a/vendor/helm.sh/helm/v3/pkg/kube/fake/fake.go +++ b/vendor/helm.sh/helm/v3/pkg/kube/fake/fake.go @@ -44,7 +44,6 @@ type FailingKubeClient struct { BuildError error BuildTableError error BuildDummy bool - DummyResources kube.ResourceList BuildUnstructuredError error WaitAndGetCompletedPodPhaseError error WaitDuration time.Duration @@ -115,22 +114,11 @@ func (f *FailingKubeClient) Update(r, modified kube.ResourceList, ignoreMe bool) return f.PrintingKubeClient.Update(r, modified, ignoreMe) } -// Update returns the configured error if set or prints -func (f *FailingKubeClient) UpdateThreeWayMerge(r, modified kube.ResourceList, ignoreMe bool) (*kube.Result, error) { - if f.UpdateError != nil { - return &kube.Result{}, f.UpdateError - } - return f.PrintingKubeClient.Update(r, modified, ignoreMe) -} - // Build returns the configured error if set or prints func (f *FailingKubeClient) Build(r io.Reader, _ bool) (kube.ResourceList, error) { if f.BuildError != nil { return []*resource.Info{}, f.BuildError } - if f.DummyResources != nil { - return f.DummyResources, nil - } if f.BuildDummy { return createDummyResourceList(), nil } diff --git a/vendor/helm.sh/helm/v3/pkg/kube/fake/printer.go b/vendor/helm.sh/helm/v3/pkg/kube/fake/printer.go index 95c89e0fd52..cc2c84b40b8 100644 --- a/vendor/helm.sh/helm/v3/pkg/kube/fake/printer.go +++ b/vendor/helm.sh/helm/v3/pkg/kube/fake/printer.go @@ -17,7 +17,6 @@ limitations under the License. package fake import ( - "fmt" "io" "strings" "time" @@ -33,8 +32,7 @@ import ( // PrintingKubeClient implements KubeClient, but simply prints the reader to // the given output. type PrintingKubeClient struct { - Out io.Writer - LogOutput io.Writer + Out io.Writer } // IsReachable checks if the cluster is reachable @@ -118,17 +116,6 @@ func (p *PrintingKubeClient) WaitAndGetCompletedPodPhase(_ string, _ time.Durati return v1.PodSucceeded, nil } -// GetPodList implements KubeClient GetPodList. -func (p *PrintingKubeClient) GetPodList(_ string, _ metav1.ListOptions) (*v1.PodList, error) { - return &v1.PodList{}, nil -} - -// OutputContainerLogsForPodList implements KubeClient OutputContainerLogsForPodList. -func (p *PrintingKubeClient) OutputContainerLogsForPodList(_ *v1.PodList, someNamespace string, _ func(namespace, pod, container string) io.Writer) error { - _, err := io.Copy(p.LogOutput, strings.NewReader(fmt.Sprintf("attempted to output logs for namespace: %s", someNamespace))) - return err -} - // DeleteWithPropagationPolicy implements KubeClient delete. // // It only prints out the content to be deleted. diff --git a/vendor/helm.sh/helm/v3/pkg/kube/interface.go b/vendor/helm.sh/helm/v3/pkg/kube/interface.go index db7591f6503..ce42ed9501d 100644 --- a/vendor/helm.sh/helm/v3/pkg/kube/interface.go +++ b/vendor/helm.sh/helm/v3/pkg/kube/interface.go @@ -72,7 +72,7 @@ type Interface interface { IsReachable() error } -// InterfaceExt was introduced to avoid breaking backwards compatibility for Interface implementers. +// InterfaceExt is introduced to avoid breaking backwards compatibility for Interface implementers. // // TODO Helm 4: Remove InterfaceExt and integrate its method(s) into the Interface. type InterfaceExt interface { @@ -80,29 +80,11 @@ type InterfaceExt interface { WaitForDelete(resources ResourceList, timeout time.Duration) error } -// InterfaceThreeWayMerge was introduced to avoid breaking backwards compatibility for Interface implementers. -// -// TODO Helm 4: Remove InterfaceThreeWayMerge and integrate its method(s) into the Interface. -type InterfaceThreeWayMerge interface { - UpdateThreeWayMerge(original, target ResourceList, force bool) (*Result, error) -} - -// InterfaceLogs was introduced to avoid breaking backwards compatibility for Interface implementers. -// -// TODO Helm 4: Remove InterfaceLogs and integrate its method(s) into the Interface. -type InterfaceLogs interface { - // GetPodList list all pods that match the specified listOptions - GetPodList(namespace string, listOptions metav1.ListOptions) (*v1.PodList, error) - - // OutputContainerLogsForPodList output the logs for a pod list - OutputContainerLogsForPodList(podList *v1.PodList, namespace string, writerFunc func(namespace, pod, container string) io.Writer) error -} - // InterfaceDeletionPropagation is introduced to avoid breaking backwards compatibility for Interface implementers. // // TODO Helm 4: Remove InterfaceDeletionPropagation and integrate its method(s) into the Interface. type InterfaceDeletionPropagation interface { - // DeleteWithPropagationPolicy destroys one or more resources. The deletion propagation is handled as per the given deletion propagation value. + // Delete destroys one or more resources. The deletion propagation is handled as per the given deletion propagation value. DeleteWithPropagationPolicy(resources ResourceList, policy metav1.DeletionPropagation) (*Result, []error) } @@ -130,7 +112,5 @@ type InterfaceResources interface { var _ Interface = (*Client)(nil) var _ InterfaceExt = (*Client)(nil) -var _ InterfaceThreeWayMerge = (*Client)(nil) -var _ InterfaceLogs = (*Client)(nil) var _ InterfaceDeletionPropagation = (*Client)(nil) var _ InterfaceResources = (*Client)(nil) diff --git a/vendor/helm.sh/helm/v3/pkg/kube/wait.go b/vendor/helm.sh/helm/v3/pkg/kube/wait.go index c602004ad4f..bdafc825564 100644 --- a/vendor/helm.sh/helm/v3/pkg/kube/wait.go +++ b/vendor/helm.sh/helm/v3/pkg/kube/wait.go @@ -49,7 +49,6 @@ type waiter struct { func (w *waiter) waitForResources(created ResourceList) error { w.log("beginning wait for %d resources with timeout of %v", len(created), w.timeout) - startTime := time.Now() ctx, cancel := context.WithTimeout(context.Background(), w.timeout) defer cancel() @@ -58,7 +57,7 @@ func (w *waiter) waitForResources(created ResourceList) error { numberOfErrors[i] = 0 } - err := wait.PollUntilContextCancel(ctx, 2*time.Second, true, func(ctx context.Context) (bool, error) { + return wait.PollUntilContextCancel(ctx, 2*time.Second, true, func(ctx context.Context) (bool, error) { waitRetries := 30 for i, v := range created { ready, err := w.c.IsReady(ctx, v) @@ -79,15 +78,6 @@ func (w *waiter) waitForResources(created ResourceList) error { } return true, nil }) - - elapsed := time.Since(startTime).Round(time.Second) - if err != nil { - w.log("wait for resources failed after %v: %v", elapsed, err) - } else { - w.log("wait for resources succeeded within %v", elapsed) - } - - return err } func (w *waiter) isRetryableError(err error, resource *resource.Info) bool { diff --git a/vendor/helm.sh/helm/v3/pkg/lint/rules/chartfile.go b/vendor/helm.sh/helm/v3/pkg/lint/rules/chartfile.go index 555ec71baf0..910602b7df5 100644 --- a/vendor/helm.sh/helm/v3/pkg/lint/rules/chartfile.go +++ b/vendor/helm.sh/helm/v3/pkg/lint/rules/chartfile.go @@ -151,9 +151,6 @@ func validateChartVersion(cf *chart.Metadata) error { func validateChartMaintainer(cf *chart.Metadata) error { for _, maintainer := range cf.Maintainers { - if maintainer == nil { - return errors.New("a maintainer entry is empty") - } if maintainer.Name == "" { return errors.New("each maintainer requires a name") } else if maintainer.Email != "" && !govalidator.IsEmail(maintainer.Email) { diff --git a/vendor/helm.sh/helm/v3/pkg/plugin/hooks.go b/vendor/helm.sh/helm/v3/pkg/plugin/hooks.go index 34d3163a406..e3481515f06 100644 --- a/vendor/helm.sh/helm/v3/pkg/plugin/hooks.go +++ b/vendor/helm.sh/helm/v3/pkg/plugin/hooks.go @@ -25,8 +25,5 @@ const ( Update = "update" ) -// PlatformHooks is a map of events to a command for a particular operating system and architecture. -type PlatformHooks map[string][]PlatformCommand - // Hooks is a map of events to commands. type Hooks map[string]string diff --git a/vendor/helm.sh/helm/v3/pkg/plugin/plugin.go b/vendor/helm.sh/helm/v3/pkg/plugin/plugin.go index e2fb78672e7..5bb7434814e 100644 --- a/vendor/helm.sh/helm/v3/pkg/plugin/plugin.go +++ b/vendor/helm.sh/helm/v3/pkg/plugin/plugin.go @@ -44,10 +44,9 @@ type Downloaders struct { // PlatformCommand represents a command for a particular operating system and architecture type PlatformCommand struct { - OperatingSystem string `json:"os"` - Architecture string `json:"arch"` - Command string `json:"command"` - Args []string `json:"args"` + OperatingSystem string `json:"os"` + Architecture string `json:"arch"` + Command string `json:"command"` } // Metadata describes a plugin. @@ -66,35 +65,23 @@ type Metadata struct { // Description is a long description shown in places like `helm help` Description string `json:"description"` - // PlatformCommand is the plugin command, with a platform selector and support for args. + // Command is the command, as a single string. // - // The command and args will be passed through environment expansion, so env vars can + // The command will be passed through environment expansion, so env vars can // be present in this command. Unless IgnoreFlags is set, this will // also merge the flags passed from Helm. // - // Note that the command is not executed in a shell. To do so, we suggest + // Note that command is not executed in a shell. To do so, we suggest // pointing the command to a shell script. // - // The following rules will apply to processing platform commands: - // - If PlatformCommand is present, it will be used + // The following rules will apply to processing commands: + // - If platformCommand is present, it will be searched first // - If both OS and Arch match the current platform, search will stop and the command will be executed - // - If OS matches and Arch is empty, the command will be executed + // - If OS matches and there is no more specific match, the command will be executed // - If no OS/Arch match is found, the default command will be executed - // - If no matches are found in platformCommand, Helm will exit with an error + // - If no command is present and no matches are found in platformCommand, Helm will exit with an error PlatformCommand []PlatformCommand `json:"platformCommand"` - - // Command is the plugin command, as a single string. - // Providing a command will result in an deprecation warning if PlatformCommand is also set. - // - // The command will be passed through environment expansion, so env vars can - // be present in this command. Unless IgnoreFlags is set, this will - // also merge the flags passed from Helm. - // - // Note that command is not executed in a shell. To do so, we suggest - // pointing the command to a shell script. - // - // DEPRECATED: Use PlatformCommand instead. Remove in Helm 4. - Command string `json:"command"` + Command string `json:"command"` // IgnoreFlags ignores any flags passed in from Helm // @@ -103,31 +90,7 @@ type Metadata struct { // the `--debug` flag will be discarded. IgnoreFlags bool `json:"ignoreFlags"` - // PlatformHooks are commands that will run on plugin events, with a platform selector and support for args. - // - // The command and args will be passed through environment expansion, so env vars can - // be present in the command. - // - // Note that the command is not executed in a shell. To do so, we suggest - // pointing the command to a shell script. - // - // The following rules will apply to processing platform hooks: - // - If PlatformHooks is present, it will be used - // - If both OS and Arch match the current platform, search will stop and the command will be executed - // - If OS matches and Arch is empty, the command will be executed - // - If no OS/Arch match is found, the default command will be executed - // - If no matches are found in platformHooks, Helm will skip the event - PlatformHooks PlatformHooks `json:"platformHooks"` - - // Hooks are commands that will run on plugin events, as a single string. - // Providing a hooks will result in an error if PlatformHooks is also set. - // - // The command will be passed through environment expansion, so env vars can - // be present in this command. - // - // Note that the command is executed in the sh shell. - // - // DEPRECATED: Use PlatformHooks instead. Remove in Helm 4. + // Hooks are commands that will run on events. Hooks Hooks // Downloaders field is used if the plugin supply downloader mechanism @@ -149,106 +112,62 @@ type Plugin struct { Dir string } -// Returns command and args strings based on the following rules in priority order: -// - From the PlatformCommand where OS and Arch match the current platform -// - From the PlatformCommand where OS matches the current platform and Arch is empty/unspecified -// - From the PlatformCommand where OS is empty/unspecified and Arch matches the current platform -// - From the PlatformCommand where OS and Arch are both empty/unspecified -// - Return nil, nil -func getPlatformCommand(cmds []PlatformCommand) ([]string, []string) { - var command, args []string - found := false - foundOs := false - +// The following rules will apply to processing the Plugin.PlatformCommand.Command: +// - If both OS and Arch match the current platform, search will stop and the command will be prepared for execution +// - If OS matches and there is no more specific match, the command will be prepared for execution +// - If no OS/Arch match is found, return nil +func getPlatformCommand(cmds []PlatformCommand) []string { + var command []string eq := strings.EqualFold for _, c := range cmds { - if eq(c.OperatingSystem, runtime.GOOS) && eq(c.Architecture, runtime.GOARCH) { - // Return early for an exact match - return strings.Split(c.Command, " "), c.Args - } - - if (len(c.OperatingSystem) > 0 && !eq(c.OperatingSystem, runtime.GOOS)) || len(c.Architecture) > 0 { - // Skip if OS is not empty and doesn't match or if arch is set as a set arch requires an OS match - continue - } - - if !foundOs && len(c.OperatingSystem) > 0 && eq(c.OperatingSystem, runtime.GOOS) { - // First OS match with empty arch, can only be overridden by a direct match + if eq(c.OperatingSystem, runtime.GOOS) { command = strings.Split(c.Command, " ") - args = c.Args - found = true - foundOs = true - } else if !found { - // First empty match, can be overridden by a direct match or an OS match - command = strings.Split(c.Command, " ") - args = c.Args - found = true + } + if eq(c.OperatingSystem, runtime.GOOS) && eq(c.Architecture, runtime.GOARCH) { + return strings.Split(c.Command, " ") } } - - return command, args + return command } -// PrepareCommands takes a []Plugin.PlatformCommand -// and prepares the command and arguments for execution. +// PrepareCommand takes a Plugin.PlatformCommand.Command, a Plugin.Command and will applying the following processing: +// - If platformCommand is present, it will be searched first +// - If both OS and Arch match the current platform, search will stop and the command will be prepared for execution +// - If OS matches and there is no more specific match, the command will be prepared for execution +// - If no OS/Arch match is found, the default command will be prepared for execution +// - If no command is present and no matches are found in platformCommand, will exit with an error // // It merges extraArgs into any arguments supplied in the plugin. It -// returns the main command and an args array. +// returns the name of the command and an args array. // // The result is suitable to pass to exec.Command. -func PrepareCommands(cmds []PlatformCommand, expandArgs bool, extraArgs []string) (string, []string, error) { - cmdParts, args := getPlatformCommand(cmds) - if len(cmdParts) == 0 || cmdParts[0] == "" { +func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string, error) { + var parts []string + platCmdLen := len(p.Metadata.PlatformCommand) + if platCmdLen > 0 { + parts = getPlatformCommand(p.Metadata.PlatformCommand) + } + if platCmdLen == 0 || parts == nil { + parts = strings.Split(p.Metadata.Command, " ") + } + if len(parts) == 0 || parts[0] == "" { return "", nil, fmt.Errorf("no plugin command is applicable") } - main := os.ExpandEnv(cmdParts[0]) + main := os.ExpandEnv(parts[0]) baseArgs := []string{} - if len(cmdParts) > 1 { - for _, cmdPart := range cmdParts[1:] { - if expandArgs { - baseArgs = append(baseArgs, os.ExpandEnv(cmdPart)) - } else { - baseArgs = append(baseArgs, cmdPart) - } + if len(parts) > 1 { + for _, cmdpart := range parts[1:] { + cmdexp := os.ExpandEnv(cmdpart) + baseArgs = append(baseArgs, cmdexp) } } - - for _, arg := range args { - if expandArgs { - baseArgs = append(baseArgs, os.ExpandEnv(arg)) - } else { - baseArgs = append(baseArgs, arg) - } - } - - if len(extraArgs) > 0 { + if !p.Metadata.IgnoreFlags { baseArgs = append(baseArgs, extraArgs...) } - return main, baseArgs, nil } -// PrepareCommand gets the correct command and arguments for a plugin. -// -// It merges extraArgs into any arguments supplied in the plugin. It returns the name of the command and an args array. -// -// The result is suitable to pass to exec.Command. -func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string, error) { - var extraArgsIn []string - - if !p.Metadata.IgnoreFlags { - extraArgsIn = extraArgs - } - - cmds := p.Metadata.PlatformCommand - if len(cmds) == 0 && len(p.Metadata.Command) > 0 { - cmds = []PlatformCommand{{Command: p.Metadata.Command}} - } - - return PrepareCommands(cmds, true, extraArgsIn) -} - // validPluginName is a regular expression that validates plugin names. // // Plugin names can only contain the ASCII characters a-z, A-Z, 0-9, ​_​ and ​-. @@ -265,14 +184,6 @@ func validatePluginData(plug *Plugin, filepath string) error { } plug.Metadata.Usage = sanitizeString(plug.Metadata.Usage) - if len(plug.Metadata.PlatformCommand) > 0 && len(plug.Metadata.Command) > 0 { - fmt.Printf("WARNING: both 'platformCommand' and 'command' are set in %q (this will become an error in a future Helm version)\n", filepath) - } - - if len(plug.Metadata.PlatformHooks) > 0 && len(plug.Metadata.Hooks) > 0 { - fmt.Printf("WARNING: both 'platformHooks' and 'hooks' are set in %q (this will become an error in a future Helm version)\n", filepath) - } - // We could also validate SemVer, executable, and other fields should we so choose. return nil } diff --git a/vendor/helm.sh/helm/v3/pkg/registry/client.go b/vendor/helm.sh/helm/v3/pkg/registry/client.go index b14b767b20d..fc26ca11a71 100644 --- a/vendor/helm.sh/helm/v3/pkg/registry/client.go +++ b/vendor/helm.sh/helm/v3/pkg/registry/client.go @@ -18,31 +18,26 @@ package registry // import "helm.sh/helm/v3/pkg/registry" import ( "context" - "crypto/tls" - "crypto/x509" + "encoding/base64" "encoding/json" "fmt" "io" "net/http" "net/url" - "os" "sort" "strings" - "sync" "github.com/Masterminds/semver/v3" "github.com/containerd/containerd/remotes" - "github.com/opencontainers/image-spec/specs-go" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" - "oras.land/oras-go/v2" - "oras.land/oras-go/v2/content" - "oras.land/oras-go/v2/content/memory" - "oras.land/oras-go/v2/registry" - "oras.land/oras-go/v2/registry/remote" - "oras.land/oras-go/v2/registry/remote/auth" - "oras.land/oras-go/v2/registry/remote/credentials" - "oras.land/oras-go/v2/registry/remote/retry" + "oras.land/oras-go/pkg/auth" + dockerauth "oras.land/oras-go/pkg/auth/docker" + "oras.land/oras-go/pkg/content" + "oras.land/oras-go/pkg/oras" + "oras.land/oras-go/pkg/registry" + registryremote "oras.land/oras-go/pkg/registry/remote" + registryauth "oras.land/oras-go/pkg/registry/remote/auth" "helm.sh/helm/v3/internal/version" "helm.sh/helm/v3/pkg/chart" @@ -56,8 +51,6 @@ storing semantic versions, Helm adopts the convention of changing plus (+) to an underscore (_) in chart version tags when pushing to a registry and back to a plus (+) when pulling from a registry.` -var errDeprecatedRemote = errors.New("providing github.com/containerd/containerd/remotes.Resolver via ClientOptResolver is no longer suported") - type ( // RemoteClient shadows the ORAS remote.Client interface // (hiding the ORAS type from Helm client visibility) @@ -75,15 +68,11 @@ type ( username string password string out io.Writer - authorizer *auth.Client + authorizer auth.Client registryAuthorizer RemoteClient - credentialsStore credentials.Store + resolver func(ref registry.Reference) (remotes.Resolver, error) httpClient *http.Client plainHTTP bool - err error // pass any errors from the ClientOption functions - - // credentialsFileTemp captures if the empty file / EOF work around is being used. - credentialsFileTemp bool } // ClientOption allows specifying various settings configurable by the user for overriding the defaults @@ -98,73 +87,101 @@ func NewClient(options ...ClientOption) (*Client, error) { } for _, option := range options { option(client) - if client.err != nil { - return nil, client.err - } } if client.credentialsFile == "" { client.credentialsFile = helmpath.ConfigPath(CredentialsFileBasename) } - if client.httpClient == nil { - transport := newTransport(client.debug) - client.httpClient = &http.Client{ - Transport: transport, + if client.authorizer == nil { + authClient, err := dockerauth.NewClientWithDockerFallback(client.credentialsFile) + if err != nil { + return nil, err } + client.authorizer = authClient } - storeOptions := credentials.StoreOptions{ - AllowPlaintextPut: true, - DetectDefaultNativeStore: true, - } - store, err := credentials.NewStore(client.credentialsFile, storeOptions) - if err != nil { - // If the file exists and is empty there will be an EOF error. This error is not wrapped so - // a check with errors.Is will not work. The only way to capture it is an EOF error is - // with string parsing. - // This handling passes no file location which will cause NewStore to invoke its - // fault tolerance for a file not existing. A bool records this bypass so that if the - // credential store needs to be written to it this work around can be handled. See the - // Login method for more details. - if strings.Contains(err.Error(), "invalid config format: EOF") { - var err2 error - store, err2 = credentials.NewStore("", storeOptions) - if err2 != nil { - return nil, err + resolverFn := client.resolver // copy for avoiding recursive call + client.resolver = func(ref registry.Reference) (remotes.Resolver, error) { + if resolverFn != nil { + // validate if the resolverFn returns a valid resolver + if resolver, err := resolverFn(ref); resolver != nil && err == nil { + return resolver, nil } - client.credentialsFileTemp = true - } else { - return nil, err } - } - dockerStore, err := credentials.NewStoreFromDocker(storeOptions) - if err != nil { - client.credentialsStore = store - } else { - // use Helm credentials with fallback to Docker - client.credentialsStore = credentials.NewStoreWithFallbacks(store, dockerStore) - } - - if client.authorizer == nil { - authorizer := auth.Client{ - Client: client.httpClient, + headers := http.Header{} + headers.Set("User-Agent", version.GetUserAgent()) + opts := []auth.ResolverOption{auth.WithResolverHeaders(headers)} + if client.httpClient != nil { + opts = append(opts, auth.WithResolverClient(client.httpClient)) + } + if client.plainHTTP { + opts = append(opts, auth.WithResolverPlainHTTP()) } - authorizer.SetUserAgent(version.GetUserAgent()) + // if username and password are set, use them for authentication + // by adding the basic auth Authorization header to the resolver if client.username != "" && client.password != "" { - authorizer.Credential = func(_ context.Context, _ string) (auth.Credential, error) { - return auth.Credential{Username: client.username, Password: client.password}, nil - } - } else { - authorizer.Credential = credentials.Credential(client.credentialsStore) + concat := client.username + ":" + client.password + encodedAuth := base64.StdEncoding.EncodeToString([]byte(concat)) + opts = append(opts, auth.WithResolverHeaders( + http.Header{ + "Authorization": []string{"Basic " + encodedAuth}, + }, + )) } - if client.enableCache { - authorizer.Cache = auth.NewCache() + resolver, err := client.authorizer.ResolverWithOpts(opts...) + if err != nil { + return nil, err } + return resolver, nil + } - client.authorizer = &authorizer + // allocate a cache if option is set + var cache registryauth.Cache + if client.enableCache { + cache = registryauth.DefaultCache } + if client.registryAuthorizer == nil { + client.registryAuthorizer = ®istryauth.Client{ + Client: client.httpClient, + Header: http.Header{ + "User-Agent": {version.GetUserAgent()}, + }, + Cache: cache, + Credential: func(_ context.Context, reg string) (registryauth.Credential, error) { + if client.username != "" && client.password != "" { + return registryauth.Credential{ + Username: client.username, + Password: client.password, + }, nil + } + + dockerClient, ok := client.authorizer.(*dockerauth.Client) + if !ok { + return registryauth.EmptyCredential, errors.New("unable to obtain docker client") + } + + username, password, err := dockerClient.Credential(reg) + if err != nil { + return registryauth.EmptyCredential, errors.New("unable to retrieve credentials") + } + // A blank returned username and password value is a bearer token + if username == "" && password != "" { + return registryauth.Credential{ + RefreshToken: password, + }, nil + } + + return registryauth.Credential{ + Username: username, + Password: password, + }, nil + + }, + } + + } return client, nil } @@ -203,7 +220,7 @@ func ClientOptWriter(out io.Writer) ClientOption { // Depending on the use-case you may need to set both ClientOptAuthorizer and ClientOptRegistryAuthorizer. func ClientOptAuthorizer(authorizer auth.Client) ClientOption { return func(client *Client) { - client.authorizer = &authorizer + client.authorizer = authorizer } } @@ -237,9 +254,12 @@ func ClientOptPlainHTTP() ClientOption { } } -func ClientOptResolver(_ remotes.Resolver) ClientOption { - return func(c *Client) { - c.err = errDeprecatedRemote +// ClientOptResolver returns a function that sets the resolver setting on a client options set +func ClientOptResolver(resolver remotes.Resolver) ClientOption { + return func(client *Client) { + client.resolver = func(_ registry.Reference) (remotes.Resolver, error) { + return resolver, nil + } } } @@ -248,194 +268,60 @@ type ( LoginOption func(*loginOperation) loginOperation struct { - host string - client *Client + username string + password string + insecure bool + certFile string + keyFile string + caFile string } ) -// Deprecated: will be removed in Helm 4 -// Added for backwards compatibility for Helm < 3.18.0 after moving to ORAS v2 -// ref: https://github.com/helm/helm/issues/30873 -// TODO: document that Helm 4 `registry login` does accept full URLs -func (c *Client) stripURL(host string) string { - // strip scheme from host in URL - for _, s := range []string{"oci://", "http://", "https://"} { - if strings.HasPrefix(host, s) { - plain := strings.TrimPrefix(host, s) - if c.debug { - fmt.Fprintf(c.out, "[WARNING] Invalid registry passed: registries should NOT be prefixed with a URL scheme. Use %q instead\n", plain) - } - host = plain - break - } - } - // strip repo from registry in URL - if idx := strings.Index(host, "/"); idx != -1 { - host = host[:idx] - if c.debug { - fmt.Fprintf(c.out, "[WARNING] Invalid registry passed: registries should NOT include a repository. Use %q instead\n", host) - } - return host - } - - return host -} - // Login logs into a registry func (c *Client) Login(host string, options ...LoginOption) error { - // This is the lowest available point to strip incorrect URL parts - host = c.stripURL(host) - + operation := &loginOperation{} for _, option := range options { - option(&loginOperation{host, c}) + option(operation) } - - reg, err := remote.NewRegistry(host) - if err != nil { - return err + authorizerLoginOpts := []auth.LoginOption{ + auth.WithLoginContext(ctx(c.out, c.debug)), + auth.WithLoginHostname(host), + auth.WithLoginUsername(operation.username), + auth.WithLoginSecret(operation.password), + auth.WithLoginUserAgent(version.GetUserAgent()), + auth.WithLoginTLS(operation.certFile, operation.keyFile, operation.caFile), } - reg.PlainHTTP = c.plainHTTP - cred := auth.Credential{Username: c.username, Password: c.password} - c.authorizer.ForceAttemptOAuth2 = true - reg.Client = c.authorizer - - ctx := context.Background() - if err := reg.Ping(ctx); err != nil { - c.authorizer.ForceAttemptOAuth2 = false - if err := reg.Ping(ctx); err != nil { - return fmt.Errorf("authenticating to %q: %w", host, err) - } - } - - // The credentialsStore loader does not handle empty files. So, there is a workaround. - // This can be removed when the credentials loader can handle empty files. - // When Helm catches an empty file error it causes the loader to trigger its fault - // tolerance for a file not existing and records it with a bool. If that bool is set and the - // file needs to be written, the file needs to be put into a usable state and loaded - // properly. - // See the NewClient function for the bypass setup. - if c.credentialsFileTemp { - err = os.WriteFile(c.credentialsFile, []byte("{}"), 0600) - if err != nil { - return err - } - storeOptions := credentials.StoreOptions{ - AllowPlaintextPut: true, - DetectDefaultNativeStore: true, - } - store, err := credentials.NewStore(c.credentialsFile, storeOptions) - if err != nil { - return err - } - c.credentialsStore = store - c.credentialsFileTemp = false + if operation.insecure { + authorizerLoginOpts = append(authorizerLoginOpts, auth.WithLoginInsecure()) } - - key := credentials.ServerAddressFromRegistry(host) - key = credentials.ServerAddressFromHostname(key) - if err := c.credentialsStore.Put(ctx, key, cred); err != nil { + if err := c.authorizer.LoginWithOpts(authorizerLoginOpts...); err != nil { return err } - fmt.Fprintln(c.out, "Login Succeeded") return nil } // LoginOptBasicAuth returns a function that sets the username/password settings on login func LoginOptBasicAuth(username string, password string) LoginOption { - return func(o *loginOperation) { - o.client.username = username - o.client.password = password - o.client.authorizer.Credential = auth.StaticCredential(o.host, auth.Credential{Username: username, Password: password}) - } -} - -// LoginOptPlainText returns a function that allows plaintext (HTTP) login -func LoginOptPlainText(isPlainText bool) LoginOption { - return func(o *loginOperation) { - o.client.plainHTTP = isPlainText - } -} - -func ensureTLSConfig(client *auth.Client) (*tls.Config, error) { - var transport *http.Transport - - switch t := client.Client.Transport.(type) { - case *http.Transport: - transport = t - case *fallbackTransport: - switch t := t.Base.(type) { - case *http.Transport: - transport = t - case *retry.Transport: - switch t := t.Base.(type) { - case *http.Transport: - transport = t - case *LoggingTransport: - switch t := t.RoundTripper.(type) { - case *http.Transport: - transport = t - } - } - } - } - - if transport == nil { - // we don't know how to access the http.Transport, most likely the - // auth.Client.Client was provided by API user - return nil, fmt.Errorf("unable to access TLS client configuration, the provided HTTP Transport is not supported, given: %T", client.Client.Transport) - } - - if transport.TLSClientConfig == nil { - transport.TLSClientConfig = &tls.Config{} + return func(operation *loginOperation) { + operation.username = username + operation.password = password } - - return transport.TLSClientConfig, nil } // LoginOptInsecure returns a function that sets the insecure setting on login func LoginOptInsecure(insecure bool) LoginOption { - return func(o *loginOperation) { - tlsConfig, err := ensureTLSConfig(o.client.authorizer) - - if err != nil { - panic(err) - } - - tlsConfig.InsecureSkipVerify = insecure + return func(operation *loginOperation) { + operation.insecure = insecure } } // LoginOptTLSClientConfig returns a function that sets the TLS settings on login. func LoginOptTLSClientConfig(certFile, keyFile, caFile string) LoginOption { - return func(o *loginOperation) { - if (certFile == "" || keyFile == "") && caFile == "" { - return - } - tlsConfig, err := ensureTLSConfig(o.client.authorizer) - if err != nil { - panic(err) - } - - if certFile != "" && keyFile != "" { - authCert, err := tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - panic(err) - } - tlsConfig.Certificates = []tls.Certificate{authCert} - } - - if caFile != "" { - certPool := x509.NewCertPool() - ca, err := os.ReadFile(caFile) - if err != nil { - panic(err) - } - if !certPool.AppendCertsFromPEM(ca) { - panic(fmt.Errorf("unable to parse CA file: %q", caFile)) - } - tlsConfig.RootCAs = certPool - } + return func(operation *loginOperation) { + operation.certFile = certFile + operation.keyFile = keyFile + operation.caFile = caFile } } @@ -452,8 +338,7 @@ func (c *Client) Logout(host string, opts ...LogoutOption) error { for _, opt := range opts { opt(operation) } - - if err := credentials.Logout(context.Background(), c.credentialsStore, host); err != nil { + if err := c.authorizer.Logout(ctx(c.out, c.debug), host); err != nil { return err } fmt.Fprintf(c.out, "Removing login credentials for %s\n", host) @@ -508,10 +393,8 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) { return nil, errors.New( "must specify at least one layer to pull (chart/prov)") } - memoryStore := memory.New() + memoryStore := content.NewMemory() allowedMediaTypes := []string{ - ocispec.MediaTypeImageIndex, - ocispec.MediaTypeImageManifest, ConfigMediaType, } minNumDescriptors := 1 // 1 for the config @@ -527,38 +410,23 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) { } var descriptors, layers []ocispec.Descriptor - - repository, err := remote.NewRepository(parsedRef.String()) + remotesResolver, err := c.resolver(parsedRef.orasReference) if err != nil { return nil, err } - repository.PlainHTTP = c.plainHTTP - repository.Client = c.authorizer - - ctx := context.Background() + registryStore := content.Registry{Resolver: remotesResolver} - sort.Strings(allowedMediaTypes) - - var mu sync.Mutex - manifest, err := oras.Copy(ctx, repository, parsedRef.String(), memoryStore, "", oras.CopyOptions{ - CopyGraphOptions: oras.CopyGraphOptions{ - PreCopy: func(_ context.Context, desc ocispec.Descriptor) error { - mediaType := desc.MediaType - if i := sort.SearchStrings(allowedMediaTypes, mediaType); i >= len(allowedMediaTypes) || allowedMediaTypes[i] != mediaType { - return oras.SkipNode - } - - mu.Lock() - layers = append(layers, desc) - mu.Unlock() - return nil - }, - }, - }) + manifest, err := oras.Copy(ctx(c.out, c.debug), registryStore, parsedRef.String(), memoryStore, "", + oras.WithPullEmptyNameAllowed(), + oras.WithAllowedMediaTypes(allowedMediaTypes), + oras.WithLayerDescriptors(func(l []ocispec.Descriptor) { + layers = l + })) if err != nil { return nil, err } + descriptors = append(descriptors, manifest) descriptors = append(descriptors, layers...) numDescriptors := len(descriptors) @@ -612,37 +480,54 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) { Prov: &DescriptorPullSummary{}, Ref: parsedRef.String(), } - - result.Manifest.Data, err = content.FetchAll(ctx, memoryStore, manifest) - if err != nil { - return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", manifest.Digest, err) + var getManifestErr error + if _, manifestData, ok := memoryStore.Get(manifest); !ok { + getManifestErr = errors.Errorf("Unable to retrieve blob with digest %s", manifest.Digest) + } else { + result.Manifest.Data = manifestData } - - result.Config.Data, err = content.FetchAll(ctx, memoryStore, *configDescriptor) - if err != nil { - return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", configDescriptor.Digest, err) + if getManifestErr != nil { + return nil, getManifestErr } - - if err := json.Unmarshal(result.Config.Data, &result.Chart.Meta); err != nil { - return nil, err + var getConfigDescriptorErr error + if _, configData, ok := memoryStore.Get(*configDescriptor); !ok { + getConfigDescriptorErr = errors.Errorf("Unable to retrieve blob with digest %s", configDescriptor.Digest) + } else { + result.Config.Data = configData + var meta *chart.Metadata + if err := json.Unmarshal(configData, &meta); err != nil { + return nil, err + } + result.Chart.Meta = meta + } + if getConfigDescriptorErr != nil { + return nil, getConfigDescriptorErr } - if operation.withChart { - result.Chart.Data, err = content.FetchAll(ctx, memoryStore, *chartDescriptor) - if err != nil { - return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", chartDescriptor.Digest, err) + var getChartDescriptorErr error + if _, chartData, ok := memoryStore.Get(*chartDescriptor); !ok { + getChartDescriptorErr = errors.Errorf("Unable to retrieve blob with digest %s", chartDescriptor.Digest) + } else { + result.Chart.Data = chartData + result.Chart.Digest = chartDescriptor.Digest.String() + result.Chart.Size = chartDescriptor.Size + } + if getChartDescriptorErr != nil { + return nil, getChartDescriptorErr } - result.Chart.Digest = chartDescriptor.Digest.String() - result.Chart.Size = chartDescriptor.Size } - if operation.withProv && !provMissing { - result.Prov.Data, err = content.FetchAll(ctx, memoryStore, *provDescriptor) - if err != nil { - return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", provDescriptor.Digest, err) + var getProvDescriptorErr error + if _, provData, ok := memoryStore.Get(*provDescriptor); !ok { + getProvDescriptorErr = errors.Errorf("Unable to retrieve blob with digest %s", provDescriptor.Digest) + } else { + result.Prov.Data = provData + result.Prov.Digest = provDescriptor.Digest.String() + result.Prov.Size = provDescriptor.Size + } + if getProvDescriptorErr != nil { + return nil, getProvDescriptorErr } - result.Prov.Digest = provDescriptor.Digest.String() - result.Prov.Size = provDescriptor.Size } fmt.Fprintf(c.out, "Pulled: %s\n", result.Ref) @@ -730,11 +615,8 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu "strict mode enabled, ref basename and tag must match the chart name and version") } } - - ctx := context.Background() - - memoryStore := memory.New() - chartDescriptor, err := oras.PushBytes(ctx, memoryStore, ChartLayerMediaType, data) + memoryStore := content.NewMemory() + chartDescriptor, err := memoryStore.Add("", ChartLayerMediaType, data) if err != nil { return nil, err } @@ -744,47 +626,43 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu return nil, err } - configDescriptor, err := oras.PushBytes(ctx, memoryStore, ConfigMediaType, configData) + configDescriptor, err := memoryStore.Add("", ConfigMediaType, configData) if err != nil { return nil, err } - layers := []ocispec.Descriptor{chartDescriptor} + descriptors := []ocispec.Descriptor{chartDescriptor} var provDescriptor ocispec.Descriptor if operation.provData != nil { - provDescriptor, err = oras.PushBytes(ctx, memoryStore, ProvLayerMediaType, operation.provData) + provDescriptor, err = memoryStore.Add("", ProvLayerMediaType, operation.provData) if err != nil { return nil, err } - layers = append(layers, provDescriptor) + descriptors = append(descriptors, provDescriptor) } - // sort layers for determinism, similar to how ORAS v1 does it - sort.Slice(layers, func(i, j int) bool { - return layers[i].Digest < layers[j].Digest - }) - ociAnnotations := generateOCIAnnotations(meta, operation.creationTime) - manifestDescriptor, err := c.tagManifest(ctx, memoryStore, configDescriptor, - layers, ociAnnotations, parsedRef) + manifestData, manifest, err := content.GenerateManifest(&configDescriptor, ociAnnotations, descriptors...) if err != nil { return nil, err } - repository, err := remote.NewRepository(parsedRef.String()) - if err != nil { + if err := memoryStore.StoreManifest(parsedRef.String(), manifest, manifestData); err != nil { return nil, err } - repository.PlainHTTP = c.plainHTTP - repository.Client = c.authorizer - manifestDescriptor, err = oras.ExtendedCopy(ctx, memoryStore, parsedRef.String(), repository, parsedRef.String(), oras.DefaultExtendedCopyOptions) + remotesResolver, err := c.resolver(parsedRef.orasReference) + if err != nil { + return nil, err + } + registryStore := content.Registry{Resolver: remotesResolver} + _, err = oras.Copy(ctx(c.out, c.debug), memoryStore, parsedRef.orasReference.String(), registryStore, "", + oras.WithNameValidation(nil)) if err != nil { return nil, err } - chartSummary := &descriptorPushSummaryWithMeta{ Meta: meta, } @@ -792,8 +670,8 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu chartSummary.Size = chartDescriptor.Size result := &PushResult{ Manifest: &descriptorPushSummary{ - Digest: manifestDescriptor.Digest.String(), - Size: manifestDescriptor.Size, + Digest: manifest.Digest.String(), + Size: manifest.Size, }, Config: &descriptorPushSummary{ Digest: configDescriptor.Digest.String(), @@ -847,29 +725,27 @@ func (c *Client) Tags(ref string) ([]string, error) { return nil, err } - ctx := context.Background() - repository, err := remote.NewRepository(parsedReference.String()) + repository := registryremote.Repository{ + Reference: parsedReference, + Client: c.registryAuthorizer, + PlainHTTP: c.plainHTTP, + } + + var registryTags []string + + registryTags, err = registry.Tags(ctx(c.out, c.debug), &repository) if err != nil { return nil, err } - repository.PlainHTTP = c.plainHTTP - repository.Client = c.authorizer var tagVersions []*semver.Version - err = repository.Tags(ctx, "", func(tags []string) error { - for _, tag := range tags { - // Change underscore (_) back to plus (+) for Helm - // See https://github.com/helm/helm/issues/10166 - tagVersion, err := semver.StrictNewVersion(strings.ReplaceAll(tag, "_", "+")) - if err == nil { - tagVersions = append(tagVersions, tagVersion) - } + for _, tag := range registryTags { + // Change underscore (_) back to plus (+) for Helm + // See https://github.com/helm/helm/issues/10166 + tagVersion, err := semver.StrictNewVersion(strings.ReplaceAll(tag, "_", "+")) + if err == nil { + tagVersions = append(tagVersions, tagVersion) } - - return nil - }) - if err != nil { - return nil, err } // Sort the collection @@ -886,29 +762,30 @@ func (c *Client) Tags(ref string) ([]string, error) { } // Resolve a reference to a descriptor. -func (c *Client) Resolve(ref string) (desc ocispec.Descriptor, err error) { - remoteRepository, err := remote.NewRepository(ref) +func (c *Client) Resolve(ref string) (*ocispec.Descriptor, error) { + ctx := context.Background() + parsedRef, err := newReference(ref) if err != nil { - return desc, err + return nil, err + } + if parsedRef.Registry == "" { + return nil, nil } - remoteRepository.PlainHTTP = c.plainHTTP - remoteRepository.Client = c.authorizer - parsedReference, err := newReference(ref) + remotesResolver, err := c.resolver(parsedRef.orasReference) if err != nil { - return desc, err + return nil, err } - ctx := context.Background() - parsedString := parsedReference.String() - return remoteRepository.Resolve(ctx, parsedString) + _, desc, err := remotesResolver.Resolve(ctx, ref) + return &desc, err } // ValidateReference for path and version func (c *Client) ValidateReference(ref, version string, u *url.URL) (*url.URL, error) { var tag string - registryReference, err := newReference(u.Host + u.Path) + registryReference, err := newReference(u.Path) if err != nil { return nil, err } @@ -923,20 +800,19 @@ func (c *Client) ValidateReference(ref, version string, u *url.URL) (*url.URL, e } if registryReference.Digest != "" { - if version == "" { + if registryReference.Tag == "" { // Install by digest only return u, nil } - u.Path = fmt.Sprintf("%s@%s", registryReference.Repository, registryReference.Digest) // Validate the tag if it was specified - path := registryReference.Registry + "/" + registryReference.Repository + ":" + version + path := registryReference.Registry + "/" + registryReference.Repository + ":" + registryReference.Tag desc, err := c.Resolve(path) if err != nil { // The resource does not have to be tagged when digest is specified return u, nil } - if desc.Digest.String() != registryReference.Digest { + if desc != nil && desc.Digest.String() != registryReference.Digest { return nil, errors.Errorf("chart reference digest mismatch: %s is not %s", desc.Digest.String(), registryReference.Digest) } return u, nil @@ -966,28 +842,7 @@ func (c *Client) ValidateReference(ref, version string, u *url.URL) (*url.URL, e } } - u.Path = fmt.Sprintf("%s:%s", registryReference.Repository, tag) + u.Path = fmt.Sprintf("%s/%s:%s", registryReference.Registry, registryReference.Repository, tag) return u, err } - -// tagManifest prepares and tags a manifest in memory storage -func (c *Client) tagManifest(ctx context.Context, memoryStore *memory.Store, - configDescriptor ocispec.Descriptor, layers []ocispec.Descriptor, - ociAnnotations map[string]string, parsedRef reference) (ocispec.Descriptor, error) { - - manifest := ocispec.Manifest{ - Versioned: specs.Versioned{SchemaVersion: 2}, - Config: configDescriptor, - Layers: layers, - Annotations: ociAnnotations, - } - - manifestData, err := json.Marshal(manifest) - if err != nil { - return ocispec.Descriptor{}, err - } - - return oras.TagBytes(ctx, memoryStore, ocispec.MediaTypeImageManifest, - manifestData, parsedRef.String()) -} diff --git a/vendor/helm.sh/helm/v3/pkg/registry/fallback.go b/vendor/helm.sh/helm/v3/pkg/registry/fallback.go deleted file mode 100644 index 1db72957643..00000000000 --- a/vendor/helm.sh/helm/v3/pkg/registry/fallback.go +++ /dev/null @@ -1,60 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package registry - -import ( - "crypto/tls" - "net/http" - "sync/atomic" -) - -// NOTE(terryhowe): This fallback feature is only provided in v3 for backward -// compatibility. ORAS v1 had this feature and this code was added when helm -// updated to ORAS v2. This will not be supported in helm v4. - -type fallbackTransport struct { - Base http.RoundTripper - forceHTTP atomic.Bool -} - -func newTransport(debug bool) *fallbackTransport { - baseTransport := NewTransport(debug) - return &fallbackTransport{ - Base: baseTransport, - } -} - -// RoundTrip wraps base round trip with conditional insecure retry. -func (t *fallbackTransport) RoundTrip(req *http.Request) (*http.Response, error) { - if ok := t.forceHTTP.Load(); ok { - req.URL.Scheme = "http" - return t.Base.RoundTrip(req) - } - resp, err := t.Base.RoundTrip(req) - // We are falling back to http here for backward compatibility with Helm v3. - // ORAS v1 provided fallback automatically, but ORAS v2 does not. - if err != nil && req.URL.Scheme == "https" { - if tlsErr, ok := err.(tls.RecordHeaderError); ok { - if string(tlsErr.RecordHeader[:]) == "HTTP/" { - t.forceHTTP.Store(true) - req.URL.Scheme = "http" - return t.Base.RoundTrip(req) - } - } - } - return resp, err -} diff --git a/vendor/helm.sh/helm/v3/pkg/registry/reference.go b/vendor/helm.sh/helm/v3/pkg/registry/reference.go index b5677761da7..9b99d73bf1b 100644 --- a/vendor/helm.sh/helm/v3/pkg/registry/reference.go +++ b/vendor/helm.sh/helm/v3/pkg/registry/reference.go @@ -19,11 +19,11 @@ package registry import ( "strings" - "oras.land/oras-go/v2/registry" + orasregistry "oras.land/oras-go/pkg/registry" ) type reference struct { - orasReference registry.Reference + orasReference orasregistry.Reference Registry string Repository string Tag string @@ -60,7 +60,7 @@ func newReference(raw string) (result reference, err error) { } } - result.orasReference, err = registry.ParseReference(raw) + result.orasReference, err = orasregistry.ParseReference(raw) if err != nil { return result, err } diff --git a/vendor/helm.sh/helm/v3/pkg/registry/util.go b/vendor/helm.sh/helm/v3/pkg/registry/util.go index 84ee698072b..bd9f151a8e7 100644 --- a/vendor/helm.sh/helm/v3/pkg/registry/util.go +++ b/vendor/helm.sh/helm/v3/pkg/registry/util.go @@ -18,20 +18,24 @@ package registry // import "helm.sh/helm/v3/pkg/registry" import ( "bytes" + "context" "fmt" "io" "net/http" "strings" "time" - "helm.sh/helm/v3/internal/tlsutil" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" helmtime "helm.sh/helm/v3/pkg/time" "github.com/Masterminds/semver/v3" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" + "github.com/sirupsen/logrus" + orascontext "oras.land/oras-go/pkg/context" + + "helm.sh/helm/v3/internal/tlsutil" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" ) var immutableOciAnnotations = []string{ @@ -39,7 +43,7 @@ var immutableOciAnnotations = []string{ ocispec.AnnotationTitle, } -// IsOCI determines whether a URL is to be treated as an OCI URL +// IsOCI determines whether or not a URL is to be treated as an OCI URL func IsOCI(url string) bool { return strings.HasPrefix(url, fmt.Sprintf("%s://", OCIScheme)) } @@ -99,6 +103,17 @@ func extractChartMeta(chartData []byte) (*chart.Metadata, error) { return ch.Metadata, nil } +// ctx retrieves a fresh context. +// disable verbose logging coming from ORAS (unless debug is enabled) +func ctx(out io.Writer, debug bool) context.Context { + if !debug { + return orascontext.Background() + } + ctx := orascontext.WithLoggerFromWriter(context.Background(), out) + orascontext.GetLogger(ctx).Logger.SetLevel(logrus.DebugLevel) + return ctx +} + // NewRegistryClientWithTLS is a helper function to create a new registry client with TLS enabled. func NewRegistryClientWithTLS(out io.Writer, certFile, keyFile, caFile string, insecureSkipTLSverify bool, registryConfig string, debug bool) (*Client, error) { tlsConf, err := tlsutil.NewClientTLS(certFile, keyFile, caFile, insecureSkipTLSverify) diff --git a/vendor/helm.sh/helm/v3/pkg/release/hook.go b/vendor/helm.sh/helm/v3/pkg/release/hook.go index 425074ac1d4..cb995558225 100644 --- a/vendor/helm.sh/helm/v3/pkg/release/hook.go +++ b/vendor/helm.sh/helm/v3/pkg/release/hook.go @@ -50,17 +50,6 @@ const ( func (x HookDeletePolicy) String() string { return string(x) } -// HookOutputLogPolicy specifies the hook output log policy -type HookOutputLogPolicy string - -// Hook output log policy types -const ( - HookOutputOnSucceeded HookOutputLogPolicy = "hook-succeeded" - HookOutputOnFailed HookOutputLogPolicy = "hook-failed" -) - -func (x HookOutputLogPolicy) String() string { return string(x) } - // HookAnnotation is the label name for a hook const HookAnnotation = "helm.sh/hook" @@ -70,9 +59,6 @@ const HookWeightAnnotation = "helm.sh/hook-weight" // HookDeleteAnnotation is the label name for the delete policy for a hook const HookDeleteAnnotation = "helm.sh/hook-delete-policy" -// HookOutputLogAnnotation is the label name for the output log policy for a hook -const HookOutputLogAnnotation = "helm.sh/hook-output-log-policy" - // Hook defines a hook object. type Hook struct { Name string `json:"name,omitempty"` @@ -90,8 +76,6 @@ type Hook struct { Weight int `json:"weight,omitempty"` // DeletePolicies are the policies that indicate when to delete the hook DeletePolicies []HookDeletePolicy `json:"delete_policies,omitempty"` - // OutputLogPolicies defines whether we should copy hook logs back to main process - OutputLogPolicies []HookOutputLogPolicy `json:"output_log_policies,omitempty"` } // A HookExecution records the result for the last execution of a hook for a given release. diff --git a/vendor/helm.sh/helm/v3/pkg/releaseutil/manifest_sorter.go b/vendor/helm.sh/helm/v3/pkg/releaseutil/manifest_sorter.go index cf851baa818..4b6109929d7 100644 --- a/vendor/helm.sh/helm/v3/pkg/releaseutil/manifest_sorter.go +++ b/vendor/helm.sh/helm/v3/pkg/releaseutil/manifest_sorter.go @@ -128,14 +128,6 @@ func SortManifests(files map[string]string, _ chartutil.VersionSet, ordering Kin // metadata: // annotations: // helm.sh/hook-delete-policy: hook-succeeded -// -// To determine the policy to output logs of the hook (for Pod and Job only), it looks for a YAML structure like this: -// -// kind: Pod -// apiVersion: v1 -// metadata: -// annotations: -// helm.sh/hook-output-log-policy: hook-succeeded,hook-failed func (file *manifestFile) sort(result *result) error { // Go through manifests in order found in file (function `SplitManifests` creates integer-sortable keys) var sortedEntryKeys []string @@ -174,14 +166,13 @@ func (file *manifestFile) sort(result *result) error { hw := calculateHookWeight(entry) h := &release.Hook{ - Name: entry.Metadata.Name, - Kind: entry.Kind, - Path: file.path, - Manifest: m, - Events: []release.HookEvent{}, - Weight: hw, - DeletePolicies: []release.HookDeletePolicy{}, - OutputLogPolicies: []release.HookOutputLogPolicy{}, + Name: entry.Metadata.Name, + Kind: entry.Kind, + Path: file.path, + Manifest: m, + Events: []release.HookEvent{}, + Weight: hw, + DeletePolicies: []release.HookDeletePolicy{}, } isUnknownHook := false @@ -205,10 +196,6 @@ func (file *manifestFile) sort(result *result) error { operateAnnotationValues(entry, release.HookDeleteAnnotation, func(value string) { h.DeletePolicies = append(h.DeletePolicies, release.HookDeletePolicy(value)) }) - - operateAnnotationValues(entry, release.HookOutputLogAnnotation, func(value string) { - h.OutputLogPolicies = append(h.OutputLogPolicies, release.HookOutputLogPolicy(value)) - }) } return nil diff --git a/vendor/helm.sh/helm/v3/pkg/repo/index.go b/vendor/helm.sh/helm/v3/pkg/repo/index.go index a93314ab8bd..e1ce3c62dd2 100644 --- a/vendor/helm.sh/helm/v3/pkg/repo/index.go +++ b/vendor/helm.sh/helm/v3/pkg/repo/index.go @@ -357,7 +357,6 @@ func loadIndex(data []byte, source string) (*IndexFile, error) { for idx := len(cvs) - 1; idx >= 0; idx-- { if cvs[idx] == nil { log.Printf("skipping loading invalid entry for chart %q from %s: empty entry", name, source) - cvs = append(cvs[:idx], cvs[idx+1:]...) continue } // When metadata section missing, initialize with no data diff --git a/vendor/helm.sh/helm/v3/pkg/time/time.go b/vendor/helm.sh/helm/v3/pkg/time/time.go index 678e368dd07..1abe8ae3d86 100644 --- a/vendor/helm.sh/helm/v3/pkg/time/time.go +++ b/vendor/helm.sh/helm/v3/pkg/time/time.go @@ -19,7 +19,7 @@ limitations under the License. // where the serializer doesn't omit an empty value for time: // https://github.com/golang/go/issues/11939. As such, this can be removed if a // proposal is ever accepted for Go -package time //nolint:revive +package time import ( "bytes" diff --git a/vendor/helm.sh/helm/v4/LICENSE b/vendor/helm.sh/helm/v4/LICENSE new file mode 100644 index 00000000000..21c57fae21f --- /dev/null +++ b/vendor/helm.sh/helm/v4/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016 The Kubernetes Authors All Rights Reserved + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/helm.sh/helm/v4/internal/chart/v3/chart.go b/vendor/helm.sh/helm/v4/internal/chart/v3/chart.go new file mode 100644 index 00000000000..48f006e7999 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/chart/v3/chart.go @@ -0,0 +1,179 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v3 + +import ( + "path/filepath" + "regexp" + "strings" + "time" + + "helm.sh/helm/v4/pkg/chart/common" +) + +// APIVersionV3 is the API version number for version 3. +const APIVersionV3 = "v3" + +// aliasNameFormat defines the characters that are legal in an alias name. +var aliasNameFormat = regexp.MustCompile("^[a-zA-Z0-9_-]+$") + +// Chart is a helm package that contains metadata, a default config, zero or more +// optionally parameterizable templates, and zero or more charts (dependencies). +type Chart struct { + // Raw contains the raw contents of the files originally contained in the chart archive. + // + // This should not be used except in special cases like `helm show values`, + // where we want to display the raw values, comments and all. + Raw []*common.File `json:"-"` + // Metadata is the contents of the Chartfile. + Metadata *Metadata `json:"metadata"` + // Lock is the contents of Chart.lock. + Lock *Lock `json:"lock"` + // Templates for this chart. + Templates []*common.File `json:"templates"` + // Values are default config for this chart. + Values map[string]interface{} `json:"values"` + // Schema is an optional JSON schema for imposing structure on Values + Schema []byte `json:"schema"` + // SchemaModTime the schema was last modified + SchemaModTime time.Time `json:"schemamodtime,omitempty"` + // Files are miscellaneous files in a chart archive, + // e.g. README, LICENSE, etc. + Files []*common.File `json:"files"` + // ModTime the chart metadata was last modified + ModTime time.Time `json:"modtime,omitzero"` + + parent *Chart + dependencies []*Chart +} + +type CRD struct { + // Name is the File.Name for the crd file + Name string + // Filename is the File obj Name including (sub-)chart.ChartFullPath + Filename string + // File is the File obj for the crd + File *common.File +} + +// SetDependencies replaces the chart dependencies. +func (ch *Chart) SetDependencies(charts ...*Chart) { + ch.dependencies = nil + ch.AddDependency(charts...) +} + +// Name returns the name of the chart. +func (ch *Chart) Name() string { + if ch.Metadata == nil { + return "" + } + return ch.Metadata.Name +} + +// AddDependency determines if the chart is a subchart. +func (ch *Chart) AddDependency(charts ...*Chart) { + for i, x := range charts { + charts[i].parent = ch + ch.dependencies = append(ch.dependencies, x) + } +} + +// Root finds the root chart. +func (ch *Chart) Root() *Chart { + if ch.IsRoot() { + return ch + } + return ch.Parent().Root() +} + +// Dependencies are the charts that this chart depends on. +func (ch *Chart) Dependencies() []*Chart { return ch.dependencies } + +// IsRoot determines if the chart is the root chart. +func (ch *Chart) IsRoot() bool { return ch.parent == nil } + +// Parent returns a subchart's parent chart. +func (ch *Chart) Parent() *Chart { return ch.parent } + +// ChartPath returns the full path to this chart in dot notation. +func (ch *Chart) ChartPath() string { + if !ch.IsRoot() { + return ch.Parent().ChartPath() + "." + ch.Name() + } + return ch.Name() +} + +// ChartFullPath returns the full path to this chart. +// Note that the path may not correspond to the path where the file can be found on the file system if the path +// points to an aliased subchart. +func (ch *Chart) ChartFullPath() string { + if !ch.IsRoot() { + return ch.Parent().ChartFullPath() + "/charts/" + ch.Name() + } + return ch.Name() +} + +// Validate validates the metadata. +func (ch *Chart) Validate() error { + return ch.Metadata.Validate() +} + +// AppVersion returns the appversion of the chart. +func (ch *Chart) AppVersion() string { + if ch.Metadata == nil { + return "" + } + return ch.Metadata.AppVersion +} + +// CRDs returns a list of File objects in the 'crds/' directory of a Helm chart. +// Deprecated: use CRDObjects() +func (ch *Chart) CRDs() []*common.File { + files := []*common.File{} + // Find all resources in the crds/ directory + for _, f := range ch.Files { + if strings.HasPrefix(f.Name, "crds/") && hasManifestExtension(f.Name) { + files = append(files, f) + } + } + // Get CRDs from dependencies, too. + for _, dep := range ch.Dependencies() { + files = append(files, dep.CRDs()...) + } + return files +} + +// CRDObjects returns a list of CRD objects in the 'crds/' directory of a Helm chart & subcharts +func (ch *Chart) CRDObjects() []CRD { + crds := []CRD{} + // Find all resources in the crds/ directory + for _, f := range ch.Files { + if strings.HasPrefix(f.Name, "crds/") && hasManifestExtension(f.Name) { + mycrd := CRD{Name: f.Name, Filename: filepath.Join(ch.ChartFullPath(), f.Name), File: f} + crds = append(crds, mycrd) + } + } + // Get CRDs from dependencies, too. + for _, dep := range ch.Dependencies() { + crds = append(crds, dep.CRDObjects()...) + } + return crds +} + +func hasManifestExtension(fname string) bool { + ext := filepath.Ext(fname) + return strings.EqualFold(ext, ".yaml") || strings.EqualFold(ext, ".yml") || strings.EqualFold(ext, ".json") +} diff --git a/vendor/helm.sh/helm/v4/internal/chart/v3/dependency.go b/vendor/helm.sh/helm/v4/internal/chart/v3/dependency.go new file mode 100644 index 00000000000..2d956b54837 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/chart/v3/dependency.go @@ -0,0 +1,82 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v3 + +import "time" + +// Dependency describes a chart upon which another chart depends. +// +// Dependencies can be used to express developer intent, or to capture the state +// of a chart. +type Dependency struct { + // Name is the name of the dependency. + // + // This must mach the name in the dependency's Chart.yaml. + Name string `json:"name" yaml:"name"` + // Version is the version (range) of this chart. + // + // A lock file will always produce a single version, while a dependency + // may contain a semantic version range. + Version string `json:"version,omitempty" yaml:"version,omitempty"` + // The URL to the repository. + // + // Appending `index.yaml` to this string should result in a URL that can be + // used to fetch the repository index. + Repository string `json:"repository" yaml:"repository"` + // A yaml path that resolves to a boolean, used for enabling/disabling charts (e.g. subchart1.enabled ) + Condition string `json:"condition,omitempty" yaml:"condition,omitempty"` + // Tags can be used to group charts for enabling/disabling together + Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` + // Enabled bool determines if chart should be loaded + Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` + // ImportValues holds the mapping of source values to parent key to be imported. Each item can be a + // string or pair of child/parent sublist items. + ImportValues []interface{} `json:"import-values,omitempty" yaml:"import-values,omitempty"` + // Alias usable alias to be used for the chart + Alias string `json:"alias,omitempty" yaml:"alias,omitempty"` +} + +// Validate checks for common problems with the dependency datastructure in +// the chart. This check must be done at load time before the dependency's charts are +// loaded. +func (d *Dependency) Validate() error { + if d == nil { + return ValidationError("dependencies must not contain empty or null nodes") + } + d.Name = sanitizeString(d.Name) + d.Version = sanitizeString(d.Version) + d.Repository = sanitizeString(d.Repository) + d.Condition = sanitizeString(d.Condition) + for i := range d.Tags { + d.Tags[i] = sanitizeString(d.Tags[i]) + } + if d.Alias != "" && !aliasNameFormat.MatchString(d.Alias) { + return ValidationErrorf("dependency %q has disallowed characters in the alias", d.Name) + } + return nil +} + +// Lock is a lock file for dependencies. +// +// It represents the state that the dependencies should be in. +type Lock struct { + // Generated is the date the lock file was last generated. + Generated time.Time `json:"generated"` + // Digest is a hash of the dependencies in Chart.yaml. + Digest string `json:"digest"` + // Dependencies is the list of dependencies that this lock file has locked. + Dependencies []*Dependency `json:"dependencies"` +} diff --git a/vendor/helm.sh/helm/v4/internal/chart/v3/doc.go b/vendor/helm.sh/helm/v4/internal/chart/v3/doc.go new file mode 100644 index 00000000000..e003833a0eb --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/chart/v3/doc.go @@ -0,0 +1,21 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package v3 provides chart handling for apiVersion v3 charts + +This package and its sub-packages provide handling for apiVersion v3 charts. +*/ +package v3 diff --git a/vendor/helm.sh/helm/v4/internal/chart/v3/errors.go b/vendor/helm.sh/helm/v4/internal/chart/v3/errors.go new file mode 100644 index 00000000000..059e43f07b9 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/chart/v3/errors.go @@ -0,0 +1,30 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v3 + +import "fmt" + +// ValidationError represents a data validation error. +type ValidationError string + +func (v ValidationError) Error() string { + return "validation: " + string(v) +} + +// ValidationErrorf takes a message and formatting options and creates a ValidationError +func ValidationErrorf(msg string, args ...interface{}) ValidationError { + return ValidationError(fmt.Sprintf(msg, args...)) +} diff --git a/vendor/helm.sh/helm/v4/internal/chart/v3/loader/archive.go b/vendor/helm.sh/helm/v4/internal/chart/v3/loader/archive.go new file mode 100644 index 00000000000..a9d4faf8f06 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/chart/v3/loader/archive.go @@ -0,0 +1,74 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package loader + +import ( + "compress/gzip" + "errors" + "fmt" + "io" + "os" + + chart "helm.sh/helm/v4/internal/chart/v3" + "helm.sh/helm/v4/pkg/chart/loader/archive" +) + +// FileLoader loads a chart from a file +type FileLoader string + +// Load loads a chart +func (l FileLoader) Load() (*chart.Chart, error) { + return LoadFile(string(l)) +} + +// LoadFile loads from an archive file. +func LoadFile(name string) (*chart.Chart, error) { + if fi, err := os.Stat(name); err != nil { + return nil, err + } else if fi.IsDir() { + return nil, errors.New("cannot load a directory") + } + + raw, err := os.Open(name) + if err != nil { + return nil, err + } + defer raw.Close() + + err = archive.EnsureArchive(name, raw) + if err != nil { + return nil, err + } + + c, err := LoadArchive(raw) + if err != nil { + if errors.Is(err, gzip.ErrHeader) { + return nil, fmt.Errorf("file '%s' does not appear to be a valid chart file (details: %s)", name, err) + } + } + return c, err +} + +// LoadArchive loads from a reader containing a compressed tar archive. +func LoadArchive(in io.Reader) (*chart.Chart, error) { + files, err := archive.LoadArchiveFiles(in) + if err != nil { + return nil, err + } + + return LoadFiles(files) +} diff --git a/vendor/helm.sh/helm/v4/internal/chart/v3/loader/directory.go b/vendor/helm.sh/helm/v4/internal/chart/v3/loader/directory.go new file mode 100644 index 00000000000..dfe3af3b200 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/chart/v3/loader/directory.go @@ -0,0 +1,122 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package loader + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "strings" + + chart "helm.sh/helm/v4/internal/chart/v3" + "helm.sh/helm/v4/internal/sympath" + "helm.sh/helm/v4/pkg/chart/loader/archive" + "helm.sh/helm/v4/pkg/ignore" +) + +var utf8bom = []byte{0xEF, 0xBB, 0xBF} + +// DirLoader loads a chart from a directory +type DirLoader string + +// Load loads the chart +func (l DirLoader) Load() (*chart.Chart, error) { + return LoadDir(string(l)) +} + +// LoadDir loads from a directory. +// +// This loads charts only from directories. +func LoadDir(dir string) (*chart.Chart, error) { + topdir, err := filepath.Abs(dir) + if err != nil { + return nil, err + } + + // Just used for errors. + c := &chart.Chart{} + + rules := ignore.Empty() + ifile := filepath.Join(topdir, ignore.HelmIgnore) + if _, err := os.Stat(ifile); err == nil { + r, err := ignore.ParseFile(ifile) + if err != nil { + return c, err + } + rules = r + } + rules.AddDefaults() + + files := []*archive.BufferedFile{} + topdir += string(filepath.Separator) + + walk := func(name string, fi os.FileInfo, err error) error { + n := strings.TrimPrefix(name, topdir) + if n == "" { + // No need to process top level. Avoid bug with helmignore .* matching + // empty names. See issue 1779. + return nil + } + + // Normalize to / since it will also work on Windows + n = filepath.ToSlash(n) + + if err != nil { + return err + } + if fi.IsDir() { + // Directory-based ignore rules should involve skipping the entire + // contents of that directory. + if rules.Ignore(n, fi) { + return filepath.SkipDir + } + return nil + } + + // If a .helmignore file matches, skip this file. + if rules.Ignore(n, fi) { + return nil + } + + // Irregular files include devices, sockets, and other uses of files that + // are not regular files. In Go they have a file mode type bit set. + // See https://golang.org/pkg/os/#FileMode for examples. + if !fi.Mode().IsRegular() { + return fmt.Errorf("cannot load irregular file %s as it has file mode type bits set", name) + } + + if fi.Size() > archive.MaxDecompressedFileSize { + return fmt.Errorf("chart file %q is larger than the maximum file size %d", fi.Name(), archive.MaxDecompressedFileSize) + } + + data, err := os.ReadFile(name) + if err != nil { + return fmt.Errorf("error reading %s: %w", n, err) + } + + data = bytes.TrimPrefix(data, utf8bom) + + files = append(files, &archive.BufferedFile{Name: n, ModTime: fi.ModTime(), Data: data}) + return nil + } + if err = sympath.Walk(topdir, walk); err != nil { + return c, err + } + + return LoadFiles(files) +} diff --git a/vendor/helm.sh/helm/v4/internal/chart/v3/loader/load.go b/vendor/helm.sh/helm/v4/internal/chart/v3/loader/load.go new file mode 100644 index 00000000000..373c4659f60 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/chart/v3/loader/load.go @@ -0,0 +1,222 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package loader + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "maps" + "os" + "path/filepath" + "slices" + "strings" + + utilyaml "k8s.io/apimachinery/pkg/util/yaml" + "sigs.k8s.io/yaml" + + chart "helm.sh/helm/v4/internal/chart/v3" + "helm.sh/helm/v4/pkg/chart/common" + "helm.sh/helm/v4/pkg/chart/loader/archive" +) + +// ChartLoader loads a chart. +type ChartLoader interface { + Load() (*chart.Chart, error) +} + +// Loader returns a new ChartLoader appropriate for the given chart name +func Loader(name string) (ChartLoader, error) { + fi, err := os.Stat(name) + if err != nil { + return nil, err + } + if fi.IsDir() { + return DirLoader(name), nil + } + return FileLoader(name), nil +} + +// Load takes a string name, tries to resolve it to a file or directory, and then loads it. +// +// This is the preferred way to load a chart. It will discover the chart encoding +// and hand off to the appropriate chart reader. +// +// If a .helmignore file is present, the directory loader will skip loading any files +// matching it. But .helmignore is not evaluated when reading out of an archive. +func Load(name string) (*chart.Chart, error) { + l, err := Loader(name) + if err != nil { + return nil, err + } + return l.Load() +} + +// LoadFiles loads from in-memory files. +func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { + c := new(chart.Chart) + subcharts := make(map[string][]*archive.BufferedFile) + var subChartsKeys []string + + // do not rely on assumed ordering of files in the chart and crash + // if Chart.yaml was not coming early enough to initialize metadata + for _, f := range files { + c.Raw = append(c.Raw, &common.File{Name: f.Name, ModTime: f.ModTime, Data: f.Data}) + if f.Name == "Chart.yaml" { + if c.Metadata == nil { + c.Metadata = new(chart.Metadata) + } + if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil { + return c, fmt.Errorf("cannot load Chart.yaml: %w", err) + } + // While the documentation says the APIVersion is required, in practice there + // are cases where that's not enforced. Since this package set is for v3 charts, + // when this function is used v3 is automatically added when not present. + if c.Metadata.APIVersion == "" { + c.Metadata.APIVersion = chart.APIVersionV3 + } + c.ModTime = f.ModTime + } + } + for _, f := range files { + switch { + case f.Name == "Chart.yaml": + // already processed + continue + case f.Name == "Chart.lock": + c.Lock = new(chart.Lock) + if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil { + return c, fmt.Errorf("cannot load Chart.lock: %w", err) + } + case f.Name == "values.yaml": + values, err := LoadValues(bytes.NewReader(f.Data)) + if err != nil { + return c, fmt.Errorf("cannot load values.yaml: %w", err) + } + c.Values = values + case f.Name == "values.schema.json": + c.Schema = f.Data + c.SchemaModTime = f.ModTime + + case strings.HasPrefix(f.Name, "templates/"): + c.Templates = append(c.Templates, &common.File{Name: f.Name, Data: f.Data, ModTime: f.ModTime}) + case strings.HasPrefix(f.Name, "charts/"): + if filepath.Ext(f.Name) == ".prov" { + c.Files = append(c.Files, &common.File{Name: f.Name, Data: f.Data, ModTime: f.ModTime}) + continue + } + + fname := strings.TrimPrefix(f.Name, "charts/") + cname := strings.SplitN(fname, "/", 2)[0] + if slices.Index(subChartsKeys, cname) == -1 { + subChartsKeys = append(subChartsKeys, cname) + } + subcharts[cname] = append(subcharts[cname], &archive.BufferedFile{Name: fname, ModTime: f.ModTime, Data: f.Data}) + default: + c.Files = append(c.Files, &common.File{Name: f.Name, ModTime: f.ModTime, Data: f.Data}) + } + } + + if c.Metadata == nil { + return c, errors.New("Chart.yaml file is missing") //nolint:staticcheck + } + + if err := c.Validate(); err != nil { + return c, err + } + + for n, files := range subcharts { + var sc *chart.Chart + var err error + switch { + case strings.IndexAny(n, "_.") == 0: + continue + case filepath.Ext(n) == ".tgz": + file := files[0] + if file.Name != n { + return c, fmt.Errorf("error unpacking subchart tar in %s: expected %s, got %s", c.Name(), n, file.Name) + } + // Untar the chart and add to c.Dependencies + sc, err = LoadArchive(bytes.NewBuffer(file.Data)) + default: + // We have to trim the prefix off of every file, and ignore any file + // that is in charts/, but isn't actually a chart. + buff := make([]*archive.BufferedFile, 0, len(files)) + for _, f := range files { + parts := strings.SplitN(f.Name, "/", 2) + if len(parts) < 2 { + continue + } + f.Name = parts[1] + buff = append(buff, f) + } + sc, err = LoadFiles(buff) + } + + if err != nil { + return c, fmt.Errorf("error unpacking subchart %s in %s: %w", n, c.Name(), err) + } + c.AddDependency(sc) + } + + return c, nil +} + +// LoadValues loads values from a reader. +// +// The reader is expected to contain one or more YAML documents, the values of which are merged. +// And the values can be either a chart's default values or user-supplied values. +func LoadValues(data io.Reader) (map[string]interface{}, error) { + values := map[string]interface{}{} + reader := utilyaml.NewYAMLReader(bufio.NewReader(data)) + for { + currentMap := map[string]interface{}{} + raw, err := reader.Read() + if err != nil { + if errors.Is(err, io.EOF) { + break + } + return nil, fmt.Errorf("error reading yaml document: %w", err) + } + if err := yaml.Unmarshal(raw, ¤tMap); err != nil { + return nil, fmt.Errorf("cannot unmarshal yaml document: %w", err) + } + values = MergeMaps(values, currentMap) + } + return values, nil +} + +// MergeMaps merges two maps. If a key exists in both maps, the value from b will be used. +// If the value is a map, the maps will be merged recursively. +func MergeMaps(a, b map[string]interface{}) map[string]interface{} { + out := make(map[string]interface{}, len(a)) + maps.Copy(out, a) + for k, v := range b { + if v, ok := v.(map[string]interface{}); ok { + if bv, ok := out[k]; ok { + if bv, ok := bv.(map[string]interface{}); ok { + out[k] = MergeMaps(bv, v) + continue + } + } + } + out[k] = v + } + return out +} diff --git a/vendor/helm.sh/helm/v4/internal/chart/v3/metadata.go b/vendor/helm.sh/helm/v4/internal/chart/v3/metadata.go new file mode 100644 index 00000000000..5f7cea897d0 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/chart/v3/metadata.go @@ -0,0 +1,181 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v3 + +import ( + "path/filepath" + "strings" + "unicode" + + "github.com/Masterminds/semver/v3" +) + +// Maintainer describes a Chart maintainer. +type Maintainer struct { + // Name is a user name or organization name + Name string `json:"name,omitempty"` + // Email is an optional email address to contact the named maintainer + Email string `json:"email,omitempty"` + // URL is an optional URL to an address for the named maintainer + URL string `json:"url,omitempty"` +} + +// Validate checks valid data and sanitizes string characters. +func (m *Maintainer) Validate() error { + if m == nil { + return ValidationError("maintainers must not contain empty or null nodes") + } + m.Name = sanitizeString(m.Name) + m.Email = sanitizeString(m.Email) + m.URL = sanitizeString(m.URL) + return nil +} + +// Metadata for a Chart file. This models the structure of a Chart.yaml file. +type Metadata struct { + // The name of the chart. Required. + Name string `json:"name,omitempty"` + // The URL to a relevant project page, git repo, or contact person + Home string `json:"home,omitempty"` + // Source is the URL to the source code of this chart + Sources []string `json:"sources,omitempty"` + // A SemVer 2 conformant version string of the chart. Required. + Version string `json:"version,omitempty"` + // A one-sentence description of the chart + Description string `json:"description,omitempty"` + // A list of string keywords + Keywords []string `json:"keywords,omitempty"` + // A list of name and URL/email address combinations for the maintainer(s) + Maintainers []*Maintainer `json:"maintainers,omitempty"` + // The URL to an icon file. + Icon string `json:"icon,omitempty"` + // The API Version of this chart. Required. + APIVersion string `json:"apiVersion,omitempty"` + // The condition to check to enable chart + Condition string `json:"condition,omitempty"` + // The tags to check to enable chart + Tags string `json:"tags,omitempty"` + // The version of the application enclosed inside of this chart. + AppVersion string `json:"appVersion,omitempty"` + // Whether or not this chart is deprecated + Deprecated bool `json:"deprecated,omitempty"` + // Annotations are additional mappings uninterpreted by Helm, + // made available for inspection by other applications. + Annotations map[string]string `json:"annotations,omitempty"` + // KubeVersion is a SemVer constraint specifying the version of Kubernetes required. + KubeVersion string `json:"kubeVersion,omitempty"` + // Dependencies are a list of dependencies for a chart. + Dependencies []*Dependency `json:"dependencies,omitempty"` + // Specifies the chart type: application or library + Type string `json:"type,omitempty"` +} + +// Validate checks the metadata for known issues and sanitizes string +// characters. +func (md *Metadata) Validate() error { + if md == nil { + return ValidationError("chart.metadata is required") + } + + md.Name = sanitizeString(md.Name) + md.Description = sanitizeString(md.Description) + md.Home = sanitizeString(md.Home) + md.Icon = sanitizeString(md.Icon) + md.Condition = sanitizeString(md.Condition) + md.Tags = sanitizeString(md.Tags) + md.AppVersion = sanitizeString(md.AppVersion) + md.KubeVersion = sanitizeString(md.KubeVersion) + for i := range md.Sources { + md.Sources[i] = sanitizeString(md.Sources[i]) + } + for i := range md.Keywords { + md.Keywords[i] = sanitizeString(md.Keywords[i]) + } + + if md.APIVersion == "" { + return ValidationError("chart.metadata.apiVersion is required") + } + if md.Name == "" { + return ValidationError("chart.metadata.name is required") + } + + if md.Name == "." || md.Name == ".." { + return ValidationErrorf("chart.metadata.name %q is not allowed", md.Name) + } + if md.Name != filepath.Base(md.Name) { + return ValidationErrorf("chart.metadata.name %q is invalid", md.Name) + } + + if md.Version == "" { + return ValidationError("chart.metadata.version is required") + } + if !isValidSemver(md.Version) { + return ValidationErrorf("chart.metadata.version %q is invalid", md.Version) + } + if !isValidChartType(md.Type) { + return ValidationError("chart.metadata.type must be application or library") + } + + for _, m := range md.Maintainers { + if err := m.Validate(); err != nil { + return err + } + } + + // Aliases need to be validated here to make sure that the alias name does + // not contain any illegal characters. + dependencies := map[string]*Dependency{} + for _, dependency := range md.Dependencies { + if err := dependency.Validate(); err != nil { + return err + } + key := dependency.Name + if dependency.Alias != "" { + key = dependency.Alias + } + if dependencies[key] != nil { + return ValidationErrorf("more than one dependency with name or alias %q", key) + } + dependencies[key] = dependency + } + return nil +} + +func isValidChartType(in string) bool { + switch in { + case "", "application", "library": + return true + } + return false +} + +func isValidSemver(v string) bool { + _, err := semver.NewVersion(v) + return err == nil +} + +// sanitizeString normalize spaces and removes non-printable characters. +func sanitizeString(str string) string { + return strings.Map(func(r rune) rune { + if unicode.IsSpace(r) { + return ' ' + } + if unicode.IsPrint(r) { + return r + } + return -1 + }, str) +} diff --git a/vendor/helm.sh/helm/v4/internal/copystructure/copystructure.go b/vendor/helm.sh/helm/v4/internal/copystructure/copystructure.go new file mode 100644 index 00000000000..c55897aaa10 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/copystructure/copystructure.go @@ -0,0 +1,128 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package copystructure + +import ( + "fmt" + "reflect" +) + +// Copy performs a deep copy of the given src. +// This implementation handles the specific use cases needed by Helm. +func Copy(src any) (any, error) { + if src == nil { + return make(map[string]any), nil + } + return copyValue(reflect.ValueOf(src)) +} + +// copyValue handles copying using reflection for non-map types +func copyValue(original reflect.Value) (any, error) { + switch original.Kind() { + case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, + reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, + reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64, + reflect.Complex64, reflect.Complex128, reflect.String, reflect.Array: + return original.Interface(), nil + + case reflect.Interface: + if original.IsNil() { + return original.Interface(), nil + } + return copyValue(original.Elem()) + + case reflect.Map: + if original.IsNil() { + return original.Interface(), nil + } + copied := reflect.MakeMap(original.Type()) + + var err error + var child any + iter := original.MapRange() + for iter.Next() { + key := iter.Key() + value := iter.Value() + + if value.Kind() == reflect.Interface && value.IsNil() { + copied.SetMapIndex(key, value) + continue + } + + child, err = copyValue(value) + if err != nil { + return nil, err + } + copied.SetMapIndex(key, reflect.ValueOf(child)) + } + return copied.Interface(), nil + + case reflect.Pointer: + if original.IsNil() { + return original.Interface(), nil + } + copied, err := copyValue(original.Elem()) + if err != nil { + return nil, err + } + ptr := reflect.New(original.Type().Elem()) + ptr.Elem().Set(reflect.ValueOf(copied)) + return ptr.Interface(), nil + + case reflect.Slice: + if original.IsNil() { + return original.Interface(), nil + } + copied := reflect.MakeSlice(original.Type(), original.Len(), original.Cap()) + for i := 0; i < original.Len(); i++ { + elem := original.Index(i) + + // Handle nil values in slices (e.g., interface{} elements that are nil) + if elem.Kind() == reflect.Interface && elem.IsNil() { + copied.Index(i).Set(elem) + continue + } + + val, err := copyValue(elem) + if err != nil { + return nil, err + } + copied.Index(i).Set(reflect.ValueOf(val)) + } + return copied.Interface(), nil + + case reflect.Struct: + copied := reflect.New(original.Type()).Elem() + for i := 0; i < original.NumField(); i++ { + elem, err := copyValue(original.Field(i)) + if err != nil { + return nil, err + } + copied.Field(i).Set(reflect.ValueOf(elem)) + } + return copied.Interface(), nil + + case reflect.Func, reflect.Chan, reflect.UnsafePointer: + if original.IsNil() { + return original.Interface(), nil + } + return original.Interface(), nil + + default: + return original.Interface(), fmt.Errorf("unsupported type %v", original) + } +} diff --git a/vendor/helm.sh/helm/v4/internal/fileutil/fileutil.go b/vendor/helm.sh/helm/v4/internal/fileutil/fileutil.go new file mode 100644 index 00000000000..39e0e330f4d --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/fileutil/fileutil.go @@ -0,0 +1,50 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fileutil + +import ( + "io" + "os" + "path/filepath" + + "helm.sh/helm/v4/internal/third_party/dep/fs" +) + +// AtomicWriteFile atomically (as atomic as os.Rename allows) writes a file to a +// disk. +func AtomicWriteFile(filename string, reader io.Reader, mode os.FileMode) error { + tempFile, err := os.CreateTemp(filepath.Split(filename)) + if err != nil { + return err + } + tempName := tempFile.Name() + + if _, err := io.Copy(tempFile, reader); err != nil { + tempFile.Close() // return value is ignored as we are already on error path + return err + } + + if err := tempFile.Close(); err != nil { + return err + } + + if err := os.Chmod(tempName, mode); err != nil { + return err + } + + return fs.RenameWithFallback(tempName, filename) +} diff --git a/vendor/helm.sh/helm/v4/internal/fileutil/fileutil_unix.go b/vendor/helm.sh/helm/v4/internal/fileutil/fileutil_unix.go new file mode 100644 index 00000000000..bbacb10bf63 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/fileutil/fileutil_unix.go @@ -0,0 +1,32 @@ +//go:build !windows + +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fileutil + +import ( + "io" + "os" +) + +// PlatformAtomicWriteFile atomically writes a file to disk. +// +// On non-Windows platforms we don't need extra coordination, so this simply +// delegates to AtomicWriteFile to preserve the existing overwrite behaviour. +func PlatformAtomicWriteFile(filename string, reader io.Reader, mode os.FileMode) error { + return AtomicWriteFile(filename, reader, mode) +} diff --git a/vendor/helm.sh/helm/v4/internal/fileutil/fileutil_windows.go b/vendor/helm.sh/helm/v4/internal/fileutil/fileutil_windows.go new file mode 100644 index 00000000000..17923786035 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/fileutil/fileutil_windows.go @@ -0,0 +1,54 @@ +//go:build windows + +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fileutil + +import ( + "io" + "os" + + "github.com/gofrs/flock" +) + +// PlatformAtomicWriteFile atomically writes a file to disk with file locking to +// prevent concurrent writes. This is particularly useful on Windows where +// concurrent writes to the same file can cause "Access Denied" errors. +// +// The function acquires a lock on the target file and performs an atomic write, +// preserving the existing behaviour of overwriting any previous content once +// the lock is obtained. +func PlatformAtomicWriteFile(filename string, reader io.Reader, mode os.FileMode) error { + // Use a separate lock file to coordinate access between processes + // We cannot lock the target file directly as it would prevent the atomic rename + lockFileName := filename + ".lock" + fileLock := flock.New(lockFileName) + + // Lock() ensures serialized access - if another process is writing, this will wait + if err := fileLock.Lock(); err != nil { + return err + } + defer func() { + fileLock.Unlock() + // Clean up the lock file + // Ignore errors as the file might not exist or be in use by another process + os.Remove(lockFileName) + }() + + // Perform the atomic write while holding the lock + return AtomicWriteFile(filename, reader, mode) +} diff --git a/vendor/helm.sh/helm/v4/internal/logging/logging.go b/vendor/helm.sh/helm/v4/internal/logging/logging.go new file mode 100644 index 00000000000..674e2db34b6 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/logging/logging.go @@ -0,0 +1,125 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package logging + +import ( + "context" + "log/slog" + "os" + "sync/atomic" +) + +// DebugEnabledFunc is a function type that determines if debug logging is enabled +// We use a function because we want to check the setting at log time, not when the logger is created +type DebugEnabledFunc func() bool + +// DebugCheckHandler checks settings.Debug at log time +type DebugCheckHandler struct { + handler slog.Handler + debugEnabled DebugEnabledFunc +} + +// Enabled implements slog.Handler.Enabled +func (h *DebugCheckHandler) Enabled(_ context.Context, level slog.Level) bool { + if level == slog.LevelDebug { + if h.debugEnabled == nil { + return false + } + return h.debugEnabled() + } + return true // Always log other levels +} + +// Handle implements slog.Handler.Handle +func (h *DebugCheckHandler) Handle(ctx context.Context, r slog.Record) error { + return h.handler.Handle(ctx, r) +} + +// WithAttrs implements slog.Handler.WithAttrs +func (h *DebugCheckHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + return &DebugCheckHandler{ + handler: h.handler.WithAttrs(attrs), + debugEnabled: h.debugEnabled, + } +} + +// WithGroup implements slog.Handler.WithGroup +func (h *DebugCheckHandler) WithGroup(name string) slog.Handler { + return &DebugCheckHandler{ + handler: h.handler.WithGroup(name), + debugEnabled: h.debugEnabled, + } +} + +// NewLogger creates a new logger with dynamic debug checking +func NewLogger(debugEnabled DebugEnabledFunc) *slog.Logger { + // Create base handler that removes timestamps + baseHandler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ + // Always use LevelDebug here to allow all messages through + // Our custom handler will do the filtering + Level: slog.LevelDebug, + ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr { + // Remove the time attribute + if a.Key == slog.TimeKey { + return slog.Attr{} + } + return a + }, + }) + + // Wrap with our dynamic debug-checking handler + dynamicHandler := &DebugCheckHandler{ + handler: baseHandler, + debugEnabled: debugEnabled, + } + + return slog.New(dynamicHandler) +} + +// LoggerSetterGetter is an interface that can set and get a logger +type LoggerSetterGetter interface { + // SetLogger sets a new slog.Handler + SetLogger(newHandler slog.Handler) + // Logger returns the slog.Logger created from the slog.Handler + Logger() *slog.Logger +} + +type LogHolder struct { + // logger is an atomic.Pointer[slog.Logger] to store the slog.Logger + // We use atomic.Pointer for thread safety + logger atomic.Pointer[slog.Logger] +} + +// Logger returns the logger for the LogHolder. If nil, returns slog.Default(). +func (l *LogHolder) Logger() *slog.Logger { + if lg := l.logger.Load(); lg != nil { + return lg + } + return slog.New(slog.DiscardHandler) // Should never be reached +} + +// SetLogger sets the logger for the LogHolder. If nil, sets the default logger. +func (l *LogHolder) SetLogger(newHandler slog.Handler) { + if newHandler == nil { + l.logger.Store(slog.New(slog.DiscardHandler)) // Assume nil as discarding logs + return + } + l.logger.Store(slog.New(newHandler)) +} + +// Ensure LogHolder implements LoggerSetterGetter +var _ LoggerSetterGetter = &LogHolder{} diff --git a/vendor/helm.sh/helm/v4/internal/plugin/config.go b/vendor/helm.sh/helm/v4/internal/plugin/config.go new file mode 100644 index 00000000000..785bde84026 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/config.go @@ -0,0 +1,53 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +import ( + "bytes" + "fmt" + "reflect" + + "go.yaml.in/yaml/v3" +) + +// Config represents a plugin type specific configuration +// It is expected to type assert (cast) the Config to its expected underlying type (schema.ConfigCLIV1, schema.ConfigGetterV1, etc). +type Config interface { + Validate() error +} + +func unmarshalConfig(pluginType string, configData map[string]any) (Config, error) { + pluginTypeMeta, ok := pluginTypesIndex[pluginType] + if !ok { + return nil, fmt.Errorf("unknown plugin type %q", pluginType) + } + + // TODO: Avoid (yaml) serialization/deserialization for type conversion here + + data, err := yaml.Marshal(configData) + if err != nil { + return nil, fmt.Errorf("failed to marshel config data (plugin type %s): %w", pluginType, err) + } + + config := reflect.New(pluginTypeMeta.configType) + d := yaml.NewDecoder(bytes.NewReader(data)) + d.KnownFields(true) + if err := d.Decode(config.Interface()); err != nil { + return nil, err + } + + return config.Interface().(Config), nil +} diff --git a/vendor/helm.sh/helm/v4/internal/plugin/descriptor.go b/vendor/helm.sh/helm/v4/internal/plugin/descriptor.go new file mode 100644 index 00000000000..ba92b3c5518 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/descriptor.go @@ -0,0 +1,24 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +// Descriptor describes a plugin to find +type Descriptor struct { + // Name is the name of the plugin + Name string + // Type is the type of the plugin (cli, getter, postrenderer) + Type string +} diff --git a/vendor/helm.sh/helm/v4/internal/plugin/doc.go b/vendor/helm.sh/helm/v4/internal/plugin/doc.go new file mode 100644 index 00000000000..39ba6300b7b --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/doc.go @@ -0,0 +1,89 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +--- +TODO: move this section to public plugin package + +Package plugin provides the implementation of the Helm plugin system. + +Conceptually, "plugins" enable extending Helm's functionality external to Helm's core codebase. The plugin system allows +code to fetch plugins by type, then invoke the plugin with an input as required by that plugin type. The plugin +returning an output for the caller to consume. + +An example of a plugin invocation: +``` +d := plugin.Descriptor{ + Type: "example/v1", // +} +plgs, err := plugin.FindPlugins([]string{settings.PluginsDirectory}, d) + +for _, plg := range plgs { + input := &plugin.Input{ + Message: schema.InputMessageExampleV1{ // The type of the input message is defined by the plugin's "type" (example/v1 here) + ... + }, + } + output, err := plg.Invoke(context.Background(), input) + if err != nil { + ... + } + + // consume the output, using type assertion to convert to the expected output type (as defined by the plugin's "type") + outputMessage, ok := output.Message.(schema.OutputMessageExampleV1) +} + +--- + +Package `plugin` provides the implementation of the Helm plugin system. + +Helm plugins are exposed to uses as the "Plugin" type, the basic interface that primarily support the "Invoke" method. + +# Plugin Runtimes +Internally, plugins must be implemented by a "runtime" that is responsible for creating the plugin instance, and dispatching the plugin's invocation to the plugin's implementation. +For example: +- forming environment variables and command line args for subprocess execution +- converting input to JSON and invoking a function in a Wasm runtime + +Internally, the code structure is: +Runtime.CreatePlugin() + | + | (creates) + | + \---> PluginRuntime + | + | (implements) + v + Plugin.Invoke() + +# Plugin Types +Each plugin implements a specific functionality, denoted by the plugin's "type" e.g. "getter/v1". The "type" includes a version, in order to allow a given types messaging schema and invocation options to evolve. + +Specifically, the plugin's "type" specifies the contract for the input and output messages that are expected to be passed to the plugin, and returned from the plugin. The plugin's "type" also defines the options that can be passed to the plugin when invoking it. + +# Metadata +Each plugin must have a `plugin.yaml`, that defines the plugin's metadata. The metadata includes the plugin's name, version, and other information. + +For legacy plugins, the type is inferred by which fields are set on the plugin: a downloader plugin is inferred when metadata contains a "downloaders" yaml node, otherwise it is assumed to define a Helm CLI subcommand. + +For v1 plugins, the metadata includes explicit apiVersion and type fields. It will also contain type-specific Config, and RuntimeConfig fields. + +# Runtime and type cardinality +From a cardinality perspective, this means there a "few" runtimes, and "many" plugins types. It is also expected that the subprocess runtime will not be extended to support extra plugin types, and deprecated in a future version of Helm. + +Future ideas that are intended to be implemented include extending the plugin system to support future Wasm standards. Or allowing Helm SDK user's to inject "plugins" that are actually implemented as native go modules. Or even moving Helm's internal functionality e.g. yaml rendering engine to be used as an "in-built" plugin, along side other plugins that may implement other (non-go template) rendering engines. +*/ + +package plugin diff --git a/vendor/helm.sh/helm/v4/internal/plugin/error.go b/vendor/helm.sh/helm/v4/internal/plugin/error.go new file mode 100644 index 00000000000..212460cea94 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/error.go @@ -0,0 +1,29 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +// InvokeExecError is returned when a plugin invocation returns a non-zero status/exit code +// - subprocess plugin: child process exit code +// - extism plugin: wasm function return code +type InvokeExecError struct { + ExitCode int // Exit code from plugin code execution + Err error // Underlying error +} + +// Error implements the error interface +func (e *InvokeExecError) Error() string { + return e.Err.Error() +} diff --git a/vendor/helm.sh/helm/v4/internal/plugin/loader.go b/vendor/helm.sh/helm/v4/internal/plugin/loader.go new file mode 100644 index 00000000000..d99395641d8 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/loader.go @@ -0,0 +1,289 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +import ( + "bytes" + "fmt" + "io" + "log/slog" + "os" + "path/filepath" + + extism "github.com/extism/go-sdk" + "github.com/tetratelabs/wazero" + "go.yaml.in/yaml/v3" + + "helm.sh/helm/v4/pkg/helmpath" +) + +func peekAPIVersion(r io.Reader) (string, error) { + type apiVersion struct { + APIVersion string `yaml:"apiVersion"` + } + + var v apiVersion + d := yaml.NewDecoder(r) + if err := d.Decode(&v); err != nil { + return "", err + } + + return v.APIVersion, nil +} + +func loadMetadataLegacy(metadataData []byte) (*Metadata, error) { + + var ml MetadataLegacy + d := yaml.NewDecoder(bytes.NewReader(metadataData)) + // NOTE: No strict unmarshalling for legacy plugins - maintain backwards compatibility + if err := d.Decode(&ml); err != nil { + return nil, err + } + + if err := ml.Validate(); err != nil { + return nil, err + } + + m := fromMetadataLegacy(ml) + if err := m.Validate(); err != nil { + return nil, err + } + return m, nil +} + +func loadMetadataV1(metadataData []byte) (*Metadata, error) { + + var mv1 MetadataV1 + d := yaml.NewDecoder(bytes.NewReader(metadataData)) + d.KnownFields(true) + if err := d.Decode(&mv1); err != nil { + return nil, err + } + + if err := mv1.Validate(); err != nil { + return nil, err + } + + m, err := fromMetadataV1(mv1) + if err != nil { + return nil, fmt.Errorf("failed to convert MetadataV1 to Metadata: %w", err) + } + + if err := m.Validate(); err != nil { + return nil, err + } + return m, nil +} + +func loadMetadata(metadataData []byte) (*Metadata, error) { + apiVersion, err := peekAPIVersion(bytes.NewReader(metadataData)) + if err != nil { + return nil, fmt.Errorf("failed to peek %s API version: %w", PluginFileName, err) + } + + switch apiVersion { + case "": // legacy + return loadMetadataLegacy(metadataData) + case "v1": + return loadMetadataV1(metadataData) + } + + return nil, fmt.Errorf("invalid plugin apiVersion: %q", apiVersion) +} + +type prototypePluginManager struct { + runtimes map[string]Runtime +} + +func newPrototypePluginManager() (*prototypePluginManager, error) { + + cc, err := wazero.NewCompilationCacheWithDir(helmpath.CachePath("wazero-build")) + if err != nil { + return nil, fmt.Errorf("failed to create wazero compilation cache: %w", err) + } + + return &prototypePluginManager{ + runtimes: map[string]Runtime{ + "subprocess": &RuntimeSubprocess{}, + "extism/v1": &RuntimeExtismV1{ + HostFunctions: map[string]extism.HostFunction{}, + CompilationCache: cc, + }, + }, + }, nil +} + +func (pm *prototypePluginManager) RegisterRuntime(runtimeName string, runtime Runtime) { + pm.runtimes[runtimeName] = runtime +} + +func (pm *prototypePluginManager) CreatePlugin(pluginPath string, metadata *Metadata) (Plugin, error) { + rt, ok := pm.runtimes[metadata.Runtime] + if !ok { + return nil, fmt.Errorf("unsupported plugin runtime type: %q", metadata.Runtime) + } + + return rt.CreatePlugin(pluginPath, metadata) +} + +// LoadDir loads a plugin from the given directory. +func LoadDir(dirname string) (Plugin, error) { + pluginfile := filepath.Join(dirname, PluginFileName) + metadataData, err := os.ReadFile(pluginfile) + if err != nil { + return nil, fmt.Errorf("failed to read plugin at %q: %w", pluginfile, err) + } + + m, err := loadMetadata(metadataData) + if err != nil { + return nil, fmt.Errorf("failed to load plugin %q: %w", dirname, err) + } + + pm, err := newPrototypePluginManager() + if err != nil { + return nil, fmt.Errorf("failed to create plugin manager: %w", err) + } + return pm.CreatePlugin(dirname, m) +} + +func LogIgnorePluginLoadErrorFilterFunc(pluginYAML string, err error) error { + slog.Warn("failed to load plugin (ignoring)", slog.String("plugin_yaml", pluginYAML), slog.Any("error", err)) + return nil +} + +// errorFilterFunc is a function that can filter errors during plugin loading +type ErrorFilterFunc func(string, error) error + +// LoadAllDir load all plugins found beneath the base directory, using the provided error filter to determine whether to fail on individual plugin load errors. +// +// This scans only one directory level. +func LoadAllDir(basedir string, errorFilter ErrorFilterFunc) ([]Plugin, error) { + // We want /*/plugin.yaml + scanpath := filepath.Join(basedir, "*", PluginFileName) + matches, err := filepath.Glob(scanpath) + if err != nil { + return nil, fmt.Errorf("failed to search for plugins in %q: %w", scanpath, err) + } + + plugins := make([]Plugin, 0, len(matches)) + + // empty dir should load + if len(matches) == 0 { + return plugins, nil + } + + for _, yamlFile := range matches { + dir := filepath.Dir(yamlFile) + p, err := LoadDir(dir) + if err != nil { + if errNew := errorFilter(yamlFile, err); errNew != nil { + return plugins, errNew + } + } else { + plugins = append(plugins, p) + } + } + return plugins, detectDuplicates(plugins) +} + +// findFunc is a function that finds plugins in a directory +type findFunc func(pluginsDir string) ([]Plugin, error) + +// filterFunc is a function that filters plugins +type filterFunc func(Plugin) bool + +// FindPlugins returns a list of plugins that match the descriptor +// Errors loading a plugin are ignored with a warning +func FindPlugins(pluginsDirs []string, descriptor Descriptor) ([]Plugin, error) { + loadAllIgnoreErrors := func(pluginsDir string) ([]Plugin, error) { + return LoadAllDir(pluginsDir, LogIgnorePluginLoadErrorFilterFunc) + } + return findPlugins(pluginsDirs, loadAllIgnoreErrors, makeDescriptorFilter(descriptor)) +} + +// findPlugins is the internal implementation that uses the find and filter functions +func findPlugins(pluginsDirs []string, findFn findFunc, filterFn filterFunc) ([]Plugin, error) { + var found []Plugin + for _, pluginsDir := range pluginsDirs { + ps, err := findFn(pluginsDir) + + if err != nil { + return nil, err + } + + for _, p := range ps { + if filterFn(p) { + found = append(found, p) + } + } + + } + + return found, nil +} + +// makeDescriptorFilter creates a filter function from a descriptor +// Additional plugin filter criteria we wish to support can be added here +func makeDescriptorFilter(descriptor Descriptor) filterFunc { + return func(p Plugin) bool { + // If name is specified, it must match + if descriptor.Name != "" && p.Metadata().Name != descriptor.Name { + return false + + } + // If type is specified, it must match + if descriptor.Type != "" && p.Metadata().Type != descriptor.Type { + return false + } + return true + } +} + +// FindPlugin returns a single plugin that matches the descriptor +func FindPlugin(dirs []string, descriptor Descriptor) (Plugin, error) { + loadAllIgnoreErrors := func(pluginsDir string) ([]Plugin, error) { + return LoadAllDir(pluginsDir, LogIgnorePluginLoadErrorFilterFunc) + } + + plugins, err := findPlugins(dirs, loadAllIgnoreErrors, makeDescriptorFilter(descriptor)) + if err != nil { + return nil, err + } + + if len(plugins) > 0 { + return plugins[0], nil + } + + return nil, fmt.Errorf("plugin: %+v not found", descriptor) +} + +func detectDuplicates(plugs []Plugin) error { + names := map[string]string{} + + for _, plug := range plugs { + if oldpath, ok := names[plug.Metadata().Name]; ok { + return fmt.Errorf( + "two plugins claim the name %q at %q and %q", + plug.Metadata().Name, + oldpath, + plug.Dir(), + ) + } + names[plug.Metadata().Name] = plug.Dir() + } + + return nil +} diff --git a/vendor/helm.sh/helm/v4/internal/plugin/metadata.go b/vendor/helm.sh/helm/v4/internal/plugin/metadata.go new file mode 100644 index 00000000000..29cc7e9f82c --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/metadata.go @@ -0,0 +1,229 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +import ( + "errors" + "fmt" + + "github.com/Masterminds/semver/v3" + + "helm.sh/helm/v4/internal/plugin/schema" +) + +// isValidSemver checks if the given string is a valid semantic version +func isValidSemver(v string) bool { + _, err := semver.StrictNewVersion(v) + return err == nil +} + +// Metadata of a plugin, converted from the "on-disk" legacy or v1 plugin.yaml +// Specifically, Config and RuntimeConfig are converted to their respective types based on the plugin type and runtime +type Metadata struct { + // APIVersion specifies the plugin API version + APIVersion string + + // Name is the name of the plugin + Name string + + // Type of plugin (eg, cli/v1, getter/v1, postrenderer/v1) + Type string + + // Runtime specifies the runtime type (subprocess, wasm) + Runtime string + + // Version is the SemVer 2 version of the plugin. + Version string + + // SourceURL is the URL where this plugin can be found + SourceURL string + + // Config contains the type-specific configuration for this plugin + Config Config + + // RuntimeConfig contains the runtime-specific configuration + RuntimeConfig RuntimeConfig +} + +func (m Metadata) Validate() error { + var errs []error + + if !validPluginName.MatchString(m.Name) { + errs = append(errs, fmt.Errorf("invalid plugin name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name)) + } + + // Require version to be valid semver if specified + if m.Version != "" && !isValidSemver(m.Version) { + errs = append(errs, fmt.Errorf("invalid plugin version %q: must be valid semver", m.Version)) + } + + if m.APIVersion == "" { + errs = append(errs, fmt.Errorf("empty APIVersion")) + } + + if m.Type == "" { + errs = append(errs, fmt.Errorf("empty type field")) + } + + if m.Runtime == "" { + errs = append(errs, fmt.Errorf("empty runtime field")) + } + + if m.Config == nil { + errs = append(errs, fmt.Errorf("missing config field")) + } + + if m.RuntimeConfig == nil { + errs = append(errs, fmt.Errorf("missing runtimeConfig field")) + } + + // Validate the config itself + if m.Config != nil { + if err := m.Config.Validate(); err != nil { + errs = append(errs, fmt.Errorf("config validation failed: %w", err)) + } + } + + // Validate the runtime config itself + if m.RuntimeConfig != nil { + if err := m.RuntimeConfig.Validate(); err != nil { + errs = append(errs, fmt.Errorf("runtime config validation failed: %w", err)) + } + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + + return nil +} + +func fromMetadataLegacy(m MetadataLegacy) *Metadata { + pluginType := "cli/v1" + + if len(m.Downloaders) > 0 { + pluginType = "getter/v1" + } + + return &Metadata{ + APIVersion: "legacy", + Name: m.Name, + Version: m.Version, + Type: pluginType, + Runtime: "subprocess", + Config: buildLegacyConfig(m, pluginType), + RuntimeConfig: buildLegacyRuntimeConfig(m), + } +} + +func buildLegacyConfig(m MetadataLegacy, pluginType string) Config { + switch pluginType { + case "getter/v1": + var protocols []string + for _, d := range m.Downloaders { + protocols = append(protocols, d.Protocols...) + } + return &schema.ConfigGetterV1{ + Protocols: protocols, + } + case "cli/v1": + return &schema.ConfigCLIV1{ + Usage: "", // Legacy plugins don't have Usage field for command syntax + ShortHelp: m.Usage, // Map legacy usage to shortHelp + LongHelp: m.Description, // Map legacy description to longHelp + IgnoreFlags: m.IgnoreFlags, + } + default: + return nil + } +} + +func buildLegacyRuntimeConfig(m MetadataLegacy) RuntimeConfig { + var protocolCommands []SubprocessProtocolCommand + if len(m.Downloaders) > 0 { + protocolCommands = + make([]SubprocessProtocolCommand, 0, len(m.Downloaders)) + for _, d := range m.Downloaders { + protocolCommands = append(protocolCommands, SubprocessProtocolCommand{ + Protocols: d.Protocols, + PlatformCommand: []PlatformCommand{{Command: d.Command}}, + }) + } + } + + platformCommand := m.PlatformCommand + if len(platformCommand) == 0 && len(m.Command) > 0 { + platformCommand = []PlatformCommand{{Command: m.Command}} + } + + platformHooks := m.PlatformHooks + expandHookArgs := true + if len(platformHooks) == 0 && len(m.Hooks) > 0 { + platformHooks = make(PlatformHooks, len(m.Hooks)) + for hookName, hookCommand := range m.Hooks { + platformHooks[hookName] = []PlatformCommand{{Command: "sh", Args: []string{"-c", hookCommand}}} + expandHookArgs = false + } + } + return &RuntimeConfigSubprocess{ + PlatformCommand: platformCommand, + PlatformHooks: platformHooks, + ProtocolCommands: protocolCommands, + expandHookArgs: expandHookArgs, + } +} + +func fromMetadataV1(mv1 MetadataV1) (*Metadata, error) { + config, err := unmarshalConfig(mv1.Type, mv1.Config) + if err != nil { + return nil, err + } + + runtimeConfig, err := convertMetadataRuntimeConfig(mv1.Runtime, mv1.RuntimeConfig) + if err != nil { + return nil, err + } + + return &Metadata{ + APIVersion: mv1.APIVersion, + Name: mv1.Name, + Type: mv1.Type, + Runtime: mv1.Runtime, + Version: mv1.Version, + SourceURL: mv1.SourceURL, + Config: config, + RuntimeConfig: runtimeConfig, + }, nil +} + +func convertMetadataRuntimeConfig(runtimeType string, runtimeConfigRaw map[string]any) (RuntimeConfig, error) { + var runtimeConfig RuntimeConfig + var err error + + switch runtimeType { + case "subprocess": + runtimeConfig, err = remarshalRuntimeConfig[*RuntimeConfigSubprocess](runtimeConfigRaw) + case "extism/v1": + runtimeConfig, err = remarshalRuntimeConfig[*RuntimeConfigExtismV1](runtimeConfigRaw) + default: + return nil, fmt.Errorf("unsupported plugin runtime type: %q", runtimeType) + } + + if err != nil { + return nil, fmt.Errorf("failed to unmarshal runtimeConfig for %s runtime: %w", runtimeType, err) + } + return runtimeConfig, nil +} diff --git a/vendor/helm.sh/helm/v4/internal/plugin/metadata_legacy.go b/vendor/helm.sh/helm/v4/internal/plugin/metadata_legacy.go new file mode 100644 index 00000000000..212dccbd1c6 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/metadata_legacy.go @@ -0,0 +1,118 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +import ( + "fmt" + "strings" + "unicode" +) + +// Downloaders represents the plugins capability if it can retrieve +// charts from special sources +type Downloaders struct { + // Protocols are the list of schemes from the charts URL. + Protocols []string `yaml:"protocols"` + // Command is the executable path with which the plugin performs + // the actual download for the corresponding Protocols + Command string `yaml:"command"` +} + +// MetadataLegacy is the legacy plugin.yaml format +type MetadataLegacy struct { + // Name is the name of the plugin + Name string `yaml:"name"` + + // Version is a SemVer 2 version of the plugin. + Version string `yaml:"version"` + + // Usage is the single-line usage text shown in help + Usage string `yaml:"usage"` + + // Description is a long description shown in places like `helm help` + Description string `yaml:"description"` + + // PlatformCommand is the plugin command, with a platform selector and support for args. + PlatformCommand []PlatformCommand `yaml:"platformCommand"` + + // Command is the plugin command, as a single string. + // DEPRECATED: Use PlatformCommand instead. Removed in subprocess/v1 plugins. + Command string `yaml:"command"` + + // IgnoreFlags ignores any flags passed in from Helm + IgnoreFlags bool `yaml:"ignoreFlags"` + + // PlatformHooks are commands that will run on plugin events, with a platform selector and support for args. + PlatformHooks PlatformHooks `yaml:"platformHooks"` + + // Hooks are commands that will run on plugin events, as a single string. + // DEPRECATED: Use PlatformHooks instead. Removed in subprocess/v1 plugins. + Hooks Hooks `yaml:"hooks"` + + // Downloaders field is used if the plugin supply downloader mechanism + // for special protocols. + Downloaders []Downloaders `yaml:"downloaders"` +} + +func (m *MetadataLegacy) Validate() error { + if !validPluginName.MatchString(m.Name) { + return fmt.Errorf("invalid plugin name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name) + } + + if m.Version != "" && !isValidSemver(m.Version) { + return fmt.Errorf("invalid plugin version %q: must be valid semver", m.Version) + } + + m.Usage = sanitizeString(m.Usage) + + if len(m.PlatformCommand) > 0 && len(m.Command) > 0 { + return fmt.Errorf("both platformCommand and command are set") + } + + if len(m.PlatformHooks) > 0 && len(m.Hooks) > 0 { + return fmt.Errorf("both platformHooks and hooks are set") + } + + // Validate downloader plugins + for i, downloader := range m.Downloaders { + if downloader.Command == "" { + return fmt.Errorf("downloader %d has empty command", i) + } + if len(downloader.Protocols) == 0 { + return fmt.Errorf("downloader %d has no protocols", i) + } + for j, protocol := range downloader.Protocols { + if protocol == "" { + return fmt.Errorf("downloader %d has empty protocol at index %d", i, j) + } + } + } + + return nil +} + +// sanitizeString normalize spaces and removes non-printable characters. +func sanitizeString(str string) string { + return strings.Map(func(r rune) rune { + if unicode.IsSpace(r) { + return ' ' + } + if unicode.IsPrint(r) { + return r + } + return -1 + }, str) +} diff --git a/vendor/helm.sh/helm/v4/internal/plugin/metadata_v1.go b/vendor/helm.sh/helm/v4/internal/plugin/metadata_v1.go new file mode 100644 index 00000000000..1eb0ae61004 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/metadata_v1.go @@ -0,0 +1,74 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +import ( + "fmt" +) + +// MetadataV1 is the APIVersion V1 plugin.yaml format +type MetadataV1 struct { + // APIVersion specifies the plugin API version + APIVersion string `yaml:"apiVersion"` + + // Name is the name of the plugin + Name string `yaml:"name"` + + // Type of plugin (eg, cli/v1, getter/v1, postrenderer/v1) + Type string `yaml:"type"` + + // Runtime specifies the runtime type (subprocess, wasm) + Runtime string `yaml:"runtime"` + + // Version is a SemVer 2 version of the plugin. + Version string `yaml:"version"` + + // SourceURL is the URL where this plugin can be found + SourceURL string `yaml:"sourceURL,omitempty"` + + // Config contains the type-specific configuration for this plugin + Config map[string]any `yaml:"config"` + + // RuntimeConfig contains the runtime-specific configuration + RuntimeConfig map[string]any `yaml:"runtimeConfig"` +} + +func (m *MetadataV1) Validate() error { + if !validPluginName.MatchString(m.Name) { + return fmt.Errorf("invalid plugin `name`") + } + + if m.Version == "" { + return fmt.Errorf("plugin `version` is required") + } + if !isValidSemver(m.Version) { + return fmt.Errorf("invalid plugin `version` %q: must be valid semver", m.Version) + } + + if m.APIVersion != "v1" { + return fmt.Errorf("invalid `apiVersion`: %q", m.APIVersion) + } + + if m.Type == "" { + return fmt.Errorf("`type` missing") + } + + if m.Runtime == "" { + return fmt.Errorf("`runtime` missing") + } + + return nil +} diff --git a/vendor/helm.sh/helm/v4/internal/plugin/plugin.go b/vendor/helm.sh/helm/v4/internal/plugin/plugin.go new file mode 100644 index 00000000000..132b1739e0b --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/plugin.go @@ -0,0 +1,81 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin // import "helm.sh/helm/v4/internal/plugin" + +import ( + "context" + "io" + "regexp" +) + +const PluginFileName = "plugin.yaml" + +// Plugin defines a plugin instance. The client (Helm codebase) facing type that can be used to introspect and invoke a plugin +type Plugin interface { + // Dir return the plugin directory (as an absolute path) on the filesystem + Dir() string + + // Metadata describes the plugin's type, version, etc. + // (This metadata type is the converted and plugin version independented in-memory representation of the plugin.yaml file) + Metadata() Metadata + + // Invoke takes the given input, and dispatches the contents to plugin instance + // The input is expected to be a JSON-serializable object, which the plugin will interpret according to its type + // The plugin is expected to return a JSON-serializable object, which the invoker + // will interpret according to the plugin's type + // + // Invoke can be thought of as a request/response mechanism. Similar to e.g. http.RoundTripper + // + // If plugin's execution fails with a non-zero "return code" (this is plugin runtime implementation specific) + // an InvokeExecError is returned + Invoke(ctx context.Context, input *Input) (*Output, error) +} + +// PluginHook allows plugins to implement hooks that are invoked on plugin management events (install, upgrade, etc) +type PluginHook interface { //nolint:revive + InvokeHook(event string) error +} + +// Input defines the input message and parameters to be passed to the plugin +type Input struct { + // Message represents the type-elided value to be passed to the plugin. + // The plugin is expected to interpret the message according to its type + // The message object must be JSON-serializable + Message any + + // Optional: Reader to be consumed plugin's "stdin" + Stdin io.Reader + + // Optional: Writers to consume the plugin's "stdout" and "stderr" + Stdout, Stderr io.Writer + + // Optional: Env represents the environment as a list of "key=value" strings + // see os.Environ + Env []string +} + +// Output defines the output message and parameters the passed from the plugin +type Output struct { + // Message represents the type-elided value returned from the plugin + // The invoker is expected to interpret the message according to the plugin's type + // The message object must be JSON-serializable + Message any +} + +// validPluginName is a regular expression that validates plugin names. +// +// Plugin names can only contain the ASCII characters a-z, A-Z, 0-9, ​_​ and ​-. +var validPluginName = regexp.MustCompile("^[A-Za-z0-9_-]+$") diff --git a/vendor/helm.sh/helm/v4/internal/plugin/plugin_type_registry.go b/vendor/helm.sh/helm/v4/internal/plugin/plugin_type_registry.go new file mode 100644 index 00000000000..5138422bd80 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/plugin_type_registry.go @@ -0,0 +1,106 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +This file contains a "registry" of supported plugin types. + +It enables "dyanmic" operations on the go type associated with a given plugin type (see: `helm.sh/helm/v4/internal/plugin/schema` package) + +Examples: + +``` + + // Create a new instance of the output message type for a given plugin type: + + pluginType := "cli/v1" // for example + ptm, ok := pluginTypesIndex[pluginType] + if !ok { + return fmt.Errorf("unknown plugin type %q", pluginType) + } + + outputMessageType := reflect.Zero(ptm.outputType).Interface() + +``` + +``` +// Create a new instance of the config type for a given plugin type + + pluginType := "cli/v1" // for example + ptm, ok := pluginTypesIndex[pluginType] + if !ok { + return nil + } + + config := reflect.New(ptm.configType).Interface().(Config) // `config` is variable of type `Config`, with + + // validate + err := config.Validate() + if err != nil { // handle error } + + // assert to concrete type if needed + cliConfig := config.(*schema.ConfigCLIV1) + +``` +*/ + +package plugin + +import ( + "reflect" + + "helm.sh/helm/v4/internal/plugin/schema" +) + +type pluginTypeMeta struct { + pluginType string + inputType reflect.Type + outputType reflect.Type + configType reflect.Type +} + +var pluginTypes = []pluginTypeMeta{ + { + pluginType: "test/v1", + inputType: reflect.TypeFor[schema.InputMessageTestV1](), + outputType: reflect.TypeFor[schema.OutputMessageTestV1](), + configType: reflect.TypeFor[schema.ConfigTestV1](), + }, + { + pluginType: "cli/v1", + inputType: reflect.TypeFor[schema.InputMessageCLIV1](), + outputType: reflect.TypeFor[schema.OutputMessageCLIV1](), + configType: reflect.TypeFor[schema.ConfigCLIV1](), + }, + { + pluginType: "getter/v1", + inputType: reflect.TypeFor[schema.InputMessageGetterV1](), + outputType: reflect.TypeFor[schema.OutputMessageGetterV1](), + configType: reflect.TypeFor[schema.ConfigGetterV1](), + }, + { + pluginType: "postrenderer/v1", + inputType: reflect.TypeFor[schema.InputMessagePostRendererV1](), + outputType: reflect.TypeFor[schema.OutputMessagePostRendererV1](), + configType: reflect.TypeFor[schema.ConfigPostRendererV1](), + }, +} + +var pluginTypesIndex = func() map[string]*pluginTypeMeta { + result := make(map[string]*pluginTypeMeta, len(pluginTypes)) + for _, m := range pluginTypes { + result[m.pluginType] = &m + } + return result +}() diff --git a/vendor/helm.sh/helm/v4/internal/plugin/runtime.go b/vendor/helm.sh/helm/v4/internal/plugin/runtime.go new file mode 100644 index 00000000000..7d39a9a4350 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/runtime.go @@ -0,0 +1,86 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +import ( + "fmt" + "strings" + + "go.yaml.in/yaml/v3" +) + +// Runtime represents a plugin runtime (subprocess, extism, etc) ie. how a plugin should be executed +// Runtime is responsible for instantiating plugins that implement the runtime +// TODO: could call this something more like "PluginRuntimeCreator"? +type Runtime interface { + // CreatePlugin creates a plugin instance from the given metadata + CreatePlugin(pluginDir string, metadata *Metadata) (Plugin, error) + + // TODO: move config unmarshalling to the runtime? + // UnmarshalConfig(runtimeConfigRaw map[string]any) (RuntimeConfig, error) +} + +// RuntimeConfig represents the assertable type for a plugin's runtime configuration. +// It is expected to type assert (cast) the a RuntimeConfig to its expected type +type RuntimeConfig interface { + Validate() error +} + +func remarshalRuntimeConfig[T RuntimeConfig](runtimeData map[string]any) (RuntimeConfig, error) { + data, err := yaml.Marshal(runtimeData) + if err != nil { + return nil, err + } + + var config T + if err := yaml.Unmarshal(data, &config); err != nil { + return nil, err + } + + return config, nil +} + +// ParseEnv takes a list of "KEY=value" environment variable strings +// and transforms the result into a map[KEY]=value +// +// - empty input strings are ignored +// - input strings with no value are stored as empty strings +// - duplicate keys overwrite earlier values +func ParseEnv(env []string) map[string]string { + result := make(map[string]string, len(env)) + for _, envVar := range env { + parts := strings.SplitN(envVar, "=", 2) + if len(parts) > 0 && parts[0] != "" { + key := parts[0] + var value string + if len(parts) > 1 { + value = parts[1] + } + result[key] = value + } + } + return result +} + +// FormatEnv takes a map[KEY]=value and transforms it into +// a list of "KEY=value" environment variable strings +func FormatEnv(env map[string]string) []string { + result := make([]string, 0, len(env)) + for key, value := range env { + result = append(result, fmt.Sprintf("%s=%s", key, value)) + } + return result +} diff --git a/vendor/helm.sh/helm/v4/internal/plugin/runtime_extismv1.go b/vendor/helm.sh/helm/v4/internal/plugin/runtime_extismv1.go new file mode 100644 index 00000000000..cd9a0253518 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/runtime_extismv1.go @@ -0,0 +1,292 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +import ( + "context" + "encoding/json" + "fmt" + "log/slog" + "os" + "path/filepath" + "reflect" + + extism "github.com/extism/go-sdk" + "github.com/tetratelabs/wazero" +) + +const ExtismV1WasmBinaryFilename = "plugin.wasm" + +// RuntimeConfigExtismV1Memory exposes the Wasm/Extism memory options for the plugin +type RuntimeConfigExtismV1Memory struct { + // The max amount of pages the plugin can allocate + // One page is 64Kib. e.g. 16 pages would require 1MiB. + // Default is 4 pages (256KiB) + MaxPages uint32 `yaml:"maxPages,omitempty"` + + // The max size of an Extism HTTP response in bytes + // Default is 4096 bytes (4KiB) + MaxHTTPResponseBytes int64 `yaml:"maxHttpResponseBytes,omitempty"` + + // The max size of all Extism vars in bytes + // Default is 4096 bytes (4KiB) + MaxVarBytes int64 `yaml:"maxVarBytes,omitempty"` +} + +// RuntimeConfigExtismV1FileSystem exposes filesystem options for the configuration +// TODO: should Helm expose AllowedPaths? +type RuntimeConfigExtismV1FileSystem struct { + // If specified, a temporary directory will be created and mapped to /tmp in the plugin's filesystem. + // Data written to the directory will be visible on the host filesystem. + // The directory will be removed when the plugin invocation completes. + CreateTempDir bool `yaml:"createTempDir,omitempty"` +} + +// RuntimeConfigExtismV1 defines the user-configurable options the plugin's Extism runtime +// The format loosely follows the Extism Manifest format: https://extism.org/docs/concepts/manifest/ +type RuntimeConfigExtismV1 struct { + // Describes the limits on the memory the plugin may be allocated. + Memory RuntimeConfigExtismV1Memory `yaml:"memory"` + + // The "config" key is a free-form map that can be passed to the plugin. + // The plugin must interpret arbitrary data this map may contain + Config map[string]string `yaml:"config,omitempty"` + + // An optional set of hosts this plugin can communicate with. + // This only has an effect if the plugin makes HTTP requests. + // If not specified, then no hosts are allowed. + AllowedHosts []string `yaml:"allowedHosts,omitempty"` + + FileSystem RuntimeConfigExtismV1FileSystem `yaml:"fileSystem,omitempty"` + + // The timeout in milliseconds for the plugin to execute + Timeout uint64 `yaml:"timeout,omitempty"` + + // HostFunction names exposed in Helm the plugin may access + // see: https://extism.org/docs/concepts/host-functions/ + HostFunctions []string `yaml:"hostFunctions,omitempty"` + + // The name of entry function name to call in the plugin + // Defaults to "helm_plugin_main". + EntryFuncName string `yaml:"entryFuncName,omitempty"` +} + +var _ RuntimeConfig = (*RuntimeConfigExtismV1)(nil) + +func (r *RuntimeConfigExtismV1) Validate() error { + // TODO + return nil +} + +type RuntimeExtismV1 struct { + HostFunctions map[string]extism.HostFunction + CompilationCache wazero.CompilationCache +} + +var _ Runtime = (*RuntimeExtismV1)(nil) + +func (r *RuntimeExtismV1) CreatePlugin(pluginDir string, metadata *Metadata) (Plugin, error) { + + rc, ok := metadata.RuntimeConfig.(*RuntimeConfigExtismV1) + if !ok { + return nil, fmt.Errorf("invalid extism/v1 plugin runtime config type: %T", metadata.RuntimeConfig) + } + + wasmFile := filepath.Join(pluginDir, ExtismV1WasmBinaryFilename) + if _, err := os.Stat(wasmFile); err != nil { + if os.IsNotExist(err) { + return nil, fmt.Errorf("wasm binary missing for extism/v1 plugin: %q", wasmFile) + } + return nil, fmt.Errorf("failed to stat extism/v1 plugin wasm binary %q: %w", wasmFile, err) + } + + return &ExtismV1PluginRuntime{ + metadata: *metadata, + dir: pluginDir, + rc: rc, + r: r, + }, nil +} + +type ExtismV1PluginRuntime struct { + metadata Metadata + dir string + rc *RuntimeConfigExtismV1 + r *RuntimeExtismV1 +} + +var _ Plugin = (*ExtismV1PluginRuntime)(nil) + +func (p *ExtismV1PluginRuntime) Metadata() Metadata { + return p.metadata +} + +func (p *ExtismV1PluginRuntime) Dir() string { + return p.dir +} + +func (p *ExtismV1PluginRuntime) Invoke(ctx context.Context, input *Input) (*Output, error) { + + var tmpDir string + if p.rc.FileSystem.CreateTempDir { + tmpDirInner, err := os.MkdirTemp(os.TempDir(), "helm-plugin-*") + slog.Debug("created plugin temp dir", slog.String("dir", tmpDirInner), slog.String("plugin", p.metadata.Name)) + if err != nil { + return nil, fmt.Errorf("failed to create temp dir for extism compilation cache: %w", err) + } + defer func() { + if err := os.RemoveAll(tmpDir); err != nil { + slog.Warn("failed to remove plugin temp dir", slog.String("dir", tmpDir), slog.String("plugin", p.metadata.Name), slog.String("error", err.Error())) + } + }() + + tmpDir = tmpDirInner + } + + manifest, err := buildManifest(p.dir, tmpDir, p.rc) + if err != nil { + return nil, err + } + + config := buildPluginConfig(input, p.r) + + hostFunctions, err := buildHostFunctions(p.r.HostFunctions, p.rc) + if err != nil { + return nil, err + } + + pe, err := extism.NewPlugin(ctx, manifest, config, hostFunctions) + if err != nil { + return nil, fmt.Errorf("failed to create existing plugin: %w", err) + } + + pe.SetLogger(func(logLevel extism.LogLevel, s string) { + slog.Debug(s, slog.String("level", logLevel.String()), slog.String("plugin", p.metadata.Name)) + }) + + inputData, err := json.Marshal(input.Message) + if err != nil { + return nil, fmt.Errorf("failed to json marshal plugin input message: %T: %w", input.Message, err) + } + + slog.Debug("plugin input", slog.String("plugin", p.metadata.Name), slog.String("inputData", string(inputData))) + + entryFuncName := p.rc.EntryFuncName + if entryFuncName == "" { + entryFuncName = "helm_plugin_main" + } + + exitCode, outputData, err := pe.Call(entryFuncName, inputData) + if err != nil { + return nil, fmt.Errorf("plugin error: %w", err) + } + + if exitCode != 0 { + return nil, &InvokeExecError{ + ExitCode: int(exitCode), + } + } + + slog.Debug("plugin output", slog.String("plugin", p.metadata.Name), slog.Int("exitCode", int(exitCode)), slog.String("outputData", string(outputData))) + + outputMessage := reflect.New(pluginTypesIndex[p.metadata.Type].outputType) + if err := json.Unmarshal(outputData, outputMessage.Interface()); err != nil { + return nil, fmt.Errorf("failed to json marshal plugin output message: %T: %w", outputMessage, err) + } + + output := &Output{ + Message: outputMessage.Elem().Interface(), + } + + return output, nil +} + +func buildManifest(pluginDir string, tmpDir string, rc *RuntimeConfigExtismV1) (extism.Manifest, error) { + wasmFile := filepath.Join(pluginDir, ExtismV1WasmBinaryFilename) + + allowedHosts := rc.AllowedHosts + if allowedHosts == nil { + allowedHosts = []string{} + } + + allowedPaths := map[string]string{} + if tmpDir != "" { + allowedPaths[tmpDir] = "/tmp" + } + + return extism.Manifest{ + Wasm: []extism.Wasm{ + extism.WasmFile{ + Path: wasmFile, + Name: wasmFile, + }, + }, + Memory: &extism.ManifestMemory{ + MaxPages: rc.Memory.MaxPages, + MaxHttpResponseBytes: rc.Memory.MaxHTTPResponseBytes, + MaxVarBytes: rc.Memory.MaxVarBytes, + }, + Config: rc.Config, + AllowedHosts: allowedHosts, + AllowedPaths: allowedPaths, + Timeout: rc.Timeout, + }, nil +} + +func buildPluginConfig(input *Input, r *RuntimeExtismV1) extism.PluginConfig { + mc := wazero.NewModuleConfig(). + WithSysWalltime() + if input.Stdin != nil { + mc = mc.WithStdin(input.Stdin) + } + if input.Stdout != nil { + mc = mc.WithStdout(input.Stdout) + } + if input.Stderr != nil { + mc = mc.WithStderr(input.Stderr) + } + if len(input.Env) > 0 { + env := ParseEnv(input.Env) + for k, v := range env { + mc = mc.WithEnv(k, v) + } + } + + config := extism.PluginConfig{ + ModuleConfig: mc, + RuntimeConfig: wazero.NewRuntimeConfigCompiler(). + WithCloseOnContextDone(true). + WithCompilationCache(r.CompilationCache), + EnableWasi: true, + EnableHttpResponseHeaders: true, + } + + return config +} + +func buildHostFunctions(hostFunctions map[string]extism.HostFunction, rc *RuntimeConfigExtismV1) ([]extism.HostFunction, error) { + result := make([]extism.HostFunction, len(rc.HostFunctions)) + for _, fnName := range rc.HostFunctions { + fn, ok := hostFunctions[fnName] + if !ok { + return nil, fmt.Errorf("plugin requested host function %q not found", fnName) + } + + result = append(result, fn) + } + + return result, nil +} diff --git a/vendor/helm.sh/helm/v4/internal/plugin/runtime_subprocess.go b/vendor/helm.sh/helm/v4/internal/plugin/runtime_subprocess.go new file mode 100644 index 00000000000..c836c1c6dce --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/runtime_subprocess.go @@ -0,0 +1,278 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +import ( + "bytes" + "context" + "fmt" + "io" + "log/slog" + "maps" + "os" + "os/exec" + "slices" + + "helm.sh/helm/v4/internal/plugin/schema" +) + +// SubprocessProtocolCommand maps a given protocol to the getter command used to retrieve artifacts for that protocol +type SubprocessProtocolCommand struct { + // Protocols are the list of schemes from the charts URL. + Protocols []string `yaml:"protocols"` + // PlatformCommand is the platform based command which the plugin performs + // to download for the corresponding getter Protocols. + PlatformCommand []PlatformCommand `yaml:"platformCommand"` +} + +// RuntimeConfigSubprocess implements RuntimeConfig for RuntimeSubprocess +type RuntimeConfigSubprocess struct { + // PlatformCommand is a list containing a plugin command, with a platform selector and support for args. + PlatformCommand []PlatformCommand `yaml:"platformCommand"` + // PlatformHooks are commands that will run on plugin events, with a platform selector and support for args. + PlatformHooks PlatformHooks `yaml:"platformHooks"` + // ProtocolCommands allows the plugin to specify protocol specific commands + // + // Obsolete/deprecated: This is a compatibility hangover from the old plugin downloader mechanism, which was extended + // to support multiple protocols in a given plugin. The command supplied in PlatformCommand should implement protocol + // specific logic by inspecting the download URL + ProtocolCommands []SubprocessProtocolCommand `yaml:"protocolCommands,omitempty"` + + expandHookArgs bool +} + +var _ RuntimeConfig = (*RuntimeConfigSubprocess)(nil) + +func (r *RuntimeConfigSubprocess) GetType() string { return "subprocess" } + +func (r *RuntimeConfigSubprocess) Validate() error { + return nil +} + +type RuntimeSubprocess struct { + EnvVars map[string]string +} + +var _ Runtime = (*RuntimeSubprocess)(nil) + +// CreatePlugin implementation for Runtime +func (r *RuntimeSubprocess) CreatePlugin(pluginDir string, metadata *Metadata) (Plugin, error) { + return &SubprocessPluginRuntime{ + metadata: *metadata, + pluginDir: pluginDir, + RuntimeConfig: *(metadata.RuntimeConfig.(*RuntimeConfigSubprocess)), + EnvVars: maps.Clone(r.EnvVars), + }, nil +} + +// SubprocessPluginRuntime implements the Plugin interface for subprocess execution +type SubprocessPluginRuntime struct { + metadata Metadata + pluginDir string + RuntimeConfig RuntimeConfigSubprocess + EnvVars map[string]string +} + +var _ Plugin = (*SubprocessPluginRuntime)(nil) + +func (r *SubprocessPluginRuntime) Dir() string { + return r.pluginDir +} + +func (r *SubprocessPluginRuntime) Metadata() Metadata { + return r.metadata +} + +func (r *SubprocessPluginRuntime) Invoke(_ context.Context, input *Input) (*Output, error) { + switch input.Message.(type) { + case schema.InputMessageCLIV1: + return r.runCLI(input) + case schema.InputMessageGetterV1: + return r.runGetter(input) + case schema.InputMessagePostRendererV1: + return r.runPostrenderer(input) + default: + return nil, fmt.Errorf("unsupported subprocess plugin type %q", r.metadata.Type) + } +} + +// InvokeWithEnv executes a plugin command with custom environment and I/O streams +// This method allows execution with different command/args than the plugin's default +func (r *SubprocessPluginRuntime) InvokeWithEnv(main string, argv []string, env []string, stdin io.Reader, stdout, stderr io.Writer) error { + mainCmdExp := os.ExpandEnv(main) + cmd := exec.Command(mainCmdExp, argv...) + cmd.Env = slices.Clone(os.Environ()) + cmd.Env = append( + cmd.Env, + fmt.Sprintf("HELM_PLUGIN_NAME=%s", r.metadata.Name), + fmt.Sprintf("HELM_PLUGIN_DIR=%s", r.pluginDir)) + cmd.Env = append(cmd.Env, env...) + + cmd.Stdin = stdin + cmd.Stdout = stdout + cmd.Stderr = stderr + + if err := executeCmd(cmd, r.metadata.Name); err != nil { + return err + } + + return nil +} + +func (r *SubprocessPluginRuntime) InvokeHook(event string) error { + cmds := r.RuntimeConfig.PlatformHooks[event] + + if len(cmds) == 0 { + return nil + } + + env := ParseEnv(os.Environ()) + maps.Insert(env, maps.All(r.EnvVars)) + env["HELM_PLUGIN_NAME"] = r.metadata.Name + env["HELM_PLUGIN_DIR"] = r.pluginDir + + main, argv, err := PrepareCommands(cmds, r.RuntimeConfig.expandHookArgs, []string{}, env) + if err != nil { + return err + } + + cmd := exec.Command(main, argv...) + cmd.Env = FormatEnv(env) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + slog.Debug("executing plugin hook command", slog.String("pluginName", r.metadata.Name), slog.String("command", cmd.String())) + if err := cmd.Run(); err != nil { + if eerr, ok := err.(*exec.ExitError); ok { + os.Stderr.Write(eerr.Stderr) + return fmt.Errorf("plugin %s hook for %q exited with error", event, r.metadata.Name) + } + return err + } + return nil +} + +// TODO decide the best way to handle this code +// right now we implement status and error return in 3 slightly different ways in this file +// then replace the other three with a call to this func +func executeCmd(prog *exec.Cmd, pluginName string) error { + if err := prog.Run(); err != nil { + if eerr, ok := err.(*exec.ExitError); ok { + slog.Debug( + "plugin execution failed", + slog.String("pluginName", pluginName), + slog.String("error", err.Error()), + slog.Int("exitCode", eerr.ExitCode()), + slog.String("stderr", string(bytes.TrimSpace(eerr.Stderr)))) + return &InvokeExecError{ + Err: fmt.Errorf("plugin %q exited with error", pluginName), + ExitCode: eerr.ExitCode(), + } + } + + return err + } + + return nil +} + +func (r *SubprocessPluginRuntime) runCLI(input *Input) (*Output, error) { + if _, ok := input.Message.(schema.InputMessageCLIV1); !ok { + return nil, fmt.Errorf("plugin %q input message does not implement InputMessageCLIV1", r.metadata.Name) + } + + extraArgs := input.Message.(schema.InputMessageCLIV1).ExtraArgs + + cmds := r.RuntimeConfig.PlatformCommand + + env := ParseEnv(os.Environ()) + maps.Insert(env, maps.All(r.EnvVars)) + maps.Insert(env, maps.All(ParseEnv(input.Env))) + env["HELM_PLUGIN_NAME"] = r.metadata.Name + env["HELM_PLUGIN_DIR"] = r.pluginDir + + command, args, err := PrepareCommands(cmds, true, extraArgs, env) + if err != nil { + return nil, fmt.Errorf("failed to prepare plugin command: %w", err) + } + + cmd := exec.Command(command, args...) + cmd.Env = FormatEnv(env) + + cmd.Stdin = input.Stdin + cmd.Stdout = input.Stdout + cmd.Stderr = input.Stderr + + slog.Debug("executing plugin command", slog.String("pluginName", r.metadata.Name), slog.String("command", cmd.String())) + if err := executeCmd(cmd, r.metadata.Name); err != nil { + return nil, err + } + + return &Output{ + Message: schema.OutputMessageCLIV1{}, + }, nil +} + +func (r *SubprocessPluginRuntime) runPostrenderer(input *Input) (*Output, error) { + if _, ok := input.Message.(schema.InputMessagePostRendererV1); !ok { + return nil, fmt.Errorf("plugin %q input message does not implement InputMessagePostRendererV1", r.metadata.Name) + } + + env := ParseEnv(os.Environ()) + maps.Insert(env, maps.All(r.EnvVars)) + maps.Insert(env, maps.All(ParseEnv(input.Env))) + env["HELM_PLUGIN_NAME"] = r.metadata.Name + env["HELM_PLUGIN_DIR"] = r.pluginDir + + msg := input.Message.(schema.InputMessagePostRendererV1) + cmds := r.RuntimeConfig.PlatformCommand + command, args, err := PrepareCommands(cmds, true, msg.ExtraArgs, env) + if err != nil { + return nil, fmt.Errorf("failed to prepare plugin command: %w", err) + } + + cmd := exec.Command( + command, + args...) + + stdin, err := cmd.StdinPipe() + if err != nil { + return nil, err + } + + go func() { + defer stdin.Close() + io.Copy(stdin, msg.Manifests) + }() + + postRendered := &bytes.Buffer{} + stderr := &bytes.Buffer{} + + cmd.Env = FormatEnv(env) + cmd.Stdout = postRendered + cmd.Stderr = stderr + + slog.Debug("executing plugin command", slog.String("pluginName", r.metadata.Name), slog.String("command", cmd.String())) + if err := executeCmd(cmd, r.metadata.Name); err != nil { + return nil, err + } + + return &Output{ + Message: schema.OutputMessagePostRendererV1{ + Manifests: postRendered, + }, + }, nil +} diff --git a/vendor/helm.sh/helm/v4/internal/plugin/runtime_subprocess_getter.go b/vendor/helm.sh/helm/v4/internal/plugin/runtime_subprocess_getter.go new file mode 100644 index 00000000000..fa6f470a9fc --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/runtime_subprocess_getter.go @@ -0,0 +1,100 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +import ( + "bytes" + "fmt" + "log/slog" + "maps" + "os" + "os/exec" + "path/filepath" + "slices" + + "helm.sh/helm/v4/internal/plugin/schema" +) + +func getProtocolCommand(commands []SubprocessProtocolCommand, protocol string) *SubprocessProtocolCommand { + for _, c := range commands { + if slices.Contains(c.Protocols, protocol) { + return &c + } + } + + return nil +} + +// TODO can we replace a lot of this func with RuntimeSubprocess.invokeWithEnv? +func (r *SubprocessPluginRuntime) runGetter(input *Input) (*Output, error) { + msg, ok := (input.Message).(schema.InputMessageGetterV1) + if !ok { + return nil, fmt.Errorf("expected input type schema.InputMessageGetterV1, got %T", input) + } + + tmpDir, err := os.MkdirTemp(os.TempDir(), fmt.Sprintf("helm-plugin-%s-", r.metadata.Name)) + if err != nil { + return nil, fmt.Errorf("failed to create temporary directory: %w", err) + } + defer os.RemoveAll(tmpDir) + + d := getProtocolCommand(r.RuntimeConfig.ProtocolCommands, msg.Protocol) + if d == nil { + return nil, fmt.Errorf("no downloader found for protocol %q", msg.Protocol) + } + + env := ParseEnv(os.Environ()) + maps.Insert(env, maps.All(r.EnvVars)) + maps.Insert(env, maps.All(ParseEnv(input.Env))) + env["HELM_PLUGIN_NAME"] = r.metadata.Name + env["HELM_PLUGIN_DIR"] = r.pluginDir + env["HELM_PLUGIN_USERNAME"] = msg.Options.Username + env["HELM_PLUGIN_PASSWORD"] = msg.Options.Password + env["HELM_PLUGIN_PASS_CREDENTIALS_ALL"] = fmt.Sprintf("%t", msg.Options.PassCredentialsAll) + + command, args, err := PrepareCommands(d.PlatformCommand, false, []string{}, env) + if err != nil { + return nil, fmt.Errorf("failed to prepare commands for protocol %q: %w", msg.Protocol, err) + } + + args = append( + args, + msg.Options.CertFile, + msg.Options.KeyFile, + msg.Options.CAFile, + msg.Href) + + buf := bytes.Buffer{} // subprocess getters are expected to write content to stdout + + pluginCommand := filepath.Join(r.pluginDir, command) + cmd := exec.Command( + pluginCommand, + args...) + cmd.Env = FormatEnv(env) + cmd.Stdout = &buf + cmd.Stderr = os.Stderr + + slog.Debug("executing plugin command", slog.String("pluginName", r.metadata.Name), slog.String("command", cmd.String())) + if err := executeCmd(cmd, r.metadata.Name); err != nil { + return nil, err + } + + return &Output{ + Message: schema.OutputMessageGetterV1{ + Data: buf.Bytes(), + }, + }, nil +} diff --git a/vendor/helm.sh/helm/v4/internal/plugin/runtime_subprocess_hooks.go b/vendor/helm.sh/helm/v4/internal/plugin/runtime_subprocess_hooks.go new file mode 100644 index 00000000000..7b4ff5a38b5 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/runtime_subprocess_hooks.go @@ -0,0 +1,32 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin // import "helm.sh/helm/v4/internal/plugin" + +// Types of hooks +const ( + // Install is executed after the plugin is added. + Install = "install" + // Delete is executed after the plugin is removed. + Delete = "delete" + // Update is executed after the plugin is updated. + Update = "update" +) + +// PlatformHooks is a map of events to a command for a particular operating system and architecture. +type PlatformHooks map[string][]PlatformCommand + +// Hooks is a map of events to commands. +type Hooks map[string]string diff --git a/vendor/helm.sh/helm/v4/internal/plugin/schema/cli.go b/vendor/helm.sh/helm/v4/internal/plugin/schema/cli.go new file mode 100644 index 00000000000..2282580f5f7 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/schema/cli.go @@ -0,0 +1,45 @@ +/* + Copyright The Helm Authors. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package schema + +import ( + "bytes" +) + +type InputMessageCLIV1 struct { + ExtraArgs []string `json:"extraArgs"` +} + +type OutputMessageCLIV1 struct { + Data *bytes.Buffer `json:"data"` +} + +// ConfigCLIV1 represents the configuration for CLI plugins +type ConfigCLIV1 struct { + // Usage is the single-line usage text shown in help + // For recommended syntax, see [spf13/cobra.command.Command] Use field comment: + // https://pkg.go.dev/github.com/spf13/cobra#Command + Usage string `yaml:"usage"` + // ShortHelp is the short description shown in the 'helm help' output + ShortHelp string `yaml:"shortHelp"` + // LongHelp is the long message shown in the 'helm help ' output + LongHelp string `yaml:"longHelp"` + // IgnoreFlags ignores any flags passed in from Helm + IgnoreFlags bool `yaml:"ignoreFlags"` +} + +func (c *ConfigCLIV1) Validate() error { + // Config validation for CLI plugins + return nil +} diff --git a/vendor/helm.sh/helm/v4/internal/plugin/schema/doc.go b/vendor/helm.sh/helm/v4/internal/plugin/schema/doc.go new file mode 100644 index 00000000000..4b3fe5d4913 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/schema/doc.go @@ -0,0 +1,18 @@ +/* + Copyright The Helm Authors. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* + + */ + +package schema diff --git a/vendor/helm.sh/helm/v4/internal/plugin/schema/getter.go b/vendor/helm.sh/helm/v4/internal/plugin/schema/getter.go new file mode 100644 index 00000000000..2c5e81df171 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/schema/getter.go @@ -0,0 +1,66 @@ +/* + Copyright The Helm Authors. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package schema + +import ( + "fmt" + "time" +) + +// TODO: can we generate these plugin input/output messages? + +type GetterOptionsV1 struct { + URL string + CertFile string + KeyFile string + CAFile string + UNTar bool + InsecureSkipVerifyTLS bool + PlainHTTP bool + AcceptHeader string + Username string + Password string + PassCredentialsAll bool + UserAgent string + Version string + Timeout time.Duration +} + +type InputMessageGetterV1 struct { + Href string `json:"href"` + Protocol string `json:"protocol"` + Options GetterOptionsV1 `json:"options"` +} + +type OutputMessageGetterV1 struct { + Data []byte `json:"data"` +} + +// ConfigGetterV1 represents the configuration for download plugins +type ConfigGetterV1 struct { + // Protocols are the list of URL schemes supported by this downloader + Protocols []string `yaml:"protocols"` +} + +func (c *ConfigGetterV1) Validate() error { + if len(c.Protocols) == 0 { + return fmt.Errorf("getter has no protocols") + } + for i, protocol := range c.Protocols { + if protocol == "" { + return fmt.Errorf("getter has empty protocol at index %d", i) + } + } + return nil +} diff --git a/vendor/helm.sh/helm/v4/internal/plugin/schema/postrenderer.go b/vendor/helm.sh/helm/v4/internal/plugin/schema/postrenderer.go new file mode 100644 index 00000000000..ef51a8a6192 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/schema/postrenderer.go @@ -0,0 +1,38 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package schema + +import ( + "bytes" +) + +// InputMessagePostRendererV1 implements Input.Message +type InputMessagePostRendererV1 struct { + Manifests *bytes.Buffer `json:"manifests"` + // from CLI --post-renderer-args + ExtraArgs []string `json:"extraArgs"` +} + +type OutputMessagePostRendererV1 struct { + Manifests *bytes.Buffer `json:"manifests"` +} + +type ConfigPostRendererV1 struct{} + +func (c *ConfigPostRendererV1) Validate() error { + return nil +} diff --git a/vendor/helm.sh/helm/v4/internal/plugin/schema/test.go b/vendor/helm.sh/helm/v4/internal/plugin/schema/test.go new file mode 100644 index 00000000000..97efa0fdede --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/schema/test.go @@ -0,0 +1,28 @@ +/* + Copyright The Helm Authors. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package schema + +type InputMessageTestV1 struct { + Name string +} + +type OutputMessageTestV1 struct { + Greeting string +} + +type ConfigTestV1 struct{} + +func (c *ConfigTestV1) Validate() error { + return nil +} diff --git a/vendor/helm.sh/helm/v4/internal/plugin/sign.go b/vendor/helm.sh/helm/v4/internal/plugin/sign.go new file mode 100644 index 00000000000..6ddf113a2e3 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/sign.go @@ -0,0 +1,156 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "errors" + "fmt" + "io" + "os" + "path/filepath" + + "sigs.k8s.io/yaml" + + "helm.sh/helm/v4/pkg/provenance" +) + +// SignPlugin signs a plugin using the SHA256 hash of the tarball data. +// +// This is used when packaging and signing a plugin from tarball data. +// It creates a signature that includes the tarball hash and plugin metadata, +// allowing verification of the original tarball later. +func SignPlugin(tarballData []byte, filename string, signer *provenance.Signatory) (string, error) { + // Extract plugin metadata from tarball data + pluginMeta, err := ExtractTgzPluginMetadata(bytes.NewReader(tarballData)) + if err != nil { + return "", fmt.Errorf("failed to extract plugin metadata: %w", err) + } + + // Marshal plugin metadata to YAML bytes + metadataBytes, err := yaml.Marshal(pluginMeta) + if err != nil { + return "", fmt.Errorf("failed to marshal plugin metadata: %w", err) + } + + // Use the generic provenance signing function + return signer.ClearSign(tarballData, filename, metadataBytes) +} + +// ExtractTgzPluginMetadata extracts plugin metadata from a gzipped tarball reader +func ExtractTgzPluginMetadata(r io.Reader) (*Metadata, error) { + gzr, err := gzip.NewReader(r) + if err != nil { + return nil, err + } + defer gzr.Close() + + tr := tar.NewReader(gzr) + for { + header, err := tr.Next() + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return nil, err + } + + // Look for plugin.yaml file + if filepath.Base(header.Name) == "plugin.yaml" { + data, err := io.ReadAll(tr) + if err != nil { + return nil, err + } + + // Parse the plugin metadata + metadata, err := loadMetadata(data) + if err != nil { + return nil, err + } + + return metadata, nil + } + } + + return nil, errors.New("plugin.yaml not found in tarball") +} + +// parsePluginMessageBlock parses a signed message block to extract plugin metadata and checksums +func parsePluginMessageBlock(data []byte) (*Metadata, *provenance.SumCollection, error) { + sc := &provenance.SumCollection{} + + // We only need the checksums for verification, not the full metadata + if err := provenance.ParseMessageBlock(data, nil, sc); err != nil { + return nil, sc, err + } + return nil, sc, nil +} + +// CreatePluginTarball creates a gzipped tarball from a plugin directory +func CreatePluginTarball(sourceDir, pluginName string, w io.Writer) error { + gzw := gzip.NewWriter(w) + defer gzw.Close() + + tw := tar.NewWriter(gzw) + defer tw.Close() + + // Use the plugin name as the base directory in the tarball + baseDir := pluginName + + // Walk the directory tree + return filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + // Create header + header, err := tar.FileInfoHeader(info, "") + if err != nil { + return err + } + + // Update the name to be relative to the source directory + relPath, err := filepath.Rel(sourceDir, path) + if err != nil { + return err + } + + // Include the base directory name in the tarball + header.Name = filepath.Join(baseDir, relPath) + + // Write header + if err := tw.WriteHeader(header); err != nil { + return err + } + + // If it's a regular file, write its content + if info.Mode().IsRegular() { + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + if _, err := io.Copy(tw, file); err != nil { + return err + } + } + + return nil + }) +} diff --git a/vendor/helm.sh/helm/v4/internal/plugin/signing_info.go b/vendor/helm.sh/helm/v4/internal/plugin/signing_info.go new file mode 100644 index 00000000000..61ee9cd15a2 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/signing_info.go @@ -0,0 +1,178 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +import ( + "crypto/sha256" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/ProtonMail/go-crypto/openpgp/clearsign" //nolint + + "helm.sh/helm/v4/pkg/helmpath" +) + +// SigningInfo contains information about a plugin's signing status +type SigningInfo struct { + // Status can be: + // - "local dev": Plugin is a symlink (development mode) + // - "unsigned": No provenance file found + // - "invalid provenance": Provenance file is malformed + // - "mismatched provenance": Provenance file does not match the installed tarball + // - "signed": Valid signature exists for the installed tarball + Status string + IsSigned bool // True if plugin has a valid signature (even if not verified against keyring) +} + +// GetPluginSigningInfo returns signing information for an installed plugin +func GetPluginSigningInfo(metadata Metadata) (*SigningInfo, error) { + pluginName := metadata.Name + pluginDir := helmpath.DataPath("plugins", pluginName) + + // Check if plugin directory exists + fi, err := os.Lstat(pluginDir) + if err != nil { + return nil, fmt.Errorf("plugin %s not found: %w", pluginName, err) + } + + // Check if it's a symlink (local development) + if fi.Mode()&os.ModeSymlink != 0 { + return &SigningInfo{ + Status: "local dev", + IsSigned: false, + }, nil + } + + // Find the exact tarball file for this plugin + pluginsDir := helmpath.DataPath("plugins") + tarballPath := filepath.Join(pluginsDir, fmt.Sprintf("%s-%s.tgz", metadata.Name, metadata.Version)) + if _, err := os.Stat(tarballPath); err != nil { + return &SigningInfo{ + Status: "unsigned", + IsSigned: false, + }, nil + } + + // Check for .prov file associated with the tarball + provFile := tarballPath + ".prov" + provData, err := os.ReadFile(provFile) + if err != nil { + if os.IsNotExist(err) { + return &SigningInfo{ + Status: "unsigned", + IsSigned: false, + }, nil + } + return nil, fmt.Errorf("failed to read provenance file: %w", err) + } + + // Parse the provenance file to check validity + block, _ := clearsign.Decode(provData) + if block == nil { + return &SigningInfo{ + Status: "invalid provenance", + IsSigned: false, + }, nil + } + + // Check if provenance matches the actual tarball + blockContent := string(block.Plaintext) + if !validateProvenanceHash(blockContent, tarballPath) { + return &SigningInfo{ + Status: "mismatched provenance", + IsSigned: false, + }, nil + } + + // We have a provenance file that is valid for this plugin + // Without a keyring, we can't verify the signature, but we know: + // 1. A .prov file exists + // 2. It's a valid clearsigned document (cryptographically signed) + // 3. The provenance contains valid checksums + return &SigningInfo{ + Status: "signed", + IsSigned: true, + }, nil +} + +func validateProvenanceHash(blockContent string, tarballPath string) bool { + // Parse provenance to get the expected hash + _, sums, err := parsePluginMessageBlock([]byte(blockContent)) + if err != nil { + return false + } + + // Must have file checksums + if len(sums.Files) == 0 { + return false + } + + // Calculate actual hash of the tarball + actualHash, err := calculateFileHash(tarballPath) + if err != nil { + return false + } + + // Check if the actual hash matches the expected hash in the provenance + for filename, expectedHash := range sums.Files { + if strings.Contains(filename, filepath.Base(tarballPath)) && expectedHash == actualHash { + return true + } + } + + return false +} + +// calculateFileHash calculates the SHA256 hash of a file +func calculateFileHash(filePath string) (string, error) { + file, err := os.Open(filePath) + if err != nil { + return "", err + } + defer file.Close() + + hasher := sha256.New() + if _, err := io.Copy(hasher, file); err != nil { + return "", err + } + + return fmt.Sprintf("sha256:%x", hasher.Sum(nil)), nil +} + +// GetSigningInfoForPlugins returns signing info for multiple plugins +func GetSigningInfoForPlugins(plugins []Plugin) map[string]*SigningInfo { + result := make(map[string]*SigningInfo) + + for _, p := range plugins { + m := p.Metadata() + + info, err := GetPluginSigningInfo(m) + if err != nil { + // If there's an error, treat as unsigned + result[m.Name] = &SigningInfo{ + Status: "unknown", + IsSigned: false, + } + } else { + result[m.Name] = info + } + } + + return result +} diff --git a/vendor/helm.sh/helm/v4/internal/plugin/subprocess_commands.go b/vendor/helm.sh/helm/v4/internal/plugin/subprocess_commands.go new file mode 100644 index 00000000000..9a57ed89149 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/subprocess_commands.go @@ -0,0 +1,114 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +import ( + "fmt" + "os" + "runtime" + "strings" +) + +// PlatformCommand represents a command for a particular operating system and architecture +type PlatformCommand struct { + OperatingSystem string `yaml:"os"` + Architecture string `yaml:"arch"` + Command string `yaml:"command"` + Args []string `yaml:"args"` +} + +// Returns command and args strings based on the following rules in priority order: +// - From the PlatformCommand where OS and Arch match the current platform +// - From the PlatformCommand where OS matches the current platform and Arch is empty/unspecified +// - From the PlatformCommand where OS is empty/unspecified and Arch matches the current platform +// - From the PlatformCommand where OS and Arch are both empty/unspecified +// - Return nil, nil +func getPlatformCommand(cmds []PlatformCommand) ([]string, []string) { + var command, args []string + found := false + foundOs := false + + eq := strings.EqualFold + for _, c := range cmds { + if eq(c.OperatingSystem, runtime.GOOS) && eq(c.Architecture, runtime.GOARCH) { + // Return early for an exact match + return strings.Split(c.Command, " "), c.Args + } + + if (len(c.OperatingSystem) > 0 && !eq(c.OperatingSystem, runtime.GOOS)) || len(c.Architecture) > 0 { + // Skip if OS is not empty and doesn't match or if arch is set as a set arch requires an OS match + continue + } + + if !foundOs && len(c.OperatingSystem) > 0 && eq(c.OperatingSystem, runtime.GOOS) { + // First OS match with empty arch, can only be overridden by a direct match + command = strings.Split(c.Command, " ") + args = c.Args + found = true + foundOs = true + } else if !found { + // First empty match, can be overridden by a direct match or an OS match + command = strings.Split(c.Command, " ") + args = c.Args + found = true + } + } + + return command, args +} + +// PrepareCommands takes a []Plugin.PlatformCommand +// and prepares the command and arguments for execution. +// +// It merges extraArgs into any arguments supplied in the plugin. It +// returns the main command and an args array. +// +// The result is suitable to pass to exec.Command. +func PrepareCommands(cmds []PlatformCommand, expandArgs bool, extraArgs []string, env map[string]string) (string, []string, error) { + cmdParts, args := getPlatformCommand(cmds) + if len(cmdParts) == 0 || cmdParts[0] == "" { + return "", nil, fmt.Errorf("no plugin command is applicable") + } + envMappingFunc := func(key string) string { + return env[key] + } + + main := os.Expand(cmdParts[0], envMappingFunc) + baseArgs := []string{} + if len(cmdParts) > 1 { + for _, cmdPart := range cmdParts[1:] { + if expandArgs { + baseArgs = append(baseArgs, os.Expand(cmdPart, envMappingFunc)) + } else { + baseArgs = append(baseArgs, cmdPart) + } + } + } + + for _, arg := range args { + if expandArgs { + baseArgs = append(baseArgs, os.Expand(arg, envMappingFunc)) + } else { + baseArgs = append(baseArgs, arg) + } + } + + if len(extraArgs) > 0 { + baseArgs = append(baseArgs, extraArgs...) + } + + return main, baseArgs, nil +} diff --git a/vendor/helm.sh/helm/v4/internal/plugin/verify.go b/vendor/helm.sh/helm/v4/internal/plugin/verify.go new file mode 100644 index 00000000000..760a56e67d2 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/plugin/verify.go @@ -0,0 +1,39 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +import ( + "path/filepath" + + "helm.sh/helm/v4/pkg/provenance" +) + +// VerifyPlugin verifies plugin data against a signature using data in memory. +func VerifyPlugin(archiveData, provData []byte, filename, keyring string) (*provenance.Verification, error) { + // Create signatory from keyring + sig, err := provenance.NewFromKeyring(keyring, "") + if err != nil { + return nil, err + } + + // Use the new VerifyData method directly + return sig.Verify(archiveData, provData, filename) +} + +// isTarball checks if a file has a tarball extension +func IsTarball(filename string) bool { + return filepath.Ext(filename) == ".gz" || filepath.Ext(filename) == ".tgz" +} diff --git a/vendor/helm.sh/helm/v4/internal/resolver/resolver.go b/vendor/helm.sh/helm/v4/internal/resolver/resolver.go new file mode 100644 index 00000000000..3efe94f10b8 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/resolver/resolver.go @@ -0,0 +1,263 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resolver + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" + "time" + + "github.com/Masterminds/semver/v3" + + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" + "helm.sh/helm/v4/pkg/helmpath" + "helm.sh/helm/v4/pkg/provenance" + "helm.sh/helm/v4/pkg/registry" + "helm.sh/helm/v4/pkg/repo/v1" +) + +// Resolver resolves dependencies from semantic version ranges to a particular version. +type Resolver struct { + chartpath string + cachepath string + registryClient *registry.Client +} + +// New creates a new resolver for a given chart, helm home and registry client. +func New(chartpath, cachepath string, registryClient *registry.Client) *Resolver { + return &Resolver{ + chartpath: chartpath, + cachepath: cachepath, + registryClient: registryClient, + } +} + +// Resolve resolves dependencies and returns a lock file with the resolution. +func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) { + + // Now we clone the dependencies, locking as we go. + locked := make([]*chart.Dependency, len(reqs)) + missing := []string{} + for i, d := range reqs { + constraint, err := semver.NewConstraint(d.Version) + if err != nil { + return nil, fmt.Errorf("dependency %q has an invalid version/constraint format: %w", d.Name, err) + } + + if d.Repository == "" { + // Local chart subfolder + if _, err := GetLocalPath(filepath.Join("charts", d.Name), r.chartpath); err != nil { + return nil, err + } + + locked[i] = &chart.Dependency{ + Name: d.Name, + Repository: "", + Version: d.Version, + } + continue + } + if strings.HasPrefix(d.Repository, "file://") { + chartpath, err := GetLocalPath(d.Repository, r.chartpath) + if err != nil { + return nil, err + } + + ch, err := loader.LoadDir(chartpath) + if err != nil { + return nil, err + } + + v, err := semver.NewVersion(ch.Metadata.Version) + if err != nil { + // Not a legit entry. + continue + } + + if !constraint.Check(v) { + missing = append(missing, fmt.Sprintf("%q (repository %q, version %q)", d.Name, d.Repository, d.Version)) + continue + } + + locked[i] = &chart.Dependency{ + Name: d.Name, + Repository: d.Repository, + Version: ch.Metadata.Version, + } + continue + } + + repoName := repoNames[d.Name] + // if the repository was not defined, but the dependency defines a repository url, bypass the cache + if repoName == "" && d.Repository != "" { + locked[i] = &chart.Dependency{ + Name: d.Name, + Repository: d.Repository, + Version: d.Version, + } + continue + } + + var vs repo.ChartVersions + var version string + var ok bool + found := true + if !registry.IsOCI(d.Repository) { + repoIndex, err := repo.LoadIndexFile(filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoName))) + if err != nil { + return nil, fmt.Errorf("no cached repository for %s found. (try 'helm repo update'): %w", repoName, err) + } + + vs, ok = repoIndex.Entries[d.Name] + if !ok { + return nil, fmt.Errorf("%s chart not found in repo %s", d.Name, d.Repository) + } + found = false + } else { + version = d.Version + + // Check to see if an explicit version has been provided + _, err := semver.NewVersion(version) + + // Use an explicit version, otherwise search for tags + if err == nil { + vs = []*repo.ChartVersion{{ + Metadata: &chart.Metadata{ + Version: version, + }, + }} + + } else { + // Retrieve list of tags for repository + ref := fmt.Sprintf("%s/%s", strings.TrimPrefix(d.Repository, fmt.Sprintf("%s://", registry.OCIScheme)), d.Name) + tags, err := r.registryClient.Tags(ref) + if err != nil { + return nil, fmt.Errorf("could not retrieve list of tags for repository %s: %w", d.Repository, err) + } + + vs = make(repo.ChartVersions, len(tags)) + for ti, t := range tags { + // Mock chart version objects + version := &repo.ChartVersion{ + Metadata: &chart.Metadata{ + Version: t, + }, + } + vs[ti] = version + } + } + } + + locked[i] = &chart.Dependency{ + Name: d.Name, + Repository: d.Repository, + Version: version, + } + // The versions are already sorted and hence the first one to satisfy the constraint is used + for _, ver := range vs { + v, err := semver.NewVersion(ver.Version) + // OCI does not need URLs + if err != nil || (!registry.IsOCI(d.Repository) && len(ver.URLs) == 0) { + // Not a legit entry. + continue + } + if constraint.Check(v) { + found = true + locked[i].Version = v.Original() + break + } + } + + if !found { + missing = append(missing, fmt.Sprintf("%q (repository %q, version %q)", d.Name, d.Repository, d.Version)) + } + } + if len(missing) > 0 { + return nil, fmt.Errorf("can't get a valid version for %d subchart(s): %s. Make sure a matching chart version exists in the repo, or change the version constraint in Chart.yaml", len(missing), strings.Join(missing, ", ")) + } + + digest, err := HashReq(reqs, locked) + if err != nil { + return nil, err + } + + return &chart.Lock{ + Generated: time.Now(), + Digest: digest, + Dependencies: locked, + }, nil +} + +// HashReq generates a hash of the dependencies. +// +// This should be used only to compare against another hash generated by this +// function. +func HashReq(req, lock []*chart.Dependency) (string, error) { + data, err := json.Marshal([2][]*chart.Dependency{req, lock}) + if err != nil { + return "", err + } + s, err := provenance.Digest(bytes.NewBuffer(data)) + return "sha256:" + s, err +} + +// HashV2Req generates a hash of requirements generated in Helm v2. +// +// This should be used only to compare against another hash generated by the +// Helm v2 hash function. It is to handle issue: +// https://github.com/helm/helm/issues/7233 +func HashV2Req(req []*chart.Dependency) (string, error) { + dep := make(map[string][]*chart.Dependency) + dep["dependencies"] = req + data, err := json.Marshal(dep) + if err != nil { + return "", err + } + s, err := provenance.Digest(bytes.NewBuffer(data)) + return "sha256:" + s, err +} + +// GetLocalPath generates absolute local path when use +// "file://" in repository of dependencies +func GetLocalPath(repo, chartpath string) (string, error) { + var depPath string + var err error + p := strings.TrimPrefix(repo, "file://") + + // root path is absolute + if strings.HasPrefix(p, "/") { + if depPath, err = filepath.Abs(p); err != nil { + return "", err + } + } else { + depPath = filepath.Join(chartpath, p) + } + + if _, err = os.Stat(depPath); errors.Is(err, fs.ErrNotExist) { + return "", fmt.Errorf("directory %s not found", depPath) + } else if err != nil { + return "", err + } + + return depPath, nil +} diff --git a/vendor/helm.sh/helm/v4/internal/statusreaders/job_status_reader.go b/vendor/helm.sh/helm/v4/internal/statusreaders/job_status_reader.go new file mode 100644 index 00000000000..3cd9ac7ac86 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/statusreaders/job_status_reader.go @@ -0,0 +1,121 @@ +/* +Copyright The Helm Authors. +This file was initially copied and modified from + https://github.com/fluxcd/kustomize-controller/blob/main/internal/statusreaders/job.go +Copyright 2022 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package statusreaders + +import ( + "context" + "fmt" + + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders" + "github.com/fluxcd/cli-utils/pkg/kstatus/status" + "github.com/fluxcd/cli-utils/pkg/object" +) + +type customJobStatusReader struct { + genericStatusReader engine.StatusReader +} + +func NewCustomJobStatusReader(mapper meta.RESTMapper) engine.StatusReader { + genericStatusReader := statusreaders.NewGenericStatusReader(mapper, jobConditions) + return &customJobStatusReader{ + genericStatusReader: genericStatusReader, + } +} + +func (j *customJobStatusReader) Supports(gk schema.GroupKind) bool { + return gk == batchv1.SchemeGroupVersion.WithKind("Job").GroupKind() +} + +func (j *customJobStatusReader) ReadStatus(ctx context.Context, reader engine.ClusterReader, resource object.ObjMetadata) (*event.ResourceStatus, error) { + return j.genericStatusReader.ReadStatus(ctx, reader, resource) +} + +func (j *customJobStatusReader) ReadStatusForObject(ctx context.Context, reader engine.ClusterReader, resource *unstructured.Unstructured) (*event.ResourceStatus, error) { + return j.genericStatusReader.ReadStatusForObject(ctx, reader, resource) +} + +// Ref: https://github.com/kubernetes-sigs/cli-utils/blob/v0.29.4/pkg/kstatus/status/core.go +// Modified to return Current status only when the Job has completed as opposed to when it's in progress. +func jobConditions(u *unstructured.Unstructured) (*status.Result, error) { + obj := u.UnstructuredContent() + + parallelism := status.GetIntField(obj, ".spec.parallelism", 1) + completions := status.GetIntField(obj, ".spec.completions", parallelism) + succeeded := status.GetIntField(obj, ".status.succeeded", 0) + failed := status.GetIntField(obj, ".status.failed", 0) + + // Conditions + // https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/job/utils.go#L24 + objc, err := status.GetObjectWithConditions(obj) + if err != nil { + return nil, err + } + for _, c := range objc.Status.Conditions { + switch c.Type { + case "Complete": + if c.Status == corev1.ConditionTrue { + message := fmt.Sprintf("Job Completed. succeeded: %d/%d", succeeded, completions) + return &status.Result{ + Status: status.CurrentStatus, + Message: message, + Conditions: []status.Condition{}, + }, nil + } + case "Failed": + message := fmt.Sprintf("Job Failed. failed: %d/%d", failed, completions) + if c.Status == corev1.ConditionTrue { + return &status.Result{ + Status: status.FailedStatus, + Message: message, + Conditions: []status.Condition{ + { + Type: status.ConditionStalled, + Status: corev1.ConditionTrue, + Reason: "JobFailed", + Message: message, + }, + }, + }, nil + } + } + } + + message := "Job in progress" + return &status.Result{ + Status: status.InProgressStatus, + Message: message, + Conditions: []status.Condition{ + { + Type: status.ConditionReconciling, + Status: corev1.ConditionTrue, + Reason: "JobInProgress", + Message: message, + }, + }, + }, nil +} diff --git a/vendor/helm.sh/helm/v4/internal/statusreaders/pod_status_reader.go b/vendor/helm.sh/helm/v4/internal/statusreaders/pod_status_reader.go new file mode 100644 index 00000000000..bf633c0ddba --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/statusreaders/pod_status_reader.go @@ -0,0 +1,104 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package statusreaders + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders" + "github.com/fluxcd/cli-utils/pkg/kstatus/status" + "github.com/fluxcd/cli-utils/pkg/object" +) + +type customPodStatusReader struct { + genericStatusReader engine.StatusReader +} + +func NewCustomPodStatusReader(mapper meta.RESTMapper) engine.StatusReader { + genericStatusReader := statusreaders.NewGenericStatusReader(mapper, podConditions) + return &customPodStatusReader{ + genericStatusReader: genericStatusReader, + } +} + +func (j *customPodStatusReader) Supports(gk schema.GroupKind) bool { + return gk == corev1.SchemeGroupVersion.WithKind("Pod").GroupKind() +} + +func (j *customPodStatusReader) ReadStatus(ctx context.Context, reader engine.ClusterReader, resource object.ObjMetadata) (*event.ResourceStatus, error) { + return j.genericStatusReader.ReadStatus(ctx, reader, resource) +} + +func (j *customPodStatusReader) ReadStatusForObject(ctx context.Context, reader engine.ClusterReader, resource *unstructured.Unstructured) (*event.ResourceStatus, error) { + return j.genericStatusReader.ReadStatusForObject(ctx, reader, resource) +} + +func podConditions(u *unstructured.Unstructured) (*status.Result, error) { + obj := u.UnstructuredContent() + phase := status.GetStringField(obj, ".status.phase", "") + switch corev1.PodPhase(phase) { + case corev1.PodSucceeded: + message := fmt.Sprintf("pod %s succeeded", u.GetName()) + return &status.Result{ + Status: status.CurrentStatus, + Message: message, + Conditions: []status.Condition{ + { + Type: status.ConditionStalled, + Status: corev1.ConditionTrue, + Message: message, + }, + }, + }, nil + case corev1.PodFailed: + message := fmt.Sprintf("pod %s failed", u.GetName()) + return &status.Result{ + Status: status.FailedStatus, + Message: message, + Conditions: []status.Condition{ + { + Type: status.ConditionStalled, + Status: corev1.ConditionTrue, + Reason: "PodFailed", + Message: message, + }, + }, + }, nil + default: + message := "Pod in progress" + return &status.Result{ + Status: status.InProgressStatus, + Message: message, + Conditions: []status.Condition{ + { + Type: status.ConditionReconciling, + Status: corev1.ConditionTrue, + Reason: "PodInProgress", + Message: message, + }, + }, + }, nil + } +} diff --git a/vendor/helm.sh/helm/v4/internal/sympath/walk.go b/vendor/helm.sh/helm/v4/internal/sympath/walk.go new file mode 100644 index 00000000000..812bb68ce34 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/sympath/walk.go @@ -0,0 +1,119 @@ +/* +Copyright (c) for portions of walk.go are held by The Go Authors, 2009 and are +provided under the BSD license. + +https://github.com/golang/go/blob/master/LICENSE + +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sympath + +import ( + "fmt" + "log/slog" + "os" + "path/filepath" + "sort" +) + +// Walk walks the file tree rooted at root, calling walkFn for each file or directory +// in the tree, including root. All errors that arise visiting files and directories +// are filtered by walkFn. The files are walked in lexical order, which makes the +// output deterministic but means that for very large directories Walk can be +// inefficient. Walk follows symbolic links. +func Walk(root string, walkFn filepath.WalkFunc) error { + info, err := os.Lstat(root) + if err != nil { + err = walkFn(root, nil, err) + } else { + err = symwalk(root, info, walkFn) + } + if err == filepath.SkipDir { + return nil + } + return err +} + +// readDirNames reads the directory named by dirname and returns +// a sorted list of directory entries. +func readDirNames(dirname string) ([]string, error) { + f, err := os.Open(dirname) + if err != nil { + return nil, err + } + names, err := f.Readdirnames(-1) + f.Close() + if err != nil { + return nil, err + } + sort.Strings(names) + return names, nil +} + +// symwalk recursively descends path, calling walkFn. +func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error { + // Recursively walk symlinked directories. + if IsSymlink(info) { + resolved, err := filepath.EvalSymlinks(path) + if err != nil { + return fmt.Errorf("error evaluating symlink %s: %w", path, err) + } + // This log message is to highlight a symlink that is being used within a chart, symlinks can be used for nefarious reasons. + slog.Info("found symbolic link in path. Contents of linked file included and used", "path", path, "resolved", resolved) + if info, err = os.Lstat(resolved); err != nil { + return err + } + if err := symwalk(path, info, walkFn); err != nil && err != filepath.SkipDir { + return err + } + return nil + } + + if err := walkFn(path, info, nil); err != nil { + return err + } + + if !info.IsDir() { + return nil + } + + names, err := readDirNames(path) + if err != nil { + return walkFn(path, info, err) + } + + for _, name := range names { + filename := filepath.Join(path, name) + fileInfo, err := os.Lstat(filename) + if err != nil { + if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir { + return err + } + } else { + err = symwalk(filename, fileInfo, walkFn) + if err != nil { + if (!fileInfo.IsDir() && !IsSymlink(fileInfo)) || err != filepath.SkipDir { + return err + } + } + } + } + return nil +} + +// IsSymlink is used to determine if the fileinfo is a symbolic link. +func IsSymlink(fi os.FileInfo) bool { + return fi.Mode()&os.ModeSymlink != 0 +} diff --git a/vendor/helm.sh/helm/v4/internal/third_party/dep/fs/fs.go b/vendor/helm.sh/helm/v4/internal/third_party/dep/fs/fs.go new file mode 100644 index 00000000000..6e2720f3b62 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/third_party/dep/fs/fs.go @@ -0,0 +1,377 @@ +/* +Copyright (c) for portions of fs.go are held by The Go Authors, 2016 and are provided under +the BSD license. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package fs + +import ( + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "runtime" + "syscall" +) + +// fs contains a copy of a few functions from dep tool code to avoid a dependency on golang/dep. +// This code is copied from https://github.com/golang/dep/blob/37d6c560cdf407be7b6cd035b23dba89df9275cf/internal/fs/fs.go +// No changes to the code were made other than removing some unused functions + +// RenameWithFallback attempts to rename a file or directory, but falls back to +// copying in the event of a cross-device link error. If the fallback copy +// succeeds, src is still removed, emulating normal rename behavior. +func RenameWithFallback(src, dst string) error { + _, err := os.Stat(src) + if err != nil { + return fmt.Errorf("cannot stat %s: %w", src, err) + } + + err = os.Rename(src, dst) + if err == nil { + return nil + } + + return renameFallback(err, src, dst) +} + +// renameByCopy attempts to rename a file or directory by copying it to the +// destination and then removing the src thus emulating the rename behavior. +func renameByCopy(src, dst string) error { + var cerr error + if dir, _ := IsDir(src); dir { + cerr = CopyDir(src, dst) + if cerr != nil { + cerr = fmt.Errorf("copying directory failed: %w", cerr) + } + } else { + cerr = CopyFile(src, dst) + if cerr != nil { + cerr = fmt.Errorf("copying file failed: %w", cerr) + } + } + + if cerr != nil { + return fmt.Errorf("rename fallback failed: cannot rename %s to %s: %w", src, dst, cerr) + } + + if err := os.RemoveAll(src); err != nil { + return fmt.Errorf("cannot delete %s: %w", src, err) + } + + return nil +} + +var ( + errSrcNotDir = errors.New("source is not a directory") + errDstExist = errors.New("destination already exists") +) + +// CopyDir recursively copies a directory tree, attempting to preserve permissions. +// Source directory must exist, destination directory must *not* exist. +func CopyDir(src, dst string) error { + src = filepath.Clean(src) + dst = filepath.Clean(dst) + + // We use os.Lstat() here to ensure we don't fall in a loop where a symlink + // actually links to a one of its parent directories. + fi, err := os.Lstat(src) + if err != nil { + return err + } + if !fi.IsDir() { + return errSrcNotDir + } + + _, err = os.Stat(dst) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return err + } + if err == nil { + return errDstExist + } + + if err = os.MkdirAll(dst, fi.Mode()); err != nil { + return fmt.Errorf("cannot mkdir %s: %w", dst, err) + } + + entries, err := os.ReadDir(src) + if err != nil { + return fmt.Errorf("cannot read directory %s: %w", dst, err) + } + + for _, entry := range entries { + srcPath := filepath.Join(src, entry.Name()) + dstPath := filepath.Join(dst, entry.Name()) + + if entry.IsDir() { + if err = CopyDir(srcPath, dstPath); err != nil { + return fmt.Errorf("copying directory failed: %w", err) + } + } else { + // This will include symlinks, which is what we want when + // copying things. + if err = CopyFile(srcPath, dstPath); err != nil { + return fmt.Errorf("copying file failed: %w", err) + } + } + } + + return nil +} + +// CopyFile copies the contents of the file named src to the file named +// by dst. The file will be created if it does not already exist. If the +// destination file exists, all its contents will be replaced by the contents +// of the source file. The file mode will be copied from the source. +func CopyFile(src, dst string) (err error) { + if sym, err := IsSymlink(src); err != nil { + return fmt.Errorf("symlink check failed: %w", err) + } else if sym { + if err := cloneSymlink(src, dst); err != nil { + if runtime.GOOS == "windows" { + // If cloning the symlink fails on Windows because the user + // does not have the required privileges, ignore the error and + // fall back to copying the file contents. + // + // ERROR_PRIVILEGE_NOT_HELD is 1314 (0x522): + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx + if lerr, ok := err.(*os.LinkError); ok && lerr.Err != syscall.Errno(1314) { + return err + } + } else { + return err + } + } else { + return nil + } + } + + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + out, err := os.Create(dst) + if err != nil { + return err + } + + if _, err = io.Copy(out, in); err != nil { + out.Close() + return err + } + + // Check for write errors on Close + if err = out.Close(); err != nil { + return err + } + + si, err := os.Stat(src) + if err != nil { + return err + } + + // Temporary fix for Go < 1.9 + // + // See: https://github.com/golang/dep/issues/774 + // and https://github.com/golang/go/issues/20829 + if runtime.GOOS == "windows" { + dst = fixLongPath(dst) + } + err = os.Chmod(dst, si.Mode()) + + return err +} + +// cloneSymlink will create a new symlink that points to the resolved path of sl. +// If sl is a relative symlink, dst will also be a relative symlink. +func cloneSymlink(sl, dst string) error { + resolved, err := os.Readlink(sl) + if err != nil { + return err + } + + return os.Symlink(resolved, dst) +} + +// IsDir determines is the path given is a directory or not. +func IsDir(name string) (bool, error) { + fi, err := os.Stat(name) + if err != nil { + return false, err + } + if !fi.IsDir() { + return false, fmt.Errorf("%q is not a directory", name) + } + return true, nil +} + +// IsSymlink determines if the given path is a symbolic link. +func IsSymlink(path string) (bool, error) { + l, err := os.Lstat(path) + if err != nil { + return false, err + } + + return l.Mode()&os.ModeSymlink == os.ModeSymlink, nil +} + +// fixLongPath returns the extended-length (\\?\-prefixed) form of +// path when needed, in order to avoid the default 260 character file +// path limit imposed by Windows. If path is not easily converted to +// the extended-length form (for example, if path is a relative path +// or contains .. elements), or is short enough, fixLongPath returns +// path unmodified. +// +// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath +func fixLongPath(path string) string { + // Do nothing (and don't allocate) if the path is "short". + // Empirically (at least on the Windows Server 2013 builder), + // the kernel is arbitrarily okay with < 248 bytes. That + // matches what the docs above say: + // "When using an API to create a directory, the specified + // path cannot be so long that you cannot append an 8.3 file + // name (that is, the directory name cannot exceed MAX_PATH + // minus 12)." Since MAX_PATH is 260, 260 - 12 = 248. + // + // The MSDN docs appear to say that a normal path that is 248 bytes long + // will work; empirically the path must be less than 248 bytes long. + if len(path) < 248 { + // Don't fix. (This is how Go 1.7 and earlier worked, + // not automatically generating the \\?\ form) + return path + } + + // The extended form begins with \\?\, as in + // \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt. + // The extended form disables evaluation of . and .. path + // elements and disables the interpretation of / as equivalent + // to \. The conversion here rewrites / to \ and elides + // . elements as well as trailing or duplicate separators. For + // simplicity it avoids the conversion entirely for relative + // paths or paths containing .. elements. For now, + // \\server\share paths are not converted to + // \\?\UNC\server\share paths because the rules for doing so + // are less well-specified. + if len(path) >= 2 && path[:2] == `\\` { + // Don't canonicalize UNC paths. + return path + } + if !isAbs(path) { + // Relative path + return path + } + + const prefix = `\\?` + + pathbuf := make([]byte, len(prefix)+len(path)+len(`\`)) + copy(pathbuf, prefix) + n := len(path) + r, w := 0, len(prefix) + for r < n { + switch { + case os.IsPathSeparator(path[r]): + // empty block + r++ + case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])): + // /./ + r++ + case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])): + // /../ is currently unhandled + return path + default: + pathbuf[w] = '\\' + w++ + for ; r < n && !os.IsPathSeparator(path[r]); r++ { + pathbuf[w] = path[r] + w++ + } + } + } + // A drive's root directory needs a trailing \ + if w == len(`\\?\c:`) { + pathbuf[w] = '\\' + w++ + } + return string(pathbuf[:w]) +} + +func isAbs(path string) (b bool) { + v := volumeName(path) + if v == "" { + return false + } + path = path[len(v):] + if path == "" { + return false + } + return os.IsPathSeparator(path[0]) +} + +func volumeName(path string) (v string) { + if len(path) < 2 { + return "" + } + // with drive letter + c := path[0] + if path[1] == ':' && + ('0' <= c && c <= '9' || 'a' <= c && c <= 'z' || + 'A' <= c && c <= 'Z') { + return path[:2] + } + // is it UNC + if l := len(path); l >= 5 && os.IsPathSeparator(path[0]) && os.IsPathSeparator(path[1]) && + !os.IsPathSeparator(path[2]) && path[2] != '.' { + // first, leading `\\` and next shouldn't be `\`. its server name. + for n := 3; n < l-1; n++ { + // second, next '\' shouldn't be repeated. + if os.IsPathSeparator(path[n]) { + n++ + // third, following something characters. its share name. + if !os.IsPathSeparator(path[n]) { + if path[n] == '.' { + break + } + for ; n < l; n++ { + if os.IsPathSeparator(path[n]) { + break + } + } + return path[:n] + } + break + } + } + } + return "" +} diff --git a/vendor/helm.sh/helm/v4/internal/third_party/dep/fs/rename.go b/vendor/helm.sh/helm/v4/internal/third_party/dep/fs/rename.go new file mode 100644 index 00000000000..5f13b1ca343 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/third_party/dep/fs/rename.go @@ -0,0 +1,57 @@ +//go:build !windows + +/* +Copyright (c) for portions of rename.go are held by The Go Authors, 2016 and are provided under +the BSD license. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package fs + +import ( + "fmt" + "os" + "syscall" +) + +// renameFallback attempts to determine the appropriate fallback to failed rename +// operation depending on the resulting error. +func renameFallback(err error, src, dst string) error { + // Rename may fail if src and dst are on different devices; fall back to + // copy if we detect that case. syscall.EXDEV is the common name for the + // cross device link error which has varying output text across different + // operating systems. + terr, ok := err.(*os.LinkError) + if !ok { + return err + } else if terr.Err != syscall.EXDEV { + return fmt.Errorf("link error: cannot rename %s to %s: %w", src, dst, terr) + } + + return renameByCopy(src, dst) +} diff --git a/vendor/helm.sh/helm/v4/internal/third_party/dep/fs/rename_windows.go b/vendor/helm.sh/helm/v4/internal/third_party/dep/fs/rename_windows.go new file mode 100644 index 00000000000..566f695d313 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/third_party/dep/fs/rename_windows.go @@ -0,0 +1,68 @@ +//go:build windows + +/* +Copyright (c) for portions of rename_windows.go are held by The Go Authors, 2016 and are provided under +the BSD license. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package fs + +import ( + "fmt" + "os" + "syscall" +) + +// renameFallback attempts to determine the appropriate fallback to failed rename +// operation depending on the resulting error. +func renameFallback(err error, src, dst string) error { + // Rename may fail if src and dst are on different devices; fall back to + // copy if we detect that case. syscall.EXDEV is the common name for the + // cross device link error which has varying output text across different + // operating systems. + terr, ok := err.(*os.LinkError) + if !ok { + return err + } + + if terr.Err != syscall.EXDEV { + // In windows it can drop down to an operating system call that + // returns an operating system error with a different number and + // message. Checking for that as a fall back. + noerr, ok := terr.Err.(syscall.Errno) + + // 0x11 (ERROR_NOT_SAME_DEVICE) is the windows error. + // See https://msdn.microsoft.com/en-us/library/cc231199.aspx + if ok && noerr != 0x11 { + return fmt.Errorf("link error: cannot rename %s to %s: %w", src, dst, terr) + } + } + + return renameByCopy(src, dst) +} diff --git a/vendor/helm.sh/helm/v4/internal/third_party/k8s.io/kubernetes/deployment/util/deploymentutil.go b/vendor/helm.sh/helm/v4/internal/third_party/k8s.io/kubernetes/deployment/util/deploymentutil.go new file mode 100644 index 00000000000..ae62d0e6f89 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/third_party/k8s.io/kubernetes/deployment/util/deploymentutil.go @@ -0,0 +1,178 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "context" + "sort" + + apps "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" + apiequality "k8s.io/apimachinery/pkg/api/equality" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + intstrutil "k8s.io/apimachinery/pkg/util/intstr" + appsclient "k8s.io/client-go/kubernetes/typed/apps/v1" +) + +// deploymentutil contains a copy of a few functions from Kubernetes controller code to avoid a dependency on k8s.io/kubernetes. +// This code is copied from https://github.com/kubernetes/kubernetes/blob/e856613dd5bb00bcfaca6974431151b5c06cbed5/pkg/controller/deployment/util/deployment_util.go +// No changes to the code were made other than removing some unused functions + +// RsListFunc returns the ReplicaSet from the ReplicaSet namespace and the List metav1.ListOptions. +type RsListFunc func(string, metav1.ListOptions) ([]*apps.ReplicaSet, error) + +// ListReplicaSets returns a slice of RSes the given deployment targets. +// Note that this does NOT attempt to reconcile ControllerRef (adopt/orphan), +// because only the controller itself should do that. +// However, it does filter out anything whose ControllerRef doesn't match. +func ListReplicaSets(deployment *apps.Deployment, getRSList RsListFunc) ([]*apps.ReplicaSet, error) { + // TODO: Right now we list replica sets by their labels. We should list them by selector, i.e. the replica set's selector + // should be a superset of the deployment's selector, see https://github.com/kubernetes/kubernetes/issues/19830. + namespace := deployment.Namespace + selector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector) + if err != nil { + return nil, err + } + options := metav1.ListOptions{LabelSelector: selector.String()} + all, err := getRSList(namespace, options) + if err != nil { + return nil, err + } + // Only include those whose ControllerRef matches the Deployment. + owned := make([]*apps.ReplicaSet, 0, len(all)) + for _, rs := range all { + if metav1.IsControlledBy(rs, deployment) { + owned = append(owned, rs) + } + } + return owned, nil +} + +// ReplicaSetsByCreationTimestamp sorts a list of ReplicaSet by creation timestamp, using their names as a tie breaker. +type ReplicaSetsByCreationTimestamp []*apps.ReplicaSet + +func (o ReplicaSetsByCreationTimestamp) Len() int { return len(o) } +func (o ReplicaSetsByCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] } +func (o ReplicaSetsByCreationTimestamp) Less(i, j int) bool { + if o[i].CreationTimestamp.Equal(&o[j].CreationTimestamp) { + return o[i].Name < o[j].Name + } + return o[i].CreationTimestamp.Before(&o[j].CreationTimestamp) +} + +// FindNewReplicaSet returns the new RS this given deployment targets (the one with the same pod template). +func FindNewReplicaSet(deployment *apps.Deployment, rsList []*apps.ReplicaSet) *apps.ReplicaSet { + sort.Sort(ReplicaSetsByCreationTimestamp(rsList)) + for i := range rsList { + if EqualIgnoreHash(&rsList[i].Spec.Template, &deployment.Spec.Template) { + // In rare cases, such as after cluster upgrades, Deployment may end up with + // having more than one new ReplicaSets that have the same template as its template, + // see https://github.com/kubernetes/kubernetes/issues/40415 + // We deterministically choose the oldest new ReplicaSet. + return rsList[i] + } + } + // new ReplicaSet does not exist. + return nil +} + +// EqualIgnoreHash returns true if two given podTemplateSpec are equal, ignoring the diff in value of Labels[pod-template-hash] +// We ignore pod-template-hash because: +// 1. The hash result would be different upon podTemplateSpec API changes +// (e.g. the addition of a new field will cause the hash code to change) +// 2. The deployment template won't have hash labels +func EqualIgnoreHash(template1, template2 *v1.PodTemplateSpec) bool { + t1Copy := template1.DeepCopy() + t2Copy := template2.DeepCopy() + // Remove hash labels from template.Labels before comparing + delete(t1Copy.Labels, apps.DefaultDeploymentUniqueLabelKey) + delete(t2Copy.Labels, apps.DefaultDeploymentUniqueLabelKey) + return apiequality.Semantic.DeepEqual(t1Copy, t2Copy) +} + +// GetNewReplicaSet returns a replica set that matches the intent of the given deployment; get ReplicaSetList from client interface. +// Returns nil if the new replica set doesn't exist yet. +func GetNewReplicaSet(deployment *apps.Deployment, c appsclient.AppsV1Interface) (*apps.ReplicaSet, error) { + rsList, err := ListReplicaSets(deployment, RsListFromClient(c)) + if err != nil { + return nil, err + } + return FindNewReplicaSet(deployment, rsList), nil +} + +// RsListFromClient returns an rsListFunc that wraps the given client. +func RsListFromClient(c appsclient.AppsV1Interface) RsListFunc { + return func(namespace string, options metav1.ListOptions) ([]*apps.ReplicaSet, error) { + rsList, err := c.ReplicaSets(namespace).List(context.Background(), options) + if err != nil { + return nil, err + } + var ret []*apps.ReplicaSet + for i := range rsList.Items { + ret = append(ret, &rsList.Items[i]) + } + return ret, err + } +} + +// IsRollingUpdate returns true if the strategy type is a rolling update. +func IsRollingUpdate(deployment *apps.Deployment) bool { + return deployment.Spec.Strategy.Type == apps.RollingUpdateDeploymentStrategyType +} + +// MaxUnavailable returns the maximum unavailable pods a rolling deployment can take. +func MaxUnavailable(deployment apps.Deployment) int32 { + if !IsRollingUpdate(&deployment) || *(deployment.Spec.Replicas) == 0 { + return int32(0) + } + // Error caught by validation + _, maxUnavailable, _ := ResolveFenceposts(deployment.Spec.Strategy.RollingUpdate.MaxSurge, deployment.Spec.Strategy.RollingUpdate.MaxUnavailable, *(deployment.Spec.Replicas)) + if maxUnavailable > *deployment.Spec.Replicas { + return *deployment.Spec.Replicas + } + return maxUnavailable +} + +// ResolveFenceposts resolves both maxSurge and maxUnavailable. This needs to happen in one +// step. For example: +// +// 2 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1), then old(-1), then new(+1) +// 1 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1) +// 2 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1) +// 1 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1) +// 2 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1) +// 1 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1) +func ResolveFenceposts(maxSurge, maxUnavailable *intstrutil.IntOrString, desired int32) (int32, int32, error) { + surge, err := intstrutil.GetValueFromIntOrPercent(intstrutil.ValueOrDefault(maxSurge, intstrutil.FromInt(0)), int(desired), true) + if err != nil { + return 0, 0, err + } + unavailable, err := intstrutil.GetValueFromIntOrPercent(intstrutil.ValueOrDefault(maxUnavailable, intstrutil.FromInt(0)), int(desired), false) + if err != nil { + return 0, 0, err + } + + if surge == 0 && unavailable == 0 { + // Validation should never allow the user to explicitly use zero values for both maxSurge + // maxUnavailable. Due to rounding down maxUnavailable though, it may resolve to zero. + // If both fenceposts resolve to zero, then we should set maxUnavailable to 1 on the + // theory that surge might not work due to quota. + unavailable = 1 + } + + return int32(surge), int32(unavailable), nil +} diff --git a/vendor/helm.sh/helm/v4/internal/tlsutil/tls.go b/vendor/helm.sh/helm/v4/internal/tlsutil/tls.go new file mode 100644 index 00000000000..88f26d47bb2 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/tlsutil/tls.go @@ -0,0 +1,122 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tlsutil + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "os" + + "errors" +) + +type TLSConfigOptions struct { + insecureSkipTLSVerify bool + certPEMBlock, keyPEMBlock []byte + caPEMBlock []byte +} + +type TLSConfigOption func(options *TLSConfigOptions) error + +func WithInsecureSkipVerify(insecureSkipTLSVerify bool) TLSConfigOption { + return func(options *TLSConfigOptions) error { + options.insecureSkipTLSVerify = insecureSkipTLSVerify + + return nil + } +} + +func WithCertKeyPairFiles(certFile, keyFile string) TLSConfigOption { + return func(options *TLSConfigOptions) error { + if certFile == "" && keyFile == "" { + return nil + } + + certPEMBlock, err := os.ReadFile(certFile) + if err != nil { + return fmt.Errorf("unable to read cert file: %q: %w", certFile, err) + } + + keyPEMBlock, err := os.ReadFile(keyFile) + if err != nil { + return fmt.Errorf("unable to read key file: %q: %w", keyFile, err) + } + + options.certPEMBlock = certPEMBlock + options.keyPEMBlock = keyPEMBlock + + return nil + } +} + +func WithCAFile(caFile string) TLSConfigOption { + return func(options *TLSConfigOptions) error { + if caFile == "" { + return nil + } + + caPEMBlock, err := os.ReadFile(caFile) + if err != nil { + return fmt.Errorf("can't read CA file: %q: %w", caFile, err) + } + + options.caPEMBlock = caPEMBlock + + return nil + } +} + +func NewTLSConfig(options ...TLSConfigOption) (*tls.Config, error) { + to := TLSConfigOptions{} + + errs := []error{} + for _, option := range options { + err := option(&to) + if err != nil { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + + config := tls.Config{ + InsecureSkipVerify: to.insecureSkipTLSVerify, + } + + if len(to.certPEMBlock) > 0 && len(to.keyPEMBlock) > 0 { + cert, err := tls.X509KeyPair(to.certPEMBlock, to.keyPEMBlock) + if err != nil { + return nil, fmt.Errorf("unable to load cert from key pair: %w", err) + } + + config.Certificates = []tls.Certificate{cert} + } + + if len(to.caPEMBlock) > 0 { + cp := x509.NewCertPool() + if !cp.AppendCertsFromPEM(to.caPEMBlock) { + return nil, fmt.Errorf("failed to append certificates from pem block") + } + + config.RootCAs = cp + } + + return &config, nil +} diff --git a/vendor/helm.sh/helm/v4/internal/urlutil/urlutil.go b/vendor/helm.sh/helm/v4/internal/urlutil/urlutil.go new file mode 100644 index 00000000000..a8cf7398c07 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/urlutil/urlutil.go @@ -0,0 +1,73 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package urlutil + +import ( + "net/url" + "path" + "path/filepath" +) + +// URLJoin joins a base URL to one or more path components. +// +// It's like filepath.Join for URLs. If the baseURL is pathish, this will still +// perform a join. +// +// If the URL is unparsable, this returns an error. +func URLJoin(baseURL string, paths ...string) (string, error) { + u, err := url.Parse(baseURL) + if err != nil { + return "", err + } + // We want path instead of filepath because path always uses /. + all := []string{u.Path} + all = append(all, paths...) + u.Path = path.Join(all...) + return u.String(), nil +} + +// Equal normalizes two URLs and then compares for equality. +func Equal(a, b string) bool { + au, err := url.Parse(a) + if err != nil { + a = filepath.Clean(a) + b = filepath.Clean(b) + // If urls are paths, return true only if they are an exact match + return a == b + } + bu, err := url.Parse(b) + if err != nil { + return false + } + + for _, u := range []*url.URL{au, bu} { + if u.Path == "" { + u.Path = "/" + } + u.Path = filepath.Clean(u.Path) + } + return au.String() == bu.String() +} + +// ExtractHostname returns hostname from URL +func ExtractHostname(addr string) (string, error) { + u, err := url.Parse(addr) + if err != nil { + return "", err + } + return u.Hostname(), nil +} diff --git a/vendor/helm.sh/helm/v4/internal/version/clientgo.go b/vendor/helm.sh/helm/v4/internal/version/clientgo.go new file mode 100644 index 00000000000..ab2a38fd50f --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/version/clientgo.go @@ -0,0 +1,44 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package version + +import ( + "fmt" + "runtime/debug" + "slices" + + _ "k8s.io/client-go/kubernetes" // Force k8s.io/client-go to be included in the build +) + +func K8sIOClientGoModVersion() (string, error) { + info, ok := debug.ReadBuildInfo() + if !ok { + return "", fmt.Errorf("failed to read build info") + } + + idx := slices.IndexFunc(info.Deps, func(m *debug.Module) bool { + return m.Path == "k8s.io/client-go" + }) + + if idx == -1 { + return "", fmt.Errorf("k8s.io/client-go not found in build info") + } + + m := info.Deps[idx] + + return m.Version, nil +} diff --git a/vendor/helm.sh/helm/v4/internal/version/version.go b/vendor/helm.sh/helm/v4/internal/version/version.go new file mode 100644 index 00000000000..3daf8089335 --- /dev/null +++ b/vendor/helm.sh/helm/v4/internal/version/version.go @@ -0,0 +1,120 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package version + +import ( + "flag" + "fmt" + "log/slog" + "runtime" + "strings" + "testing" + + "github.com/Masterminds/semver/v3" +) + +var ( + // version is the current version of Helm. + // Update this whenever making a new release. + // The version is of the format Major.Minor.Patch[-Prerelease][+BuildMetadata] + // + // Increment major number for new feature additions and behavioral changes. + // Increment minor number for bug fixes and performance enhancements. + version = "v4.1" + + // metadata is extra build time data + metadata = "" + // gitCommit is the git sha1 + gitCommit = "" + // gitTreeState is the state of the git tree + gitTreeState = "" +) + +const ( + kubeClientGoVersionTesting = "v1.20" +) + +// BuildInfo describes the compile time information. +type BuildInfo struct { + // Version is the current semver. + Version string `json:"version,omitempty"` + // GitCommit is the git sha1. + GitCommit string `json:"git_commit,omitempty"` + // GitTreeState is the state of the git tree. + GitTreeState string `json:"git_tree_state,omitempty"` + // GoVersion is the version of the Go compiler used. + GoVersion string `json:"go_version,omitempty"` + // KubeClientVersion is the version of client-go Helm was build with + KubeClientVersion string `json:"kube_client_version"` +} + +// GetVersion returns the semver string of the version +func GetVersion() string { + if metadata == "" { + return version + } + return version + "+" + metadata +} + +// GetUserAgent returns a user agent for user with an HTTP client +func GetUserAgent() string { + return "Helm/" + strings.TrimPrefix(GetVersion(), "v") +} + +// Get returns build info +func Get() BuildInfo { + + makeKubeClientVersionString := func() string { + // Test builds don't include debug info / module info + // (And even if they did, we probably want a stable version during tests anyway) + // Return a default value for test builds + if testing.Testing() { + return kubeClientGoVersionTesting + } + + vstr, err := K8sIOClientGoModVersion() + if err != nil { + slog.Error("failed to retrieve k8s.io/client-go version", slog.Any("error", err)) + return "" + } + + v, err := semver.NewVersion(vstr) + if err != nil { + slog.Error("unable to parse k8s.io/client-go version", slog.String("version", vstr), slog.Any("error", err)) + return "" + } + + kubeClientVersionMajor := v.Major() + 1 + kubeClientVersionMinor := v.Minor() + + return fmt.Sprintf("v%d.%d", kubeClientVersionMajor, kubeClientVersionMinor) + } + + v := BuildInfo{ + Version: GetVersion(), + GitCommit: gitCommit, + GitTreeState: gitTreeState, + GoVersion: runtime.Version(), + KubeClientVersion: makeKubeClientVersionString(), + } + + // HACK(bacongobbler): strip out GoVersion during a test run for consistent test output + if flag.Lookup("test.v") != nil { + v.GoVersion = "" + } + return v +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/action.go b/vendor/helm.sh/helm/v4/pkg/action/action.go new file mode 100644 index 00000000000..b6573d7884f --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/action.go @@ -0,0 +1,632 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "bytes" + "errors" + "fmt" + "io" + "log/slog" + "maps" + "os" + "path" + "path/filepath" + "slices" + "strings" + "sync" + "text/template" + "time" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/client-go/discovery" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "sigs.k8s.io/kustomize/kyaml/kio" + kyaml "sigs.k8s.io/kustomize/kyaml/yaml" + + "helm.sh/helm/v4/internal/logging" + "helm.sh/helm/v4/pkg/chart/common" + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/engine" + "helm.sh/helm/v4/pkg/kube" + "helm.sh/helm/v4/pkg/postrenderer" + "helm.sh/helm/v4/pkg/registry" + ri "helm.sh/helm/v4/pkg/release" + release "helm.sh/helm/v4/pkg/release/v1" + releaseutil "helm.sh/helm/v4/pkg/release/v1/util" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" +) + +// Timestamper is a function capable of producing a timestamp.Timestamper. +// +// By default, this is a time.Time function from the Helm time package. This can +// be overridden for testing though, so that timestamps are predictable. +var Timestamper = time.Now + +var ( + // errMissingChart indicates that a chart was not provided. + errMissingChart = errors.New("no chart provided") + // errMissingRelease indicates that a release (name) was not provided. + errMissingRelease = errors.New("no release provided") + // errInvalidRevision indicates that an invalid release revision number was provided. + errInvalidRevision = errors.New("invalid release revision") + // errPending indicates that another instance of Helm is already applying an operation on a release. + errPending = errors.New("another operation (install/upgrade/rollback) is in progress") +) + +type DryRunStrategy string + +const ( + // DryRunNone indicates the client will make all mutating calls + DryRunNone DryRunStrategy = "none" + + // DryRunClient, or client-side dry-run, indicates the client will avoid + // making calls to the server + DryRunClient DryRunStrategy = "client" + + // DryRunServer, or server-side dry-run, indicates the client will send + // calls to the APIServer with the dry-run parameter to prevent persisting changes + DryRunServer DryRunStrategy = "server" +) + +// Configuration injects the dependencies that all actions share. +type Configuration struct { + // RESTClientGetter is an interface that loads Kubernetes clients. + RESTClientGetter RESTClientGetter + + // Releases stores records of releases. + Releases *storage.Storage + + // KubeClient is a Kubernetes API client. + KubeClient kube.Interface + + // RegistryClient is a client for working with registries + RegistryClient *registry.Client + + // Capabilities describes the capabilities of the Kubernetes cluster. + Capabilities *common.Capabilities + + // CustomTemplateFuncs is defined by users to provide custom template funcs + CustomTemplateFuncs template.FuncMap + + // HookOutputFunc called with container name and returns and expects writer that will receive the log output. + HookOutputFunc func(namespace, pod, container string) io.Writer + + // Mutex is an exclusive lock for concurrent access to the action + mutex sync.Mutex + + // Embed a LogHolder to provide logger functionality + logging.LogHolder +} + +type ConfigurationOption func(c *Configuration) + +// Override the default logging handler +// If unspecified, the default logger will be used +func ConfigurationSetLogger(h slog.Handler) ConfigurationOption { + return func(c *Configuration) { + c.SetLogger(h) + } +} + +func NewConfiguration(options ...ConfigurationOption) *Configuration { + c := &Configuration{} + c.SetLogger(slog.Default().Handler()) + + for _, o := range options { + o(c) + } + + return c +} + +const ( + // filenameAnnotation is the annotation key used to store the original filename + // information in manifest annotations for post-rendering reconstruction. + filenameAnnotation = "postrenderer.helm.sh/postrender-filename" +) + +// fixDocSeparators ensures YAML document separators ("---") are always +// followed by a newline in rendered template content. Go template whitespace +// trimming ({{-) can remove the newline after "---", producing e.g. +// "---apiVersion: v1" which is not a valid YAML document separator. +// This function inserts a newline after any "---" at the start of a line +// that is immediately followed by non-whitespace content. +func fixDocSeparators(content string) string { + var b strings.Builder + remaining := content + for { + // Find "---" at the start of a line (or start of content). + idx := strings.Index(remaining, "---") + if idx == -1 { + b.WriteString(remaining) + break + } + // "---" must be at the start of a line: either idx==0 or preceded by '\n'. + if idx > 0 && remaining[idx-1] != '\n' { + b.WriteString(remaining[:idx+3]) + remaining = remaining[idx+3:] + continue + } + b.WriteString(remaining[:idx+3]) + remaining = remaining[idx+3:] + // If "---" is followed by non-whitespace (e.g. "---apiVersion"), + // insert a newline to make it a proper document separator. + if len(remaining) > 0 && remaining[0] != '\n' && remaining[0] != '\r' && remaining[0] != ' ' && remaining[0] != '\t' { + b.WriteByte('\n') + } + } + return b.String() +} + +// annotateAndMerge combines multiple YAML files into a single stream of documents, +// adding filename annotations to each document for later reconstruction. +func annotateAndMerge(files map[string]string) (string, error) { + var combinedManifests []*kyaml.RNode + + // Get sorted filenames to ensure result is deterministic + fnames := slices.Sorted(maps.Keys(files)) + + for _, fname := range fnames { + content := files[fname] + // Skip partials and empty files. + if strings.HasPrefix(path.Base(fname), "_") || strings.TrimSpace(content) == "" { + continue + } + + // Fix document separators where Go template whitespace trimming + // ({{-) has removed the newline after "---", producing e.g. + // "---apiVersion: v1" which is not a valid YAML document + // separator. Insert the missing newline so kio.ParseAll can + // parse the content correctly. + content = fixDocSeparators(content) + + manifests, err := kio.ParseAll(content) + if err != nil { + return "", fmt.Errorf("parsing %s: %w", fname, err) + } + for _, manifest := range manifests { + if err := manifest.PipeE(kyaml.SetAnnotation(filenameAnnotation, fname)); err != nil { + return "", fmt.Errorf("annotating %s: %w", fname, err) + } + combinedManifests = append(combinedManifests, manifest) + } + } + + merged, err := kio.StringAll(combinedManifests) + if err != nil { + return "", fmt.Errorf("writing merged docs: %w", err) + } + return merged, nil +} + +// splitAndDeannotate reconstructs individual files from a merged YAML stream, +// removing filename annotations and grouping documents by their original filenames. +func splitAndDeannotate(postrendered string) (map[string]string, error) { + manifests, err := kio.ParseAll(postrendered) + if err != nil { + return nil, fmt.Errorf("error parsing YAML: %w", err) + } + + manifestsByFilename := make(map[string][]*kyaml.RNode) + for i, manifest := range manifests { + meta, err := manifest.GetMeta() + if err != nil { + return nil, fmt.Errorf("getting metadata: %w", err) + } + fname := meta.Annotations[filenameAnnotation] + if fname == "" { + fname = fmt.Sprintf("generated-by-postrender-%d.yaml", i) + } + if err := manifest.PipeE(kyaml.ClearAnnotation(filenameAnnotation)); err != nil { + return nil, fmt.Errorf("clearing filename annotation: %w", err) + } + manifestsByFilename[fname] = append(manifestsByFilename[fname], manifest) + } + + reconstructed := make(map[string]string, len(manifestsByFilename)) + for fname, docs := range manifestsByFilename { + fileContents, err := kio.StringAll(docs) + if err != nil { + return nil, fmt.Errorf("re-writing %s: %w", fname, err) + } + reconstructed[fname] = fileContents + } + return reconstructed, nil +} + +// renderResources renders the templates in a chart +// +// TODO: This function is badly in need of a refactor. +// TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed +// +// This code has to do with writing files to disk. +func (cfg *Configuration) renderResources(ch *chart.Chart, values common.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrenderer.PostRenderer, interactWithRemote, enableDNS, hideSecret bool) ([]*release.Hook, *bytes.Buffer, string, error) { + var hs []*release.Hook + b := bytes.NewBuffer(nil) + + caps, err := cfg.getCapabilities() + if err != nil { + return hs, b, "", err + } + + if ch.Metadata.KubeVersion != "" { + if !chartutil.IsCompatibleRange(ch.Metadata.KubeVersion, caps.KubeVersion.String()) { + return hs, b, "", fmt.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.Version) + } + } + + var files map[string]string + var err2 error + + // A `helm template` should not talk to the remote cluster. However, commands with the flag + // `--dry-run` with the value of `false`, `none`, or `server` should try to interact with the cluster. + // It may break in interesting and exotic ways because other data (e.g. discovery) is mocked. + if interactWithRemote && cfg.RESTClientGetter != nil { + restConfig, err := cfg.RESTClientGetter.ToRESTConfig() + if err != nil { + return hs, b, "", err + } + e := engine.New(restConfig) + e.EnableDNS = enableDNS + e.CustomTemplateFuncs = cfg.CustomTemplateFuncs + + files, err2 = e.Render(ch, values) + } else { + var e engine.Engine + e.EnableDNS = enableDNS + e.CustomTemplateFuncs = cfg.CustomTemplateFuncs + + files, err2 = e.Render(ch, values) + } + + if err2 != nil { + return hs, b, "", err2 + } + + // NOTES.txt gets rendered like all the other files, but because it's not a hook nor a resource, + // pull it out of here into a separate file so that we can actually use the output of the rendered + // text file. We have to spin through this map because the file contains path information, so we + // look for terminating NOTES.txt. We also remove it from the files so that we don't have to skip + // it in the sortHooks. + var notesBuffer bytes.Buffer + for k, v := range files { + if strings.HasSuffix(k, notesFileSuffix) { + if subNotes || (k == path.Join(ch.Name(), "templates", notesFileSuffix)) { + // If buffer contains data, add newline before adding more + if notesBuffer.Len() > 0 { + notesBuffer.WriteString("\n") + } + notesBuffer.WriteString(v) + } + delete(files, k) + } + } + notes := notesBuffer.String() + + if pr != nil { + // We need to send files to the post-renderer before sorting and splitting + // hooks from manifests. The post-renderer interface expects a stream of + // manifests (similar to what tools like Kustomize and kubectl expect), whereas + // the sorter uses filenames. + // Here, we merge the documents into a stream, post-render them, and then split + // them back into a map of filename -> content. + + // Merge files as stream of documents for sending to post renderer + merged, err := annotateAndMerge(files) + if err != nil { + return hs, b, notes, fmt.Errorf("error merging manifests: %w", err) + } + + // Run the post renderer + postRendered, err := pr.Run(bytes.NewBufferString(merged)) + if err != nil { + return hs, b, notes, fmt.Errorf("error while running post render on files: %w", err) + } + + // Use the file list and contents received from the post renderer + files, err = splitAndDeannotate(postRendered.String()) + if err != nil { + return hs, b, notes, fmt.Errorf("error while parsing post rendered output: %w", err) + } + } + + // Sort hooks, manifests, and partials. Only hooks and manifests are returned, + // as partials are not used after renderer.Render. Empty manifests are also + // removed here. + hs, manifests, err := releaseutil.SortManifests(files, nil, releaseutil.InstallOrder) + if err != nil { + // By catching parse errors here, we can prevent bogus releases from going + // to Kubernetes. + // + // We return the files as a big blob of data to help the user debug parser + // errors. + for name, content := range files { + if strings.TrimSpace(content) == "" { + continue + } + fmt.Fprintf(b, "---\n# Source: %s\n%s\n", name, content) + } + return hs, b, "", err + } + + // Aggregate all valid manifests into one big doc. + fileWritten := make(map[string]bool) + + if includeCrds { + for _, crd := range ch.CRDObjects() { + if outputDir == "" { + fmt.Fprintf(b, "---\n# Source: %s\n%s\n", crd.Filename, string(crd.File.Data[:])) + } else { + err = writeToFile(outputDir, crd.Filename, string(crd.File.Data[:]), fileWritten[crd.Filename]) + if err != nil { + return hs, b, "", err + } + fileWritten[crd.Filename] = true + } + } + } + + for _, m := range manifests { + if outputDir == "" { + if hideSecret && m.Head.Kind == "Secret" && m.Head.Version == "v1" { + fmt.Fprintf(b, "---\n# Source: %s\n# HIDDEN: The Secret output has been suppressed\n", m.Name) + } else { + fmt.Fprintf(b, "---\n# Source: %s\n%s\n", m.Name, m.Content) + } + } else { + newDir := outputDir + if useReleaseName { + newDir = filepath.Join(outputDir, releaseName) + } + // NOTE: We do not have to worry about the post-renderer because + // output dir is only used by `helm template`. In the next major + // release, we should move this logic to template only as it is not + // used by install or upgrade + err = writeToFile(newDir, m.Name, m.Content, fileWritten[m.Name]) + if err != nil { + return hs, b, "", err + } + fileWritten[m.Name] = true + } + } + + return hs, b, notes, nil +} + +// RESTClientGetter gets the rest client +type RESTClientGetter interface { + ToRESTConfig() (*rest.Config, error) + ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) + ToRESTMapper() (meta.RESTMapper, error) +} + +// capabilities builds a Capabilities from discovery information. +func (cfg *Configuration) getCapabilities() (*common.Capabilities, error) { + if cfg.Capabilities != nil { + return cfg.Capabilities, nil + } + dc, err := cfg.RESTClientGetter.ToDiscoveryClient() + if err != nil { + return nil, fmt.Errorf("could not get Kubernetes discovery client: %w", err) + } + // force a discovery cache invalidation to always fetch the latest server version/capabilities. + dc.Invalidate() + kubeVersion, err := dc.ServerVersion() + if err != nil { + return nil, fmt.Errorf("could not get server version from Kubernetes: %w", err) + } + // Issue #6361: + // Client-Go emits an error when an API service is registered but unimplemented. + // We trap that error here and print a warning. But since the discovery client continues + // building the API object, it is correctly populated with all valid APIs. + // See https://github.com/kubernetes/kubernetes/issues/72051#issuecomment-521157642 + apiVersions, err := GetVersionSet(dc) + if err != nil { + if discovery.IsGroupDiscoveryFailedError(err) { + cfg.Logger().Warn("the kubernetes server has an orphaned API service", slog.Any("error", err)) + cfg.Logger().Warn("to fix this, kubectl delete apiservice ") + } else { + return nil, fmt.Errorf("could not get apiVersions from Kubernetes: %w", err) + } + } + + cfg.Capabilities = &common.Capabilities{ + APIVersions: apiVersions, + KubeVersion: common.KubeVersion{ + Version: kubeVersion.GitVersion, + Major: kubeVersion.Major, + Minor: kubeVersion.Minor, + }, + HelmVersion: common.DefaultCapabilities.HelmVersion, + } + return cfg.Capabilities, nil +} + +// KubernetesClientSet creates a new kubernetes ClientSet based on the configuration +func (cfg *Configuration) KubernetesClientSet() (kubernetes.Interface, error) { + conf, err := cfg.RESTClientGetter.ToRESTConfig() + if err != nil { + return nil, fmt.Errorf("unable to generate config for kubernetes client: %w", err) + } + + return kubernetes.NewForConfig(conf) +} + +// Now generates a timestamp +// +// If the configuration has a Timestamper on it, that will be used. +// Otherwise, this will use time.Now(). +func (cfg *Configuration) Now() time.Time { + return Timestamper() +} + +func (cfg *Configuration) releaseContent(name string, version int) (ri.Releaser, error) { + if err := chartutil.ValidateReleaseName(name); err != nil { + return nil, fmt.Errorf("releaseContent: Release name is invalid: %s", name) + } + + if version <= 0 { + return cfg.Releases.Last(name) + } + + return cfg.Releases.Get(name, version) +} + +// GetVersionSet retrieves a set of available k8s API versions +func GetVersionSet(client discovery.ServerResourcesInterface) (common.VersionSet, error) { + groups, resources, err := client.ServerGroupsAndResources() + if err != nil && !discovery.IsGroupDiscoveryFailedError(err) { + return common.DefaultVersionSet, fmt.Errorf("could not get apiVersions from Kubernetes: %w", err) + } + + // FIXME: The Kubernetes test fixture for cli appears to always return nil + // for calls to Discovery().ServerGroupsAndResources(). So in this case, we + // return the default API list. This is also a safe value to return in any + // other odd-ball case. + if len(groups) == 0 && len(resources) == 0 { + return common.DefaultVersionSet, nil + } + + versionMap := make(map[string]interface{}) + var versions []string + + // Extract the groups + for _, g := range groups { + for _, gv := range g.Versions { + versionMap[gv.GroupVersion] = struct{}{} + } + } + + // Extract the resources + var id string + var ok bool + for _, r := range resources { + for _, rl := range r.APIResources { + + // A Kind at a GroupVersion can show up more than once. We only want + // it displayed once in the final output. + id = path.Join(r.GroupVersion, rl.Kind) + if _, ok = versionMap[id]; !ok { + versionMap[id] = struct{}{} + } + } + } + + // Convert to a form that NewVersionSet can use + for k := range versionMap { + versions = append(versions, k) + } + + return common.VersionSet(versions), nil +} + +// recordRelease with an update operation in case reuse has been set. +func (cfg *Configuration) recordRelease(r *release.Release) { + if err := cfg.Releases.Update(r); err != nil { + cfg.Logger().Warn( + "failed to update release", + slog.String("name", r.Name), + slog.Int("revision", r.Version), + slog.Any("error", err), + ) + } +} + +// Init initializes the action configuration +func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string) error { + kc := kube.New(getter) + kc.SetLogger(cfg.Logger().Handler()) + + lazyClient := &lazyClient{ + namespace: namespace, + clientFn: kc.Factory.KubernetesClientSet, + } + + var store *storage.Storage + switch helmDriver { + case "secret", "secrets", "": + d := driver.NewSecrets(newSecretClient(lazyClient)) + d.SetLogger(cfg.Logger().Handler()) + store = storage.Init(d) + case "configmap", "configmaps": + d := driver.NewConfigMaps(newConfigMapClient(lazyClient)) + d.SetLogger(cfg.Logger().Handler()) + store = storage.Init(d) + case "memory": + var d *driver.Memory + if cfg.Releases != nil { + if mem, ok := cfg.Releases.Driver.(*driver.Memory); ok { + // This function can be called more than once (e.g., helm list --all-namespaces). + // If a memory driver was already initialized, reuse it but set the possibly new namespace. + // We reuse it in case some releases where already created in the existing memory driver. + d = mem + } + } + if d == nil { + d = driver.NewMemory() + } + d.SetLogger(cfg.Logger().Handler()) + d.SetNamespace(namespace) + store = storage.Init(d) + case "sql": + d, err := driver.NewSQL( + os.Getenv("HELM_DRIVER_SQL_CONNECTION_STRING"), + namespace, + ) + if err != nil { + return fmt.Errorf("unable to instantiate SQL driver: %w", err) + } + d.SetLogger(cfg.Logger().Handler()) + store = storage.Init(d) + default: + return fmt.Errorf("unknown driver %q", helmDriver) + } + + cfg.RESTClientGetter = getter + cfg.KubeClient = kc + cfg.Releases = store + cfg.HookOutputFunc = func(_, _, _ string) io.Writer { return io.Discard } + + return nil +} + +// SetHookOutputFunc sets the HookOutputFunc on the Configuration. +func (cfg *Configuration) SetHookOutputFunc(hookOutputFunc func(_, _, _ string) io.Writer) { + cfg.HookOutputFunc = hookOutputFunc +} + +func determineReleaseSSApplyMethod(serverSideApply bool) release.ApplyMethod { + if serverSideApply { + return release.ApplyMethodServerSideApply + } + return release.ApplyMethodClientSideApply +} + +// isDryRun returns true if the strategy is set to run as a DryRun +func isDryRun(strategy DryRunStrategy) bool { + return strategy == DryRunClient || strategy == DryRunServer +} + +// interactWithServer determine whether or not to interact with a remote Kubernetes server +func interactWithServer(strategy DryRunStrategy) bool { + return strategy == DryRunNone || strategy == DryRunServer +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/dependency.go b/vendor/helm.sh/helm/v4/pkg/action/dependency.go new file mode 100644 index 00000000000..b12887bde1f --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/dependency.go @@ -0,0 +1,237 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/Masterminds/semver/v3" + "github.com/gosuri/uitable" + + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" +) + +// Dependency is the action for building a given chart's dependency tree. +// +// It provides the implementation of 'helm dependency' and its respective subcommands. +type Dependency struct { + Verify bool + Keyring string + SkipRefresh bool + ColumnWidth uint + Username string + Password string + CertFile string + KeyFile string + CaFile string + InsecureSkipTLSVerify bool + PlainHTTP bool +} + +// NewDependency creates a new Dependency object with the given configuration. +func NewDependency() *Dependency { + return &Dependency{ + ColumnWidth: 80, + } +} + +// List executes 'helm dependency list'. +func (d *Dependency) List(chartpath string, out io.Writer) error { + c, err := loader.Load(chartpath) + if err != nil { + return err + } + + if c.Metadata.Dependencies == nil { + fmt.Fprintf(out, "WARNING: no dependencies at %s\n", filepath.Join(chartpath, "charts")) + return nil + } + + d.printDependencies(chartpath, out, c) + fmt.Fprintln(out) + d.printMissing(chartpath, out, c.Metadata.Dependencies) + return nil +} + +// dependencyStatus returns a string describing the status of a dependency viz a viz the parent chart. +func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, parent *chart.Chart) string { + filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*") + + // If a chart is unpacked, this will check the unpacked chart's `charts/` directory for tarballs. + // Technically, this is COMPLETELY unnecessary, and should be removed in Helm 4. It is here + // to preserved backward compatibility. In Helm 2/3, there is a "difference" between + // the tgz version (which outputs "ok" if it unpacks) and the loaded version (which outputs + // "unpacked"). Early in Helm 2's history, this would have made a difference. But it no + // longer does. However, since this code shipped with Helm 3, the output must remain stable + // until Helm 4. + switch archives, err := filepath.Glob(filepath.Join(chartpath, "charts", filename)); { + case err != nil: + return "bad pattern" + case len(archives) > 1: + // See if the second part is a SemVer + found := []string{} + for _, arc := range archives { + // we need to trip the prefix dirs and the extension off. + filename = strings.TrimSuffix(filepath.Base(arc), ".tgz") + maybeVersion := strings.TrimPrefix(filename, fmt.Sprintf("%s-", dep.Name)) + + if _, err := semver.StrictNewVersion(maybeVersion); err == nil { + // If the version parsed without an error, it is possibly a valid + // version. + found = append(found, arc) + } + } + + if l := len(found); l == 1 { + // If we get here, we do the same thing as in len(archives) == 1. + if r := statArchiveForStatus(found[0], dep); r != "" { + return r + } + + // Fall through and look for directories + } else if l > 1 { + return "too many matches" + } + + // The sanest thing to do here is to fall through and see if we have any directory + // matches. + + case len(archives) == 1: + archive := archives[0] + if r := statArchiveForStatus(archive, dep); r != "" { + return r + } + + } + // End unnecessary code. + + var depChart *chart.Chart + for _, item := range parent.Dependencies() { + if item.Name() == dep.Name { + depChart = item + } + } + + if depChart == nil { + return "missing" + } + + if depChart.Metadata.Version != dep.Version { + constraint, err := semver.NewConstraint(dep.Version) + if err != nil { + return "invalid version" + } + + v, err := semver.NewVersion(depChart.Metadata.Version) + if err != nil { + return "invalid version" + } + + if !constraint.Check(v) { + return "wrong version" + } + } + + return "unpacked" +} + +// stat an archive and return a message if the stat is successful +// +// This is a refactor of the code originally in dependencyStatus. It is here to +// support legacy behavior, and should be removed in Helm 4. +func statArchiveForStatus(archive string, dep *chart.Dependency) string { + if _, err := os.Stat(archive); err == nil { + c, err := loader.Load(archive) + if err != nil { + return "corrupt" + } + if c.Name() != dep.Name { + return "misnamed" + } + + if c.Metadata.Version != dep.Version { + constraint, err := semver.NewConstraint(dep.Version) + if err != nil { + return "invalid version" + } + + v, err := semver.NewVersion(c.Metadata.Version) + if err != nil { + return "invalid version" + } + + if !constraint.Check(v) { + return "wrong version" + } + } + return "ok" + } + return "" +} + +// printDependencies prints all of the dependencies in the yaml file. +func (d *Dependency) printDependencies(chartpath string, out io.Writer, c *chart.Chart) { + table := uitable.New() + table.MaxColWidth = d.ColumnWidth + table.AddRow("NAME", "VERSION", "REPOSITORY", "STATUS") + for _, row := range c.Metadata.Dependencies { + table.AddRow(row.Name, row.Version, row.Repository, d.dependencyStatus(chartpath, row, c)) + } + fmt.Fprintln(out, table) +} + +// printMissing prints warnings about charts that are present on disk, but are +// not in Chart.yaml. +func (d *Dependency) printMissing(chartpath string, out io.Writer, reqs []*chart.Dependency) { + folder := filepath.Join(chartpath, "charts/*") + files, err := filepath.Glob(folder) + if err != nil { + fmt.Fprintln(out, err) + return + } + + for _, f := range files { + fi, err := os.Stat(f) + if err != nil { + fmt.Fprintf(out, "Warning: %s\n", err) + } + // Skip anything that is not a directory and not a tgz file. + if !fi.IsDir() && filepath.Ext(f) != ".tgz" { + continue + } + c, err := loader.Load(f) + if err != nil { + fmt.Fprintf(out, "WARNING: %q is not a chart.\n", f) + continue + } + found := false + for _, d := range reqs { + if d.Name == c.Name() { + found = true + break + } + } + if !found { + fmt.Fprintf(out, "WARNING: %q is not in Chart.yaml.\n", f) + } + } +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/doc.go b/vendor/helm.sh/helm/v4/pkg/action/doc.go new file mode 100644 index 00000000000..3c91bd61848 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/doc.go @@ -0,0 +1,22 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package action contains the logic for each action that Helm can perform. +// +// This is a library for calling top-level Helm actions like 'install', +// 'upgrade', or 'list'. Actions approximately match the command line +// invocations that the Helm client uses. +package action diff --git a/vendor/helm.sh/helm/v4/pkg/action/get.go b/vendor/helm.sh/helm/v4/pkg/action/get.go new file mode 100644 index 00000000000..b5e7c194b1e --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/get.go @@ -0,0 +1,47 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + release "helm.sh/helm/v4/pkg/release" +) + +// Get is the action for checking a given release's information. +// +// It provides the implementation of 'helm get' and its respective subcommands (except `helm get values`). +type Get struct { + cfg *Configuration + + // Initializing Version to 0 will get the latest revision of the release. + Version int +} + +// NewGet creates a new Get object with the given configuration. +func NewGet(cfg *Configuration) *Get { + return &Get{ + cfg: cfg, + } +} + +// Run executes 'helm get' against the given release. +func (g *Get) Run(name string) (release.Releaser, error) { + if err := g.cfg.KubeClient.IsReachable(); err != nil { + return nil, err + } + + return g.cfg.releaseContent(name, g.Version) +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/get_metadata.go b/vendor/helm.sh/helm/v4/pkg/action/get_metadata.go new file mode 100644 index 00000000000..5312dac7f72 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/get_metadata.go @@ -0,0 +1,127 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "errors" + "log/slog" + "sort" + "strings" + "time" + + ci "helm.sh/helm/v4/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/release" +) + +// GetMetadata is the action for checking a given release's metadata. +// +// It provides the implementation of 'helm get metadata'. +type GetMetadata struct { + cfg *Configuration + + Version int +} + +type Metadata struct { + Name string `json:"name" yaml:"name"` + Chart string `json:"chart" yaml:"chart"` + Version string `json:"version" yaml:"version"` + AppVersion string `json:"appVersion" yaml:"appVersion"` + // Annotations are fetched from the Chart.yaml file + Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"` + // Labels of the release which are stored in driver metadata fields storage + Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` + Dependencies []ci.Dependency `json:"dependencies,omitempty" yaml:"dependencies,omitempty"` + Namespace string `json:"namespace" yaml:"namespace"` + Revision int `json:"revision" yaml:"revision"` + Status string `json:"status" yaml:"status"` + DeployedAt string `json:"deployedAt" yaml:"deployedAt"` + ApplyMethod string `json:"applyMethod,omitempty" yaml:"applyMethod,omitempty"` +} + +// NewGetMetadata creates a new GetMetadata object with the given configuration. +func NewGetMetadata(cfg *Configuration) *GetMetadata { + return &GetMetadata{ + cfg: cfg, + } +} + +// Run executes 'helm get metadata' against the given release. +func (g *GetMetadata) Run(name string) (*Metadata, error) { + if err := g.cfg.KubeClient.IsReachable(); err != nil { + return nil, err + } + + rel, err := g.cfg.releaseContent(name, g.Version) + if err != nil { + return nil, err + } + + rac, err := release.NewAccessor(rel) + if err != nil { + return nil, err + } + ac, err := ci.NewAccessor(rac.Chart()) + if err != nil { + return nil, err + } + + charti := rac.Chart() + + var chrt *chart.Chart + switch c := charti.(type) { + case *chart.Chart: + chrt = c + case chart.Chart: + chrt = &c + default: + return nil, errors.New("invalid chart apiVersion") + } + + return &Metadata{ + Name: rac.Name(), + Chart: chrt.Metadata.Name, + Version: chrt.Metadata.Version, + AppVersion: chrt.Metadata.AppVersion, + Dependencies: ac.MetaDependencies(), + Annotations: chrt.Metadata.Annotations, + Labels: rac.Labels(), + Namespace: rac.Namespace(), + Revision: rac.Version(), + Status: rac.Status(), + DeployedAt: rac.DeployedAt().Format(time.RFC3339), + ApplyMethod: rac.ApplyMethod(), + }, nil +} + +// FormattedDepNames formats metadata.dependencies names into a comma-separated list. +func (m *Metadata) FormattedDepNames() string { + depsNames := make([]string, 0, len(m.Dependencies)) + for _, dep := range m.Dependencies { + ac, err := ci.NewDependencyAccessor(dep) + if err != nil { + slog.Error("unable to access dependency metadata", "error", err) + continue + } + depsNames = append(depsNames, ac.Name()) + + } + sort.StringSlice(depsNames).Sort() + + return strings.Join(depsNames, ",") +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/get_values.go b/vendor/helm.sh/helm/v4/pkg/action/get_values.go new file mode 100644 index 00000000000..6475a140b85 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/get_values.go @@ -0,0 +1,84 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "fmt" + + "helm.sh/helm/v4/pkg/chart/common/util" + release "helm.sh/helm/v4/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" +) + +// GetValues is the action for checking a given release's values. +// +// It provides the implementation of 'helm get values'. +type GetValues struct { + cfg *Configuration + + Version int + AllValues bool +} + +// NewGetValues creates a new GetValues object with the given configuration. +func NewGetValues(cfg *Configuration) *GetValues { + return &GetValues{ + cfg: cfg, + } +} + +// Run executes 'helm get values' against the given release. +func (g *GetValues) Run(name string) (map[string]interface{}, error) { + if err := g.cfg.KubeClient.IsReachable(); err != nil { + return nil, err + } + + reli, err := g.cfg.releaseContent(name, g.Version) + if err != nil { + return nil, err + } + + rel, err := releaserToV1Release(reli) + if err != nil { + return nil, err + } + + // If the user wants all values, compute the values and return. + if g.AllValues { + cfg, err := util.CoalesceValues(rel.Chart, rel.Config) + if err != nil { + return nil, err + } + return cfg, nil + } + return rel.Config, nil +} + +// releaserToV1Release is a helper function to convert a v1 release passed by interface +// into the type object. +func releaserToV1Release(rel release.Releaser) (*rspb.Release, error) { + switch r := rel.(type) { + case rspb.Release: + return &r, nil + case *rspb.Release: + return r, nil + case nil: + return nil, nil + default: + return nil, fmt.Errorf("unsupported release type: %T", rel) + } +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/history.go b/vendor/helm.sh/helm/v4/pkg/action/history.go new file mode 100644 index 00000000000..3d561b3adf7 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/history.go @@ -0,0 +1,58 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "fmt" + + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/release" +) + +// History is the action for checking the release's ledger. +// +// It provides the implementation of 'helm history'. +// It returns all the revisions for a specific release. +// To list up to one revision of every release in one specific, or in all, +// namespaces, see the List action. +type History struct { + cfg *Configuration + + Max int + Version int +} + +// NewHistory creates a new History object with the given configuration. +func NewHistory(cfg *Configuration) *History { + return &History{ + cfg: cfg, + } +} + +// Run executes 'helm history' against the given release. +func (h *History) Run(name string) ([]release.Releaser, error) { + if err := h.cfg.KubeClient.IsReachable(); err != nil { + return nil, err + } + + if err := chartutil.ValidateReleaseName(name); err != nil { + return nil, fmt.Errorf("release name is invalid: %s", name) + } + + h.cfg.Logger().Debug("getting history for release", "release", name) + return h.cfg.Releases.History(name) +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/hooks.go b/vendor/helm.sh/helm/v4/pkg/action/hooks.go new file mode 100644 index 00000000000..28033395b5a --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/hooks.go @@ -0,0 +1,297 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "bytes" + "fmt" + "log" + "slices" + "sort" + "time" + + "helm.sh/helm/v4/pkg/kube" + + "go.yaml.in/yaml/v3" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + release "helm.sh/helm/v4/pkg/release/v1" +) + +// execHook executes all of the hooks for the given hook event. +func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, + waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, + timeout time.Duration, serverSideApply bool) error { + + shutdown, err := cfg.execHookWithDelayedShutdown(rl, hook, waitStrategy, waitOptions, timeout, serverSideApply) + if shutdown == nil { + return err + } + if err != nil { + if err := shutdown(); err != nil { + return err + } + return err + } + return shutdown() +} + +type ExecuteShutdownFunc = func() error + +func shutdownNoOp() error { + return nil +} + +// execHookWithDelayedShutdown executes all of the hooks for the given hook event and returns a shutdownHook function to trigger deletions after doing other things like e.g. retrieving logs. +func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook release.HookEvent, + waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration, + serverSideApply bool) (ExecuteShutdownFunc, error) { + + executingHooks := []*release.Hook{} + + for _, h := range rl.Hooks { + for _, e := range h.Events { + if e == hook { + executingHooks = append(executingHooks, h) + } + } + } + + // hooke are pre-ordered by kind, so keep order stable + sort.Stable(hookByWeight(executingHooks)) + + for i, h := range executingHooks { + // Set default delete policy to before-hook-creation + cfg.hookSetDeletePolicy(h) + + if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation, waitStrategy, waitOptions, timeout); err != nil { + return shutdownNoOp, err + } + + resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), true) + if err != nil { + return shutdownNoOp, fmt.Errorf("unable to build kubernetes object for %s hook %s: %w", hook, h.Path, err) + } + + // Record the time at which the hook was applied to the cluster + h.LastRun = release.HookExecution{ + StartedAt: time.Now(), + Phase: release.HookPhaseRunning, + } + cfg.recordRelease(rl) + + // As long as the implementation of WatchUntilReady does not panic, HookPhaseFailed or HookPhaseSucceeded + // should always be set by this function. If we fail to do that for any reason, then HookPhaseUnknown is + // the most appropriate value to surface. + h.LastRun.Phase = release.HookPhaseUnknown + + // Create hook resources + if _, err := cfg.KubeClient.Create( + resources, + kube.ClientCreateOptionServerSideApply(serverSideApply, false)); err != nil { + h.LastRun.CompletedAt = time.Now() + h.LastRun.Phase = release.HookPhaseFailed + return shutdownNoOp, fmt.Errorf("warning: Hook %s %s failed: %w", hook, h.Path, err) + } + + var waiter kube.Waiter + if c, supportsOptions := cfg.KubeClient.(kube.InterfaceWaitOptions); supportsOptions { + waiter, err = c.GetWaiterWithOptions(waitStrategy, waitOptions...) + } else { + waiter, err = cfg.KubeClient.GetWaiter(waitStrategy) + } + if err != nil { + return shutdownNoOp, fmt.Errorf("unable to get waiter: %w", err) + } + // Watch hook resources until they have completed + err = waiter.WatchUntilReady(resources, timeout) + // Note the time of success/failure + h.LastRun.CompletedAt = time.Now() + // Mark hook as succeeded or failed + if err != nil { + h.LastRun.Phase = release.HookPhaseFailed + // If a hook is failed, check the annotation of the hook to determine if we should copy the logs client side + if errOutputting := cfg.outputLogsByPolicy(h, rl.Namespace, release.HookOutputOnFailed); errOutputting != nil { + // We log the error here as we want to propagate the hook failure upwards to the release object. + log.Printf("error outputting logs for hook failure: %v", errOutputting) + } + // If a hook is failed, check the annotation of the hook to determine whether the hook should be deleted + // under failed condition. If so, then clear the corresponding resource object in the hook + return func() error { + if errDeleting := cfg.deleteHookByPolicy(h, release.HookFailed, waitStrategy, waitOptions, timeout); errDeleting != nil { + // We log the error here as we want to propagate the hook failure upwards to the release object. + log.Printf("error deleting the hook resource on hook failure: %v", errDeleting) + } + + // If a hook is failed, check the annotation of the previous successful hooks to determine whether the hooks + // should be deleted under succeeded condition. + if err := cfg.deleteHooksByPolicy(executingHooks[0:i], release.HookSucceeded, waitStrategy, waitOptions, timeout); err != nil { + return err + } + return err + }, err + } + h.LastRun.Phase = release.HookPhaseSucceeded + } + + return func() error { + // If all hooks are successful, check the annotation of each hook to determine whether the hook should be deleted + // or output should be logged under succeeded condition. If so, then clear the corresponding resource object in each hook + for i := len(executingHooks) - 1; i >= 0; i-- { + h := executingHooks[i] + if err := cfg.outputLogsByPolicy(h, rl.Namespace, release.HookOutputOnSucceeded); err != nil { + // We log here as we still want to attempt hook resource deletion even if output logging fails. + log.Printf("error outputting logs for hook failure: %v", err) + } + if err := cfg.deleteHookByPolicy(h, release.HookSucceeded, waitStrategy, waitOptions, timeout); err != nil { + return err + } + } + return nil + }, nil +} + +// hookByWeight is a sorter for hooks +type hookByWeight []*release.Hook + +func (x hookByWeight) Len() int { return len(x) } +func (x hookByWeight) Swap(i, j int) { x[i], x[j] = x[j], x[i] } +func (x hookByWeight) Less(i, j int) bool { + if x[i].Weight == x[j].Weight { + return x[i].Name < x[j].Name + } + return x[i].Weight < x[j].Weight +} + +// deleteHookByPolicy deletes a hook if the hook policy instructs it to +func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy, + waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration) error { + + // Never delete CustomResourceDefinitions; this could cause lots of + // cascading garbage collection. + if h.Kind == "CustomResourceDefinition" { + return nil + } + if cfg.hookHasDeletePolicy(h, policy) { + resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), false) + if err != nil { + return fmt.Errorf("unable to build kubernetes object for deleting hook %s: %w", h.Path, err) + } + _, errs := cfg.KubeClient.Delete(resources, metav1.DeletePropagationBackground) + if len(errs) > 0 { + return joinErrors(errs, "; ") + } + + var waiter kube.Waiter + if c, supportsOptions := cfg.KubeClient.(kube.InterfaceWaitOptions); supportsOptions { + waiter, err = c.GetWaiterWithOptions(waitStrategy, waitOptions...) + } else { + waiter, err = cfg.KubeClient.GetWaiter(waitStrategy) + } + if err != nil { + return err + } + if err := waiter.WaitForDelete(resources, timeout); err != nil { + return err + } + } + return nil +} + +// deleteHooksByPolicy deletes all hooks if the hook policy instructs it to +func (cfg *Configuration) deleteHooksByPolicy(hooks []*release.Hook, policy release.HookDeletePolicy, + waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration) error { + + for _, h := range hooks { + if err := cfg.deleteHookByPolicy(h, policy, waitStrategy, waitOptions, timeout); err != nil { + return err + } + } + + return nil +} + +// hookHasDeletePolicy determines whether the defined hook deletion policy matches the hook deletion polices +// supported by helm. If so, mark the hook as one should be deleted. +func (cfg *Configuration) hookHasDeletePolicy(h *release.Hook, policy release.HookDeletePolicy) bool { + cfg.mutex.Lock() + defer cfg.mutex.Unlock() + return slices.Contains(h.DeletePolicies, policy) +} + +// hookSetDeletePolicy determines whether the defined hook deletion policy matches the hook deletion polices +// supported by helm. If so, mark the hook as one should be deleted. +func (cfg *Configuration) hookSetDeletePolicy(h *release.Hook) { + cfg.mutex.Lock() + defer cfg.mutex.Unlock() + if len(h.DeletePolicies) == 0 { + // TODO(jlegrone): Only apply before-hook-creation delete policy to run to completion + // resources. For all other resource types update in place if a + // resource with the same name already exists and is owned by the + // current release. + h.DeletePolicies = []release.HookDeletePolicy{release.HookBeforeHookCreation} + } +} + +// outputLogsByPolicy outputs a pods logs if the hook policy instructs it to +func (cfg *Configuration) outputLogsByPolicy(h *release.Hook, releaseNamespace string, policy release.HookOutputLogPolicy) error { + if !hookHasOutputLogPolicy(h, policy) { + return nil + } + namespace, err := cfg.deriveNamespace(h, releaseNamespace) + if err != nil { + return err + } + switch h.Kind { + case "Job": + return cfg.outputContainerLogsForListOptions(namespace, metav1.ListOptions{LabelSelector: fmt.Sprintf("job-name=%s", h.Name)}) + case "Pod": + return cfg.outputContainerLogsForListOptions(namespace, metav1.ListOptions{FieldSelector: fmt.Sprintf("metadata.name=%s", h.Name)}) + default: + return nil + } +} + +func (cfg *Configuration) outputContainerLogsForListOptions(namespace string, listOptions metav1.ListOptions) error { + podList, err := cfg.KubeClient.GetPodList(namespace, listOptions) + if err != nil { + return err + } + + return cfg.KubeClient.OutputContainerLogsForPodList(podList, namespace, cfg.HookOutputFunc) +} + +func (cfg *Configuration) deriveNamespace(h *release.Hook, namespace string) (string, error) { + tmp := struct { + Metadata struct { + Namespace string + } + }{} + err := yaml.Unmarshal([]byte(h.Manifest), &tmp) + if err != nil { + return "", fmt.Errorf("unable to parse metadata.namespace from kubernetes manifest for output logs hook %s: %w", h.Path, err) + } + if tmp.Metadata.Namespace == "" { + return namespace, nil + } + return tmp.Metadata.Namespace, nil +} + +// hookHasOutputLogPolicy determines whether the defined hook output log policy matches the hook output log policies +// supported by helm. +func hookHasOutputLogPolicy(h *release.Hook, policy release.HookOutputLogPolicy) bool { + return slices.Contains(h.OutputLogPolicies, policy) +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/install.go b/vendor/helm.sh/helm/v4/pkg/action/install.go new file mode 100644 index 00000000000..84099ebc0db --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/install.go @@ -0,0 +1,988 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "io/fs" + "log/slog" + "net/url" + "os" + "path/filepath" + "strings" + "sync" + "sync/atomic" + "text/template" + "time" + + "github.com/Masterminds/sprig/v3" + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/cli-runtime/pkg/resource" + "sigs.k8s.io/yaml" + + ci "helm.sh/helm/v4/pkg/chart" + "helm.sh/helm/v4/pkg/chart/common" + "helm.sh/helm/v4/pkg/chart/common/util" + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/downloader" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/kube" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + "helm.sh/helm/v4/pkg/postrenderer" + "helm.sh/helm/v4/pkg/registry" + ri "helm.sh/helm/v4/pkg/release" + rcommon "helm.sh/helm/v4/pkg/release/common" + release "helm.sh/helm/v4/pkg/release/v1" + releaseutil "helm.sh/helm/v4/pkg/release/v1/util" + "helm.sh/helm/v4/pkg/repo/v1" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" +) + +// notesFileSuffix that we want to treat specially. It goes through the templating engine +// but it's not a YAML file (resource) hence can't have hooks, etc. And the user actually +// wants to see this file after rendering in the status command. However, it must be a suffix +// since there can be filepath in front of it. +const notesFileSuffix = "NOTES.txt" + +const defaultDirectoryPermission = 0755 + +// Install performs an installation operation. +type Install struct { + cfg *Configuration + + ChartPathOptions + + // ForceReplace will, if set to `true`, ignore certain warnings and perform the install anyway. + // + // This should be used with caution. + ForceReplace bool + // ForceConflicts causes server-side apply to force conflicts ("Overwrite value, become sole manager") + // see: https://kubernetes.io/docs/reference/using-api/server-side-apply/#conflicts + ForceConflicts bool + // ServerSideApply when true (default) will enable changes to be applied via Kubernetes server-side apply + // see: https://kubernetes.io/docs/reference/using-api/server-side-apply/ + ServerSideApply bool + CreateNamespace bool + // DryRunStrategy can be set to prepare, but not execute the operation and whether or not to interact with the remote cluster + DryRunStrategy DryRunStrategy + // HideSecret can be set to true when DryRun is enabled in order to hide + // Kubernetes Secrets in the output. It cannot be used outside of DryRun. + HideSecret bool + DisableHooks bool + Replace bool + WaitStrategy kube.WaitStrategy + WaitOptions []kube.WaitOption + WaitForJobs bool + Devel bool + DependencyUpdate bool + Timeout time.Duration + Namespace string + ReleaseName string + GenerateName bool + NameTemplate string + Description string + OutputDir string + // RollbackOnFailure enables rolling back (uninstalling) the release on failure if set + RollbackOnFailure bool + SkipCRDs bool + SubNotes bool + HideNotes bool + SkipSchemaValidation bool + DisableOpenAPIValidation bool + IncludeCRDs bool + Labels map[string]string + // KubeVersion allows specifying a custom kubernetes version to use and + // APIVersions allows a manual set of supported API Versions to be passed + // (for things like templating). + KubeVersion *common.KubeVersion + APIVersions common.VersionSet + // Used by helm template to render charts with .Release.IsUpgrade. Ignored if Dry-Run is false + IsUpgrade bool + // Enable DNS lookups when rendering templates + EnableDNS bool + // Used by helm template to add the release as part of OutputDir path + // OutputDir/ + UseReleaseName bool + // TakeOwnership will ignore the check for helm annotations and take ownership of the resources. + TakeOwnership bool + PostRenderer postrenderer.PostRenderer + // Lock to control raceconditions when the process receives a SIGTERM + Lock sync.Mutex + goroutineCount atomic.Int32 +} + +// ChartPathOptions captures common options used for controlling chart paths +type ChartPathOptions struct { + CaFile string // --ca-file + CertFile string // --cert-file + KeyFile string // --key-file + InsecureSkipTLSVerify bool // --insecure-skip-verify + PlainHTTP bool // --plain-http + Keyring string // --keyring + Password string // --password + PassCredentialsAll bool // --pass-credentials + RepoURL string // --repo + Username string // --username + Verify bool // --verify + Version string // --version + + // registryClient provides a registry client but is not added with + // options from a flag + registryClient *registry.Client +} + +// NewInstall creates a new Install object with the given configuration. +func NewInstall(cfg *Configuration) *Install { + in := &Install{ + cfg: cfg, + ServerSideApply: true, // Must always match the CLI default. + DryRunStrategy: DryRunNone, + } + in.registryClient = cfg.RegistryClient + + return in +} + +// SetRegistryClient sets the registry client for the install action +func (i *Install) SetRegistryClient(registryClient *registry.Client) { + i.registryClient = registryClient +} + +// GetRegistryClient get the registry client. +func (i *Install) GetRegistryClient() *registry.Client { + return i.registryClient +} + +func (i *Install) installCRDs(crds []chart.CRD) error { + // We do these one file at a time in the order they were read. + totalItems := []*resource.Info{} + for _, obj := range crds { + if obj.File == nil { + return fmt.Errorf("failed to install CRD %s: file is empty", obj.Name) + } + + if obj.File.Data == nil { + return fmt.Errorf("failed to install CRD %s: file data is empty", obj.Name) + } + + // Read in the resources + res, err := i.cfg.KubeClient.Build(bytes.NewBuffer(obj.File.Data), false) + if err != nil { + return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err) + } + + if len(res) == 0 { + return fmt.Errorf("failed to install CRD %s: resources are empty", obj.Name) + } + + // Send them to Kube + if _, err := i.cfg.KubeClient.Create( + res, + kube.ClientCreateOptionServerSideApply(i.ServerSideApply, i.ForceConflicts)); err != nil { + // If the error is CRD already exists, continue. + if apierrors.IsAlreadyExists(err) { + crdName := obj.Name + i.cfg.Logger().Debug("CRD is already present. Skipping", "crd", crdName) + continue + } + return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err) + } + totalItems = append(totalItems, res...) + } + if len(totalItems) > 0 { + var waiter kube.Waiter + var err error + if c, supportsOptions := i.cfg.KubeClient.(kube.InterfaceWaitOptions); supportsOptions { + waiter, err = c.GetWaiterWithOptions(i.WaitStrategy, i.WaitOptions...) + } else { + waiter, err = i.cfg.KubeClient.GetWaiter(i.WaitStrategy) + } + if err != nil { + return fmt.Errorf("unable to get waiter: %w", err) + } + // Give time for the CRD to be recognized. + if err := waiter.Wait(totalItems, 60*time.Second); err != nil { + return err + } + + // If we have already gathered the capabilities, we need to invalidate + // the cache so that the new CRDs are recognized. This should only be + // the case when an action configuration is reused for multiple actions, + // as otherwise it is later loaded by ourselves when getCapabilities + // is called later on in the installation process. + if i.cfg.RESTClientGetter != nil { + if i.cfg.Capabilities != nil { + discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient() + if err != nil { + return err + } + + if discoveryClient != nil { + i.cfg.Logger().Debug("clearing discovery cache") + discoveryClient.Invalidate() + _, _ = discoveryClient.ServerGroups() + } + } + + // Invalidate the REST mapper, since it will not have the new CRDs + // present. + restMapper, err := i.cfg.RESTClientGetter.ToRESTMapper() + if err != nil { + return err + } + if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok { + i.cfg.Logger().Debug("clearing REST mapper cache") + resettable.Reset() + } + } + } + return nil +} + +// Run executes the installation +// +// If DryRun is set to true, this will prepare the release, but not install it + +func (i *Install) Run(chrt ci.Charter, vals map[string]interface{}) (ri.Releaser, error) { + ctx := context.Background() + return i.RunWithContext(ctx, chrt, vals) +} + +// RunWithContext executes the installation with Context +// +// When the task is cancelled through ctx, the function returns and the install +// proceeds in the background. +func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[string]interface{}) (ri.Releaser, error) { + var chrt *chart.Chart + switch c := ch.(type) { + case *chart.Chart: + chrt = c + case chart.Chart: + chrt = &c + default: + return nil, errors.New("invalid chart apiVersion") + } + + if interactWithServer(i.DryRunStrategy) { + if err := i.cfg.KubeClient.IsReachable(); err != nil { + i.cfg.Logger().Error(fmt.Sprintf("cluster reachability check failed: %v", err)) + return nil, fmt.Errorf("cluster reachability check failed: %w", err) + } + } + + // HideSecret must be used with dry run. Otherwise, return an error. + if !isDryRun(i.DryRunStrategy) && i.HideSecret { + i.cfg.Logger().Error("hiding Kubernetes secrets requires a dry-run mode") + return nil, errors.New("hiding Kubernetes secrets requires a dry-run mode") + } + + if err := i.availableName(); err != nil { + i.cfg.Logger().Error("release name check failed", slog.Any("error", err)) + return nil, fmt.Errorf("release name check failed: %w", err) + } + + if err := chartutil.ProcessDependencies(chrt, vals); err != nil { + i.cfg.Logger().Error("chart dependencies processing failed", slog.Any("error", err)) + return nil, fmt.Errorf("chart dependencies processing failed: %w", err) + } + + // Pre-install anything in the crd/ directory. We do this before Helm + // contacts the upstream server and builds the capabilities object. + if crds := chrt.CRDObjects(); interactWithServer(i.DryRunStrategy) && !i.SkipCRDs && len(crds) > 0 { + // On dry run, bail here + if isDryRun(i.DryRunStrategy) { + i.cfg.Logger().Warn("This chart or one of its subcharts contains CRDs. Rendering may fail or contain inaccuracies.") + } else if err := i.installCRDs(crds); err != nil { + return nil, err + } + } + + if !interactWithServer(i.DryRunStrategy) { + // Add mock objects in here so it doesn't use Kube API server + // NOTE(bacongobbler): used for `helm template` + i.cfg.Capabilities = common.DefaultCapabilities.Copy() + if i.KubeVersion != nil { + i.cfg.Capabilities.KubeVersion = *i.KubeVersion + } + i.cfg.Capabilities.APIVersions = append(i.cfg.Capabilities.APIVersions, i.APIVersions...) + i.cfg.KubeClient = &kubefake.PrintingKubeClient{Out: io.Discard} + + mem := driver.NewMemory() + mem.SetNamespace(i.Namespace) + i.cfg.Releases = storage.Init(mem) + } else if interactWithServer(i.DryRunStrategy) && len(i.APIVersions) > 0 { + i.cfg.Logger().Debug("API Version list given outside of client only mode, this list will be ignored") + } + + // Make sure if RollbackOnFailure is set, that wait is set as well. This makes it so + // the user doesn't have to specify both + if i.WaitStrategy == kube.HookOnlyStrategy && i.RollbackOnFailure { + i.WaitStrategy = kube.StatusWatcherStrategy + } + + caps, err := i.cfg.getCapabilities() + if err != nil { + return nil, err + } + + // special case for helm template --is-upgrade + isUpgrade := i.IsUpgrade && isDryRun(i.DryRunStrategy) + options := common.ReleaseOptions{ + Name: i.ReleaseName, + Namespace: i.Namespace, + Revision: 1, + IsInstall: !isUpgrade, + IsUpgrade: isUpgrade, + } + valuesToRender, err := util.ToRenderValuesWithSchemaValidation(chrt, vals, options, caps, i.SkipSchemaValidation) + if err != nil { + return nil, err + } + + if driver.ContainsSystemLabels(i.Labels) { + return nil, fmt.Errorf("user supplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()) + } + + rel := i.createRelease(chrt, vals, i.Labels) + + var manifestDoc *bytes.Buffer + rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, interactWithServer(i.DryRunStrategy), i.EnableDNS, i.HideSecret) + // Even for errors, attach this if available + if manifestDoc != nil { + rel.Manifest = manifestDoc.String() + } + // Check error from render + if err != nil { + rel.SetStatus(rcommon.StatusFailed, fmt.Sprintf("failed to render resource: %s", err.Error())) + // Return a release with partial data so that the client can show debugging information. + return rel, err + } + + // Mark this release as in-progress + rel.SetStatus(rcommon.StatusPendingInstall, "Initial install underway") + + var toBeAdopted kube.ResourceList + resources, err := i.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), !i.DisableOpenAPIValidation) + if err != nil { + return nil, fmt.Errorf("unable to build kubernetes objects from release manifest: %w", err) + } + + // It is safe to use "forceOwnership" here because these are resources currently rendered by the chart. + err = resources.Visit(setMetadataVisitor(rel.Name, rel.Namespace, true)) + if err != nil { + return nil, err + } + + // Install requires an extra validation step of checking that resources + // don't already exist before we actually create resources. If we continue + // forward and create the release object with resources that already exist, + // we'll end up in a state where we will delete those resources upon + // deleting the release because the manifest will be pointing at that + // resource + if interactWithServer(i.DryRunStrategy) && !isUpgrade && len(resources) > 0 { + if i.TakeOwnership { + toBeAdopted, err = requireAdoption(resources) + } else { + toBeAdopted, err = existingResourceConflict(resources, rel.Name, rel.Namespace) + } + if err != nil { + return nil, fmt.Errorf("unable to continue with install: %w", err) + } + } + + // Bail out here if it is a dry run + if isDryRun(i.DryRunStrategy) { + rel.Info.Description = "Dry run complete" + return rel, nil + } + + if i.CreateNamespace { + ns := &v1.Namespace{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Namespace", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: i.Namespace, + Labels: map[string]string{ + "name": i.Namespace, + }, + }, + } + buf, err := yaml.Marshal(ns) + if err != nil { + return nil, err + } + resourceList, err := i.cfg.KubeClient.Build(bytes.NewBuffer(buf), true) + if err != nil { + return nil, err + } + + if _, err := i.cfg.KubeClient.Create( + resourceList, + kube.ClientCreateOptionServerSideApply(i.ServerSideApply, false)); err != nil && !apierrors.IsAlreadyExists(err) { + return nil, err + } + } + + // If Replace is true, we need to supersede the last release. + if i.Replace { + if err := i.replaceRelease(rel); err != nil { + return nil, err + } + } + + // Store the release in history before continuing. We always know that this is a create operation + if err := i.cfg.Releases.Create(rel); err != nil { + // We could try to recover gracefully here, but since nothing has been installed + // yet, this is probably safer than trying to continue when we know storage is + // not working. + return rel, err + } + + rel, err = i.performInstallCtx(ctx, rel, toBeAdopted, resources) + if err != nil { + rel, err = i.failRelease(rel, err) + } + return rel, err +} + +func (i *Install) performInstallCtx(ctx context.Context, rel *release.Release, toBeAdopted kube.ResourceList, resources kube.ResourceList) (*release.Release, error) { + type Msg struct { + r *release.Release + e error + } + resultChan := make(chan Msg, 1) + + go func() { + i.goroutineCount.Add(1) + rel, err := i.performInstall(rel, toBeAdopted, resources) + resultChan <- Msg{rel, err} + i.goroutineCount.Add(-1) + }() + select { + case <-ctx.Done(): + err := ctx.Err() + return rel, err + case msg := <-resultChan: + return msg.r, msg.e + } +} + +// getGoroutineCount return the number of running routines +func (i *Install) getGoroutineCount() int32 { + return i.goroutineCount.Load() +} + +func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.ResourceList, resources kube.ResourceList) (*release.Release, error) { + var err error + // pre-install hooks + if !i.DisableHooks { + if err := i.cfg.execHook(rel, release.HookPreInstall, i.WaitStrategy, i.WaitOptions, i.Timeout, i.ServerSideApply); err != nil { + return rel, fmt.Errorf("failed pre-install: %s", err) + } + } + + // At this point, we can do the install. Note that before we were detecting whether to + // do an update, but it's not clear whether we WANT to do an update if the reuse is set + // to true, since that is basically an upgrade operation. + if len(toBeAdopted) == 0 && len(resources) > 0 { + _, err = i.cfg.KubeClient.Create( + resources, + kube.ClientCreateOptionServerSideApply(i.ServerSideApply, false)) + } else if len(resources) > 0 { + updateThreeWayMergeForUnstructured := i.TakeOwnership && !i.ServerSideApply // Use three-way merge when taking ownership (and not using server-side apply) + _, err = i.cfg.KubeClient.Update( + toBeAdopted, + resources, + kube.ClientUpdateOptionForceReplace(i.ForceReplace), + kube.ClientUpdateOptionServerSideApply(i.ServerSideApply, i.ForceConflicts), + kube.ClientUpdateOptionThreeWayMergeForUnstructured(updateThreeWayMergeForUnstructured), + kube.ClientUpdateOptionUpgradeClientSideFieldManager(true)) + } + if err != nil { + return rel, err + } + + var waiter kube.Waiter + if c, supportsOptions := i.cfg.KubeClient.(kube.InterfaceWaitOptions); supportsOptions { + waiter, err = c.GetWaiterWithOptions(i.WaitStrategy, i.WaitOptions...) + } else { + waiter, err = i.cfg.KubeClient.GetWaiter(i.WaitStrategy) + } + if err != nil { + return rel, fmt.Errorf("failed to get waiter: %w", err) + } + + if i.WaitForJobs { + err = waiter.WaitWithJobs(resources, i.Timeout) + } else { + err = waiter.Wait(resources, i.Timeout) + } + if err != nil { + return rel, err + } + + if !i.DisableHooks { + if err := i.cfg.execHook(rel, release.HookPostInstall, i.WaitStrategy, i.WaitOptions, i.Timeout, i.ServerSideApply); err != nil { + return rel, fmt.Errorf("failed post-install: %s", err) + } + } + + if len(i.Description) > 0 { + rel.SetStatus(rcommon.StatusDeployed, i.Description) + } else { + rel.SetStatus(rcommon.StatusDeployed, "Install complete") + } + + // This is a tricky case. The release has been created, but the result + // cannot be recorded. The truest thing to tell the user is that the + // release was created. However, the user will not be able to do anything + // further with this release. + // + // One possible strategy would be to do a timed retry to see if we can get + // this stored in the future. + if err := i.recordRelease(rel); err != nil { + i.cfg.Logger().Error("failed to record the release", slog.Any("error", err)) + } + + return rel, nil +} + +func (i *Install) failRelease(rel *release.Release, err error) (*release.Release, error) { + rel.SetStatus(rcommon.StatusFailed, fmt.Sprintf("Release %q failed: %s", i.ReleaseName, err.Error())) + if i.RollbackOnFailure { + i.cfg.Logger().Debug("install failed and rollback-on-failure is set, uninstalling release", "release", i.ReleaseName) + uninstall := NewUninstall(i.cfg) + uninstall.DisableHooks = i.DisableHooks + uninstall.KeepHistory = false + uninstall.Timeout = i.Timeout + uninstall.WaitStrategy = i.WaitStrategy + uninstall.WaitOptions = i.WaitOptions + if _, uninstallErr := uninstall.Run(i.ReleaseName); uninstallErr != nil { + return rel, fmt.Errorf("an error occurred while uninstalling the release. original install error: %w: %w", err, uninstallErr) + } + return rel, fmt.Errorf("release %s failed, and has been uninstalled due to rollback-on-failure being set: %w", i.ReleaseName, err) + } + i.recordRelease(rel) // Ignore the error, since we have another error to deal with. + return rel, err +} + +// availableName tests whether a name is available +// +// Roughly, this will return an error if name is +// +// - empty +// - too long +// - already in use, and not deleted +// - used by a deleted release, and i.Replace is false +func (i *Install) availableName() error { + start := i.ReleaseName + + if err := chartutil.ValidateReleaseName(start); err != nil { + return fmt.Errorf("release name %q: %w", start, err) + } + // On dry run, bail here + if isDryRun(i.DryRunStrategy) { + return nil + } + + h, err := i.cfg.Releases.History(start) + if err != nil || len(h) < 1 { + return nil + } + + hl, err := releaseListToV1List(h) + if err != nil { + return err + } + + releaseutil.Reverse(hl, releaseutil.SortByRevision) + rel := hl[0] + + if st := rel.Info.Status; i.Replace && (st == rcommon.StatusUninstalled || st == rcommon.StatusFailed) { + return nil + } + return errors.New("cannot reuse a name that is still in use") +} + +func releaseListToV1List(ls []ri.Releaser) ([]*release.Release, error) { + rls := make([]*release.Release, 0, len(ls)) + for _, val := range ls { + rel, err := releaserToV1Release(val) + if err != nil { + return nil, err + } + rls = append(rls, rel) + } + + return rls, nil +} + +func releaseV1ListToReleaserList(ls []*release.Release) ([]ri.Releaser, error) { + rls := make([]ri.Releaser, 0, len(ls)) + for _, val := range ls { + rls = append(rls, val) + } + + return rls, nil +} + +// createRelease creates a new release object +func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]interface{}, labels map[string]string) *release.Release { + ts := i.cfg.Now() + + r := &release.Release{ + Name: i.ReleaseName, + Namespace: i.Namespace, + Chart: chrt, + Config: rawVals, + Info: &release.Info{ + FirstDeployed: ts, + LastDeployed: ts, + Status: rcommon.StatusUnknown, + }, + Version: 1, + Labels: labels, + ApplyMethod: string(determineReleaseSSApplyMethod(i.ServerSideApply)), + } + + return r +} + +// recordRelease with an update operation in case reuse has been set. +func (i *Install) recordRelease(r *release.Release) error { + // This is a legacy function which has been reduced to a oneliner. Could probably + // refactor it out. + return i.cfg.Releases.Update(r) +} + +// replaceRelease replaces an older release with this one +// +// This allows us to reuse names by superseding an existing release with a new one +func (i *Install) replaceRelease(rel *release.Release) error { + hist, err := i.cfg.Releases.History(rel.Name) + if err != nil || len(hist) == 0 { + // No releases exist for this name, so we can return early + return nil + } + hl, err := releaseListToV1List(hist) + if err != nil { + return err + } + + releaseutil.Reverse(hl, releaseutil.SortByRevision) + last := hl[0] + + // Update version to the next available + rel.Version = last.Version + 1 + + // Do not change the status of a failed release. + if last.Info.Status == rcommon.StatusFailed { + return nil + } + + // For any other status, mark it as superseded and store the old record + last.SetStatus(rcommon.StatusSuperseded, "superseded by new release") + return i.recordRelease(last) +} + +// write the to /. controls if the file is created or content will be appended +func writeToFile(outputDir string, name string, data string, appendData bool) error { + outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) + + err := ensureDirectoryForFile(outfileName) + if err != nil { + return err + } + + f, err := createOrOpenFile(outfileName, appendData) + if err != nil { + return err + } + + defer f.Close() + + _, err = fmt.Fprintf(f, "---\n# Source: %s\n%s\n", name, data) + + if err != nil { + return err + } + + fmt.Printf("wrote %s\n", outfileName) + return nil +} + +func createOrOpenFile(filename string, appendData bool) (*os.File, error) { + if appendData { + return os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600) + } + return os.Create(filename) +} + +// check if the directory exists to create file. creates if doesn't exist +func ensureDirectoryForFile(file string) error { + baseDir := filepath.Dir(file) + _, err := os.Stat(baseDir) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return err + } + + return os.MkdirAll(baseDir, defaultDirectoryPermission) +} + +// NameAndChart returns the name and chart that should be used. +// +// This will read the flags and handle name generation if necessary. +func (i *Install) NameAndChart(args []string) (string, string, error) { + flagsNotSet := func() error { + if i.GenerateName { + return errors.New("cannot set --generate-name and also specify a name") + } + if i.NameTemplate != "" { + return errors.New("cannot set --name-template and also specify a name") + } + return nil + } + + if len(args) > 2 { + return args[0], args[1], fmt.Errorf("expected at most two arguments, unexpected arguments: %v", strings.Join(args[2:], ", ")) + } + + if len(args) == 2 { + return args[0], args[1], flagsNotSet() + } + + if i.NameTemplate != "" { + name, err := TemplateName(i.NameTemplate) + return name, args[0], err + } + + if i.ReleaseName != "" { + return i.ReleaseName, args[0], nil + } + + if !i.GenerateName { + return "", args[0], errors.New("must either provide a name or specify --generate-name") + } + + base := filepath.Base(args[0]) + if base == "." || base == "" { + base = "chart" + } + // if present, strip out the file extension from the name + if idx := strings.Index(base, "."); idx != -1 { + base = base[0:idx] + } + + return fmt.Sprintf("%s-%d", base, time.Now().Unix()), args[0], nil +} + +// TemplateName renders a name template, returning the name or an error. +func TemplateName(nameTemplate string) (string, error) { + if nameTemplate == "" { + return "", nil + } + + t, err := template.New("name-template").Funcs(sprig.TxtFuncMap()).Parse(nameTemplate) + if err != nil { + return "", err + } + var b bytes.Buffer + if err := t.Execute(&b, nil); err != nil { + return "", err + } + + return b.String(), nil +} + +// CheckDependencies checks the dependencies for a chart. +func CheckDependencies(ch ci.Charter, reqs []ci.Dependency) error { + ac, err := ci.NewAccessor(ch) + if err != nil { + return err + } + + var missing []string + +OUTER: + for _, r := range reqs { + rac, err := ci.NewDependencyAccessor(r) + if err != nil { + return err + } + for _, d := range ac.Dependencies() { + dac, err := ci.NewAccessor(d) + if err != nil { + return err + } + if dac.Name() == rac.Name() { + continue OUTER + } + } + missing = append(missing, rac.Name()) + } + + if len(missing) > 0 { + return fmt.Errorf("found in Chart.yaml, but missing in charts/ directory: %s", strings.Join(missing, ", ")) + } + return nil +} + +func portOrDefault(u *url.URL) string { + if p := u.Port(); p != "" { + return p + } + + switch u.Scheme { + case "http": + return "80" + case "https": + return "443" + default: + return "" + } +} + +func urlEqual(u1, u2 *url.URL) bool { + return u1.Scheme == u2.Scheme && u1.Hostname() == u2.Hostname() && portOrDefault(u1) == portOrDefault(u2) +} + +// LocateChart looks for a chart directory in known places, and returns either the full path or an error. +// +// This does not ensure that the chart is well-formed; only that the requested filename exists. +// +// Order of resolution: +// - relative to current working directory when --repo flag is not presented +// - if path is absolute or begins with '.', error out here +// - URL +// +// If 'verify' was set on ChartPathOptions, this will attempt to also verify the chart. +func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (string, error) { + if registry.IsOCI(name) && c.registryClient == nil { + return "", fmt.Errorf("unable to lookup chart %q, missing registry client", name) + } + + name = strings.TrimSpace(name) + version := strings.TrimSpace(c.Version) + + if c.RepoURL == "" { + if _, err := os.Stat(name); err == nil { + abs, err := filepath.Abs(name) + if err != nil { + return abs, err + } + if c.Verify { + if _, err := downloader.VerifyChart(abs, abs+".prov", c.Keyring); err != nil { + return "", err + } + } + return abs, nil + } + if filepath.IsAbs(name) || strings.HasPrefix(name, ".") { + return name, fmt.Errorf("path %q not found", name) + } + } + + dl := downloader.ChartDownloader{ + Out: os.Stdout, + Keyring: c.Keyring, + Getters: getter.All(settings), + Options: []getter.Option{ + getter.WithPassCredentialsAll(c.PassCredentialsAll), + getter.WithTLSClientConfig(c.CertFile, c.KeyFile, c.CaFile), + getter.WithInsecureSkipVerifyTLS(c.InsecureSkipTLSVerify), + getter.WithPlainHTTP(c.PlainHTTP), + getter.WithBasicAuth(c.Username, c.Password), + }, + RepositoryConfig: settings.RepositoryConfig, + RepositoryCache: settings.RepositoryCache, + ContentCache: settings.ContentCache, + RegistryClient: c.registryClient, + } + + if registry.IsOCI(name) { + dl.Options = append(dl.Options, getter.WithRegistryClient(c.registryClient)) + } + + if c.Verify { + dl.Verify = downloader.VerifyAlways + } + if c.RepoURL != "" { + chartURL, err := repo.FindChartInRepoURL( + c.RepoURL, + name, + getter.All(settings), + repo.WithChartVersion(version), + repo.WithClientTLS(c.CertFile, c.KeyFile, c.CaFile), + repo.WithUsernamePassword(c.Username, c.Password), + repo.WithInsecureSkipTLSVerify(c.InsecureSkipTLSVerify), + repo.WithPassCredentialsAll(c.PassCredentialsAll), + ) + if err != nil { + return "", err + } + name = chartURL + + // Only pass the user/pass on when the user has said to or when the + // location of the chart repo and the chart are the same domain. + u1, err := url.Parse(c.RepoURL) + if err != nil { + return "", err + } + u2, err := url.Parse(chartURL) + if err != nil { + return "", err + } + + // Host on URL (returned from url.Parse) contains the port if present. + // This check ensures credentials are not passed between different + // services on different ports. + if c.PassCredentialsAll || urlEqual(u1, u2) { + dl.Options = append(dl.Options, getter.WithBasicAuth(c.Username, c.Password)) + } else { + dl.Options = append(dl.Options, getter.WithBasicAuth("", "")) + } + } else { + dl.Options = append(dl.Options, getter.WithBasicAuth(c.Username, c.Password)) + } + + if err := os.MkdirAll(settings.RepositoryCache, 0755); err != nil { + return "", err + } + + filename, _, err := dl.DownloadToCache(name, version) + if err != nil { + return "", err + } + + lname, err := filepath.Abs(filename) + if err != nil { + return filename, err + } + return lname, nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/lazyclient.go b/vendor/helm.sh/helm/v4/pkg/action/lazyclient.go new file mode 100644 index 00000000000..9037782bb5c --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/lazyclient.go @@ -0,0 +1,197 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "context" + "sync" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + applycorev1 "k8s.io/client-go/applyconfigurations/core/v1" + "k8s.io/client-go/kubernetes" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" +) + +// lazyClient is a workaround to deal with Kubernetes having an unstable client API. +// In Kubernetes v1.18 the defaults where removed which broke creating a +// client without an explicit configuration. ಠ_ಠ +type lazyClient struct { + // client caches an initialized kubernetes client + initClient sync.Once + client kubernetes.Interface + clientErr error + + // clientFn loads a kubernetes client + clientFn func() (*kubernetes.Clientset, error) + + // namespace passed to each client request + namespace string +} + +func (s *lazyClient) init() error { + s.initClient.Do(func() { + s.client, s.clientErr = s.clientFn() + }) + return s.clientErr +} + +// secretClient implements a corev1.SecretsInterface +type secretClient struct{ *lazyClient } + +var _ corev1.SecretInterface = (*secretClient)(nil) + +func newSecretClient(lc *lazyClient) *secretClient { + return &secretClient{lazyClient: lc} +} + +func (s *secretClient) Create(ctx context.Context, secret *v1.Secret, opts metav1.CreateOptions) (result *v1.Secret, err error) { + if err := s.init(); err != nil { + return nil, err + } + return s.client.CoreV1().Secrets(s.namespace).Create(ctx, secret, opts) +} + +func (s *secretClient) Update(ctx context.Context, secret *v1.Secret, opts metav1.UpdateOptions) (*v1.Secret, error) { + if err := s.init(); err != nil { + return nil, err + } + return s.client.CoreV1().Secrets(s.namespace).Update(ctx, secret, opts) +} + +func (s *secretClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { + if err := s.init(); err != nil { + return err + } + return s.client.CoreV1().Secrets(s.namespace).Delete(ctx, name, opts) +} + +func (s *secretClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { + if err := s.init(); err != nil { + return err + } + return s.client.CoreV1().Secrets(s.namespace).DeleteCollection(ctx, opts, listOpts) +} + +func (s *secretClient) Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Secret, error) { + if err := s.init(); err != nil { + return nil, err + } + return s.client.CoreV1().Secrets(s.namespace).Get(ctx, name, opts) +} + +func (s *secretClient) List(ctx context.Context, opts metav1.ListOptions) (*v1.SecretList, error) { + if err := s.init(); err != nil { + return nil, err + } + return s.client.CoreV1().Secrets(s.namespace).List(ctx, opts) +} + +func (s *secretClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { + if err := s.init(); err != nil { + return nil, err + } + return s.client.CoreV1().Secrets(s.namespace).Watch(ctx, opts) +} + +func (s *secretClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*v1.Secret, error) { + if err := s.init(); err != nil { + return nil, err + } + return s.client.CoreV1().Secrets(s.namespace).Patch(ctx, name, pt, data, opts, subresources...) +} + +func (s *secretClient) Apply(ctx context.Context, secretConfiguration *applycorev1.SecretApplyConfiguration, opts metav1.ApplyOptions) (*v1.Secret, error) { + if err := s.init(); err != nil { + return nil, err + } + return s.client.CoreV1().Secrets(s.namespace).Apply(ctx, secretConfiguration, opts) +} + +// configMapClient implements a corev1.ConfigMapInterface +type configMapClient struct{ *lazyClient } + +var _ corev1.ConfigMapInterface = (*configMapClient)(nil) + +func newConfigMapClient(lc *lazyClient) *configMapClient { + return &configMapClient{lazyClient: lc} +} + +func (c *configMapClient) Create(ctx context.Context, configMap *v1.ConfigMap, opts metav1.CreateOptions) (*v1.ConfigMap, error) { + if err := c.init(); err != nil { + return nil, err + } + return c.client.CoreV1().ConfigMaps(c.namespace).Create(ctx, configMap, opts) +} + +func (c *configMapClient) Update(ctx context.Context, configMap *v1.ConfigMap, opts metav1.UpdateOptions) (*v1.ConfigMap, error) { + if err := c.init(); err != nil { + return nil, err + } + return c.client.CoreV1().ConfigMaps(c.namespace).Update(ctx, configMap, opts) +} + +func (c *configMapClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { + if err := c.init(); err != nil { + return err + } + return c.client.CoreV1().ConfigMaps(c.namespace).Delete(ctx, name, opts) +} + +func (c *configMapClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { + if err := c.init(); err != nil { + return err + } + return c.client.CoreV1().ConfigMaps(c.namespace).DeleteCollection(ctx, opts, listOpts) +} + +func (c *configMapClient) Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.ConfigMap, error) { + if err := c.init(); err != nil { + return nil, err + } + return c.client.CoreV1().ConfigMaps(c.namespace).Get(ctx, name, opts) +} + +func (c *configMapClient) List(ctx context.Context, opts metav1.ListOptions) (*v1.ConfigMapList, error) { + if err := c.init(); err != nil { + return nil, err + } + return c.client.CoreV1().ConfigMaps(c.namespace).List(ctx, opts) +} + +func (c *configMapClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { + if err := c.init(); err != nil { + return nil, err + } + return c.client.CoreV1().ConfigMaps(c.namespace).Watch(ctx, opts) +} + +func (c *configMapClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*v1.ConfigMap, error) { + if err := c.init(); err != nil { + return nil, err + } + return c.client.CoreV1().ConfigMaps(c.namespace).Patch(ctx, name, pt, data, opts, subresources...) +} + +func (c *configMapClient) Apply(ctx context.Context, configMap *applycorev1.ConfigMapApplyConfiguration, opts metav1.ApplyOptions) (*v1.ConfigMap, error) { + if err := c.init(); err != nil { + return nil, err + } + return c.client.CoreV1().ConfigMaps(c.namespace).Apply(ctx, configMap, opts) +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/lint.go b/vendor/helm.sh/helm/v4/pkg/action/lint.go new file mode 100644 index 00000000000..208fd463736 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/lint.go @@ -0,0 +1,136 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "helm.sh/helm/v4/pkg/chart/common" + "helm.sh/helm/v4/pkg/chart/v2/lint" + "helm.sh/helm/v4/pkg/chart/v2/lint/support" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" +) + +// Lint is the action for checking that the semantics of a chart are well-formed. +// +// It provides the implementation of 'helm lint'. +type Lint struct { + Strict bool + Namespace string + WithSubcharts bool + Quiet bool + SkipSchemaValidation bool + KubeVersion *common.KubeVersion +} + +// LintResult is the result of Lint +type LintResult struct { + TotalChartsLinted int + Messages []support.Message + Errors []error +} + +// NewLint creates a new Lint object with the given configuration. +func NewLint() *Lint { + return &Lint{} +} + +// Run executes 'helm Lint' against the given chart. +func (l *Lint) Run(paths []string, vals map[string]interface{}) *LintResult { + lowestTolerance := support.ErrorSev + if l.Strict { + lowestTolerance = support.WarningSev + } + result := &LintResult{} + for _, path := range paths { + linter, err := lintChart(path, vals, l.Namespace, l.KubeVersion, l.SkipSchemaValidation) + if err != nil { + result.Errors = append(result.Errors, err) + continue + } + + result.Messages = append(result.Messages, linter.Messages...) + result.TotalChartsLinted++ + for _, msg := range linter.Messages { + if msg.Severity >= lowestTolerance { + result.Errors = append(result.Errors, msg.Err) + } + } + } + return result +} + +// HasWarningsOrErrors checks is LintResult has any warnings or errors +func HasWarningsOrErrors(result *LintResult) bool { + for _, msg := range result.Messages { + if msg.Severity > support.InfoSev { + return true + } + } + return len(result.Errors) > 0 +} + +func lintChart(path string, vals map[string]interface{}, namespace string, kubeVersion *common.KubeVersion, skipSchemaValidation bool) (support.Linter, error) { + var chartPath string + linter := support.Linter{} + + if strings.HasSuffix(path, ".tgz") || strings.HasSuffix(path, ".tar.gz") { + tempDir, err := os.MkdirTemp("", "helm-lint") + if err != nil { + return linter, fmt.Errorf("unable to create temp dir to extract tarball: %w", err) + } + defer os.RemoveAll(tempDir) + + file, err := os.Open(path) + if err != nil { + return linter, fmt.Errorf("unable to open tarball: %w", err) + } + defer file.Close() + + if err = chartutil.Expand(tempDir, file); err != nil { + return linter, fmt.Errorf("unable to extract tarball: %w", err) + } + + files, err := os.ReadDir(tempDir) + if err != nil { + return linter, fmt.Errorf("unable to read temporary output directory %s: %w", tempDir, err) + } + if !files[0].IsDir() { + return linter, fmt.Errorf("unexpected file %s in temporary output directory %s", files[0].Name(), tempDir) + } + + chartPath = filepath.Join(tempDir, files[0].Name()) + } else { + chartPath = path + } + + // Guard: Error out if this is not a chart. + if _, err := os.Stat(filepath.Join(chartPath, "Chart.yaml")); err != nil { + return linter, fmt.Errorf("unable to check Chart.yaml file in chart: %w", err) + } + + return lint.RunAll( + chartPath, + vals, + namespace, + lint.WithKubeVersion(kubeVersion), + lint.WithSkipSchemaValidation(skipSchemaValidation), + ), nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/list.go b/vendor/helm.sh/helm/v4/pkg/action/list.go new file mode 100644 index 00000000000..06727bd9a59 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/list.go @@ -0,0 +1,334 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "path" + "regexp" + + "k8s.io/apimachinery/pkg/labels" + + ri "helm.sh/helm/v4/pkg/release" + release "helm.sh/helm/v4/pkg/release/v1" + releaseutil "helm.sh/helm/v4/pkg/release/v1/util" +) + +// ListStates represents zero or more status codes that a list item may have set +// +// Because this is used as a bitmask filter, more than one bit can be flipped +// in the ListStates. +type ListStates uint + +const ( + // ListDeployed filters on status "deployed" + ListDeployed ListStates = 1 << iota + // ListUninstalled filters on status "uninstalled" + ListUninstalled + // ListUninstalling filters on status "uninstalling" (uninstall in progress) + ListUninstalling + // ListPendingInstall filters on status "pending" (deployment in progress) + ListPendingInstall + // ListPendingUpgrade filters on status "pending_upgrade" (upgrade in progress) + ListPendingUpgrade + // ListPendingRollback filters on status "pending_rollback" (rollback in progress) + ListPendingRollback + // ListSuperseded filters on status "superseded" (historical release version that is no longer deployed) + ListSuperseded + // ListFailed filters on status "failed" (release version not deployed because of error) + ListFailed + // ListUnknown filters on an unknown status + ListUnknown +) + +// FromName takes a state name and returns a ListStates representation. +// +// Currently, there are only names for individual flipped bits, so the returned +// ListStates will only match one of the constants. However, it is possible that +// this behavior could change in the future. +func (s ListStates) FromName(str string) ListStates { + switch str { + case "deployed": + return ListDeployed + case "uninstalled": + return ListUninstalled + case "superseded": + return ListSuperseded + case "failed": + return ListFailed + case "uninstalling": + return ListUninstalling + case "pending-install": + return ListPendingInstall + case "pending-upgrade": + return ListPendingUpgrade + case "pending-rollback": + return ListPendingRollback + } + return ListUnknown +} + +// ListAll is a convenience for enabling all list filters +const ListAll = ListDeployed | ListUninstalled | ListUninstalling | ListPendingInstall | ListPendingRollback | ListPendingUpgrade | ListSuperseded | ListFailed + +// Sorter is a top-level sort +type Sorter uint + +const ( + // ByNameDesc sorts by descending lexicographic order + ByNameDesc Sorter = iota + 1 + // ByDateAsc sorts by ascending dates (oldest updated release first) + ByDateAsc + // ByDateDesc sorts by descending dates (latest updated release first) + ByDateDesc +) + +// List is the action for listing releases. +// +// It provides, for example, the implementation of 'helm list'. +// It returns no more than one revision of every release in one specific, or in +// all, namespaces. +// To list all the revisions of a specific release, see the History action. +type List struct { + cfg *Configuration + + // All ignores the limit/offset + All bool + // AllNamespaces searches across namespaces + AllNamespaces bool + // Sort indicates the sort to use + // + // see pkg/releaseutil for several useful sorters + Sort Sorter + // Overrides the default lexicographic sorting + ByDate bool + SortReverse bool + // StateMask accepts a bitmask of states for items to show. + // The default is ListDeployed + StateMask ListStates + // Limit is the number of items to return per Run() + Limit int + // Offset is the starting index for the Run() call + Offset int + // Filter is a filter that is applied to the results + Filter string + Short bool + NoHeaders bool + TimeFormat string + Uninstalled bool + Superseded bool + Uninstalling bool + Deployed bool + Failed bool + Pending bool + Selector string +} + +// NewList constructs a new *List +func NewList(cfg *Configuration) *List { + return &List{ + StateMask: ListAll, + cfg: cfg, + } +} + +// Run executes the list command, returning a set of matches. +func (l *List) Run() ([]ri.Releaser, error) { + if err := l.cfg.KubeClient.IsReachable(); err != nil { + return nil, err + } + + var filter *regexp.Regexp + if l.Filter != "" { + var err error + filter, err = regexp.Compile(l.Filter) + if err != nil { + return nil, err + } + } + + results, err := l.cfg.Releases.List(func(rel ri.Releaser) bool { + r, err := releaserToV1Release(rel) + if err != nil { + return false + } + // Skip anything that doesn't match the filter. + if filter != nil && !filter.MatchString(r.Name) { + return false + } + + return true + }) + + if err != nil { + return nil, err + } + + if results == nil { + return results, nil + } + + rresults, err := releaseListToV1List(results) + if err != nil { + return nil, err + } + + // by definition, superseded releases are never shown if + // only the latest releases are returned. so if requested statemask + // is _only_ ListSuperseded, skip the latest release filter + if l.StateMask != ListSuperseded { + rresults = filterLatestReleases(rresults) + } + + // State mask application must occur after filtering to + // latest releases, otherwise outdated entries can be returned + rresults = l.filterStateMask(rresults) + + // Skip anything that doesn't match the selector + selectorObj, err := labels.Parse(l.Selector) + if err != nil { + return nil, err + } + rresults = l.filterSelector(rresults, selectorObj) + + // Unfortunately, we have to sort before truncating, which can incur substantial overhead + l.sort(rresults) + + // Guard on offset + if l.Offset >= len(rresults) { + return releaseV1ListToReleaserList([]*release.Release{}) + } + + // Calculate the limit and offset, and then truncate results if necessary. + limit := len(results) + if l.Limit > 0 && l.Limit < limit { + limit = l.Limit + } + last := l.Offset + limit + if l := len(rresults); l < last { + last = l + } + rresults = rresults[l.Offset:last] + + return releaseV1ListToReleaserList(rresults) +} + +// sort is an in-place sort where order is based on the value of a.Sort +func (l *List) sort(rels []*release.Release) { + if l.SortReverse { + l.Sort = ByNameDesc + } + + if l.ByDate { + l.Sort = ByDateDesc + if l.SortReverse { + l.Sort = ByDateAsc + } + } + + switch l.Sort { + case ByDateDesc: + releaseutil.SortByDate(rels) + case ByDateAsc: + releaseutil.Reverse(rels, releaseutil.SortByDate) + case ByNameDesc: + releaseutil.Reverse(rels, releaseutil.SortByName) + default: + releaseutil.SortByName(rels) + } +} + +// filterLatestReleases returns a list scrubbed of old releases. +func filterLatestReleases(releases []*release.Release) []*release.Release { + latestReleases := make(map[string]*release.Release) + + for _, rls := range releases { + name, namespace := rls.Name, rls.Namespace + key := path.Join(namespace, name) + if latestRelease, exists := latestReleases[key]; exists && latestRelease.Version > rls.Version { + continue + } + latestReleases[key] = rls + } + + var list = make([]*release.Release, 0, len(latestReleases)) + for _, rls := range latestReleases { + list = append(list, rls) + } + return list +} + +func (l *List) filterStateMask(releases []*release.Release) []*release.Release { + desiredStateReleases := make([]*release.Release, 0) + + for _, rls := range releases { + currentStatus := l.StateMask.FromName(rls.Info.Status.String()) + mask := l.StateMask & currentStatus + if mask == 0 { + continue + } + desiredStateReleases = append(desiredStateReleases, rls) + } + + return desiredStateReleases +} + +func (l *List) filterSelector(releases []*release.Release, selector labels.Selector) []*release.Release { + desiredStateReleases := make([]*release.Release, 0) + + for _, rls := range releases { + if selector.Matches(labels.Set(rls.Labels)) { + desiredStateReleases = append(desiredStateReleases, rls) + } + } + + return desiredStateReleases +} + +// SetStateMask calculates the state mask based on parameters. +func (l *List) SetStateMask() { + if l.All { + l.StateMask = ListAll + return + } + + state := ListStates(0) + if l.Deployed { + state |= ListDeployed + } + if l.Uninstalled { + state |= ListUninstalled + } + if l.Uninstalling { + state |= ListUninstalling + } + if l.Pending { + state |= ListPendingInstall | ListPendingRollback | ListPendingUpgrade + } + if l.Failed { + state |= ListFailed + } + if l.Superseded { + state |= ListSuperseded + } + + // Apply a default + if state == 0 { + state = ListAll + } + + l.StateMask = state +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/package.go b/vendor/helm.sh/helm/v4/pkg/action/package.go new file mode 100644 index 00000000000..0ab49538ce6 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/package.go @@ -0,0 +1,256 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "bufio" + "errors" + "fmt" + "os" + "path/filepath" + "syscall" + + "github.com/Masterminds/semver/v3" + "golang.org/x/term" + "sigs.k8s.io/yaml" + + ci "helm.sh/helm/v4/pkg/chart" + "helm.sh/helm/v4/pkg/chart/loader" + chart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/provenance" +) + +// Package is the action for packaging a chart. +// +// It provides the implementation of 'helm package'. +type Package struct { + Sign bool + Key string + Keyring string + PassphraseFile string + cachedPassphrase []byte + Version string + AppVersion string + Destination string + DependencyUpdate bool + + RepositoryConfig string + RepositoryCache string + PlainHTTP bool + Username string + Password string + CertFile string + KeyFile string + CaFile string + InsecureSkipTLSVerify bool +} + +const ( + passPhraseFileStdin = "-" +) + +// NewPackage creates a new Package object with the given configuration. +func NewPackage() *Package { + return &Package{} +} + +// Run executes 'helm package' against the given chart and returns the path to the packaged chart. +func (p *Package) Run(path string, _ map[string]interface{}) (string, error) { + chrt, err := loader.LoadDir(path) + if err != nil { + return "", err + } + var ch *chart.Chart + switch c := chrt.(type) { + case *chart.Chart: + ch = c + case chart.Chart: + ch = &c + default: + return "", errors.New("invalid chart apiVersion") + } + + ac, err := ci.NewAccessor(ch) + if err != nil { + return "", err + } + + // If version is set, modify the version. + if p.Version != "" { + ch.Metadata.Version = p.Version + } + + if err := validateVersion(ch.Metadata.Version); err != nil { + return "", err + } + + if p.AppVersion != "" { + ch.Metadata.AppVersion = p.AppVersion + } + + if reqs := ac.MetaDependencies(); len(reqs) > 0 { + if err := CheckDependencies(ch, reqs); err != nil { + return "", err + } + } + + var dest string + if p.Destination == "." { + // Save to the current working directory. + dest, err = os.Getwd() + if err != nil { + return "", err + } + } else { + // Otherwise save to set destination + dest = p.Destination + } + + name, err := chartutil.Save(ch, dest) + if err != nil { + return "", fmt.Errorf("failed to save: %w", err) + } + + if p.Sign { + err = p.Clearsign(name) + } + + return name, err +} + +// validateVersion Verify that version is a Version, and error out if it is not. +func validateVersion(ver string) error { + if _, err := semver.NewVersion(ver); err != nil { + return err + } + return nil +} + +// Clearsign signs a chart +func (p *Package) Clearsign(filename string) error { + // Load keyring + signer, err := provenance.NewFromKeyring(p.Keyring, p.Key) + if err != nil { + return err + } + + passphraseFetcher := promptUser + if p.PassphraseFile != "" { + passphraseFetcher, err = p.passphraseFileFetcher(p.PassphraseFile, os.Stdin) + if err != nil { + return err + } + } + + if err := signer.DecryptKey(passphraseFetcher); err != nil { + return err + } + + // Load the chart archive to extract metadata + chrt, err := loader.LoadFile(filename) + if err != nil { + return fmt.Errorf("failed to load chart for signing: %w", err) + } + var ch *chart.Chart + switch c := chrt.(type) { + case *chart.Chart: + ch = c + case chart.Chart: + ch = &c + default: + return errors.New("invalid chart apiVersion") + } + + // Marshal chart metadata to YAML bytes + metadataBytes, err := yaml.Marshal(ch.Metadata) + if err != nil { + return fmt.Errorf("failed to marshal chart metadata: %w", err) + } + + // Read the chart archive file + archiveData, err := os.ReadFile(filename) + if err != nil { + return fmt.Errorf("failed to read chart archive: %w", err) + } + + // Use the generic provenance signing function + sig, err := signer.ClearSign(archiveData, filepath.Base(filename), metadataBytes) + if err != nil { + return err + } + + return os.WriteFile(filename+".prov", []byte(sig), 0644) +} + +// promptUser implements provenance.PassphraseFetcher +func promptUser(name string) ([]byte, error) { + fmt.Printf("Password for key %q > ", name) + // syscall.Stdin is not an int in all environments and needs to be coerced + // into one there (e.g., Windows) + pw, err := term.ReadPassword(int(syscall.Stdin)) + fmt.Println() + return pw, err +} + +func (p *Package) passphraseFileFetcher(passphraseFile string, stdin *os.File) (provenance.PassphraseFetcher, error) { + // When reading from stdin we cache the passphrase here. If we are + // packaging multiple charts, we reuse the cached passphrase. This + // allows giving the passphrase once on stdin without failing with + // complaints about stdin already being closed. + // + // An alternative to this would be to omit file.Close() for stdin + // below and require the user to provide the same passphrase once + // per chart on stdin, but that does not seem very user-friendly. + + if p.cachedPassphrase == nil { + file, err := openPassphraseFile(passphraseFile, stdin) + if err != nil { + return nil, err + } + defer file.Close() + + reader := bufio.NewReader(file) + passphrase, _, err := reader.ReadLine() + if err != nil { + return nil, err + } + p.cachedPassphrase = passphrase + + return func(_ string) ([]byte, error) { + return passphrase, nil + }, nil + } + + return func(_ string) ([]byte, error) { + return p.cachedPassphrase, nil + }, nil +} + +func openPassphraseFile(passphraseFile string, stdin *os.File) (*os.File, error) { + if passphraseFile == passPhraseFileStdin { + stat, err := stdin.Stat() + if err != nil { + return nil, err + } + if (stat.Mode() & os.ModeNamedPipe) == 0 { + return nil, errors.New("specified reading passphrase from stdin, without input on stdin") + } + return stdin, nil + } + return os.Open(passphraseFile) +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/pull.go b/vendor/helm.sh/helm/v4/pkg/action/pull.go new file mode 100644 index 00000000000..dd051167bd2 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/pull.go @@ -0,0 +1,175 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/downloader" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/registry" + "helm.sh/helm/v4/pkg/repo/v1" +) + +// Pull is the action for checking a given release's information. +// +// It provides the implementation of 'helm pull'. +type Pull struct { + ChartPathOptions + + Settings *cli.EnvSettings // TODO: refactor this out of pkg/action + + Devel bool + Untar bool + VerifyLater bool + UntarDir string + DestDir string + cfg *Configuration +} + +type PullOpt func(*Pull) + +func WithConfig(cfg *Configuration) PullOpt { + return func(p *Pull) { + p.cfg = cfg + } +} + +// NewPull creates a new Pull with configuration options. +func NewPull(opts ...PullOpt) *Pull { + p := &Pull{} + for _, fn := range opts { + fn(p) + } + + return p +} + +// SetRegistryClient sets the registry client on the pull configuration object. +func (p *Pull) SetRegistryClient(client *registry.Client) { + p.cfg.RegistryClient = client +} + +// Run executes 'helm pull' against the given release. +func (p *Pull) Run(chartRef string) (string, error) { + var out strings.Builder + + c := downloader.ChartDownloader{ + Out: &out, + Keyring: p.Keyring, + Verify: downloader.VerifyNever, + Getters: getter.All(p.Settings), + Options: []getter.Option{ + getter.WithBasicAuth(p.Username, p.Password), + getter.WithPassCredentialsAll(p.PassCredentialsAll), + getter.WithTLSClientConfig(p.CertFile, p.KeyFile, p.CaFile), + getter.WithInsecureSkipVerifyTLS(p.InsecureSkipTLSVerify), + getter.WithPlainHTTP(p.PlainHTTP), + }, + RegistryClient: p.cfg.RegistryClient, + RepositoryConfig: p.Settings.RepositoryConfig, + RepositoryCache: p.Settings.RepositoryCache, + ContentCache: p.Settings.ContentCache, + } + + if registry.IsOCI(chartRef) { + c.Options = append(c.Options, + getter.WithRegistryClient(p.cfg.RegistryClient)) + c.RegistryClient = p.cfg.RegistryClient + } + + if p.Verify { + c.Verify = downloader.VerifyAlways + } else if p.VerifyLater { + c.Verify = downloader.VerifyLater + } + + // If untar is set, we fetch to a tempdir, then untar and copy after + // verification. + dest := p.DestDir + if p.Untar { + var err error + dest, err = os.MkdirTemp("", "helm-") + if err != nil { + return out.String(), fmt.Errorf("failed to untar: %w", err) + } + defer os.RemoveAll(dest) + } + + downloadSourceRef := chartRef + if p.RepoURL != "" { + chartURL, err := repo.FindChartInRepoURL( + p.RepoURL, + chartRef, + getter.All(p.Settings), + repo.WithChartVersion(p.Version), + repo.WithClientTLS(p.CertFile, p.KeyFile, p.CaFile), + repo.WithUsernamePassword(p.Username, p.Password), + repo.WithInsecureSkipTLSVerify(p.InsecureSkipTLSVerify), + repo.WithPassCredentialsAll(p.PassCredentialsAll), + ) + if err != nil { + return out.String(), err + } + downloadSourceRef = chartURL + } + + saved, v, err := c.DownloadTo(downloadSourceRef, p.Version, dest) + if err != nil { + return out.String(), err + } + + if p.Verify { + for name := range v.SignedBy.Identities { + fmt.Fprintf(&out, "Signed by: %v\n", name) + } + fmt.Fprintf(&out, "Using Key With Fingerprint: %X\n", v.SignedBy.PrimaryKey.Fingerprint) + fmt.Fprintf(&out, "Chart Hash Verified: %s\n", v.FileHash) + } + + // After verification, untar the chart into the requested directory. + if p.Untar { + ud := p.UntarDir + if !filepath.IsAbs(ud) { + ud = filepath.Join(p.DestDir, ud) + } + // Let udCheck to check conflict file/dir without replacing ud when untarDir is the current directory(.). + udCheck := ud + if udCheck == "." { + _, udCheck = filepath.Split(chartRef) + } else { + _, chartName := filepath.Split(chartRef) + udCheck = filepath.Join(udCheck, chartName) + } + + if _, err := os.Stat(udCheck); err != nil { + if err := os.MkdirAll(udCheck, 0755); err != nil { + return out.String(), fmt.Errorf("failed to untar (mkdir): %w", err) + } + } else { + return out.String(), fmt.Errorf("failed to untar: a file or directory with the name %s already exists", udCheck) + } + + return out.String(), chartutil.ExpandFile(ud, saved) + } + return out.String(), nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/push.go b/vendor/helm.sh/helm/v4/pkg/action/push.go new file mode 100644 index 00000000000..0c7148f656a --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/push.go @@ -0,0 +1,112 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "io" + "strings" + + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/pusher" + "helm.sh/helm/v4/pkg/registry" + "helm.sh/helm/v4/pkg/uploader" +) + +// Push is the action for uploading a chart. +// +// It provides the implementation of 'helm push'. +type Push struct { + Settings *cli.EnvSettings + cfg *Configuration + certFile string + keyFile string + caFile string + insecureSkipTLSVerify bool + plainHTTP bool + out io.Writer +} + +// PushOpt is a type of function that sets options for a push action. +type PushOpt func(*Push) + +// WithPushConfig sets the cfg field on the push configuration object. +func WithPushConfig(cfg *Configuration) PushOpt { + return func(p *Push) { + p.cfg = cfg + } +} + +// WithTLSClientConfig sets the certFile, keyFile, and caFile fields on the push configuration object. +func WithTLSClientConfig(certFile, keyFile, caFile string) PushOpt { + return func(p *Push) { + p.certFile = certFile + p.keyFile = keyFile + p.caFile = caFile + } +} + +// WithInsecureSkipTLSVerify determines if a TLS Certificate will be checked +func WithInsecureSkipTLSVerify(insecureSkipTLSVerify bool) PushOpt { + return func(p *Push) { + p.insecureSkipTLSVerify = insecureSkipTLSVerify + } +} + +// WithPlainHTTP configures the use of plain HTTP connections. +func WithPlainHTTP(plainHTTP bool) PushOpt { + return func(p *Push) { + p.plainHTTP = plainHTTP + } +} + +// WithPushOptWriter sets the registryOut field on the push configuration object. +func WithPushOptWriter(out io.Writer) PushOpt { + return func(p *Push) { + p.out = out + } +} + +// NewPushWithOpts creates a new push, with configuration options. +func NewPushWithOpts(opts ...PushOpt) *Push { + p := &Push{} + for _, fn := range opts { + fn(p) + } + return p +} + +// Run executes 'helm push' against the given chart archive. +func (p *Push) Run(chartRef string, remote string) (string, error) { + var out strings.Builder + + c := uploader.ChartUploader{ + Out: &out, + Pushers: pusher.All(p.Settings), + Options: []pusher.Option{ + pusher.WithTLSClientConfig(p.certFile, p.keyFile, p.caFile), + pusher.WithInsecureSkipTLSVerify(p.insecureSkipTLSVerify), + pusher.WithPlainHTTP(p.plainHTTP), + }, + } + + if registry.IsOCI(remote) { + // Don't use the default registry client if tls options are set. + c.Options = append(c.Options, pusher.WithRegistryClient(p.cfg.RegistryClient)) + } + + return out.String(), c.UploadTo(chartRef, remote) +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/registry_login.go b/vendor/helm.sh/helm/v4/pkg/action/registry_login.go new file mode 100644 index 00000000000..fd9d4bfc615 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/registry_login.go @@ -0,0 +1,99 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "io" + + "helm.sh/helm/v4/pkg/registry" +) + +// RegistryLogin performs a registry login operation. +type RegistryLogin struct { + cfg *Configuration + certFile string + keyFile string + caFile string + insecure bool + plainHTTP bool +} + +type RegistryLoginOpt func(*RegistryLogin) error + +// WithCertFile specifies the path to the certificate file to use for TLS. +func WithCertFile(certFile string) RegistryLoginOpt { + return func(r *RegistryLogin) error { + r.certFile = certFile + return nil + } +} + +// WithInsecure specifies whether to verify certificates. +func WithInsecure(insecure bool) RegistryLoginOpt { + return func(r *RegistryLogin) error { + r.insecure = insecure + return nil + } +} + +// WithKeyFile specifies the path to the key file to use for TLS. +func WithKeyFile(keyFile string) RegistryLoginOpt { + return func(r *RegistryLogin) error { + r.keyFile = keyFile + return nil + } +} + +// WithCAFile specifies the path to the CA file to use for TLS. +func WithCAFile(caFile string) RegistryLoginOpt { + return func(r *RegistryLogin) error { + r.caFile = caFile + return nil + } +} + +// WithPlainHTTPLogin use http rather than https for login. +func WithPlainHTTPLogin(isPlain bool) RegistryLoginOpt { + return func(r *RegistryLogin) error { + r.plainHTTP = isPlain + return nil + } +} + +// NewRegistryLogin creates a new RegistryLogin object with the given configuration. +func NewRegistryLogin(cfg *Configuration) *RegistryLogin { + return &RegistryLogin{ + cfg: cfg, + } +} + +// Run executes the registry login operation +func (a *RegistryLogin) Run(_ io.Writer, hostname string, username string, password string, opts ...RegistryLoginOpt) error { + for _, opt := range opts { + if err := opt(a); err != nil { + return err + } + } + + return a.cfg.RegistryClient.Login( + hostname, + registry.LoginOptBasicAuth(username, password), + registry.LoginOptInsecure(a.insecure), + registry.LoginOptTLSClientConfig(a.certFile, a.keyFile, a.caFile), + registry.LoginOptPlainText(a.plainHTTP), + ) +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/registry_logout.go b/vendor/helm.sh/helm/v4/pkg/action/registry_logout.go new file mode 100644 index 00000000000..7ce92defcfc --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/registry_logout.go @@ -0,0 +1,38 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "io" +) + +// RegistryLogout performs a registry login operation. +type RegistryLogout struct { + cfg *Configuration +} + +// NewRegistryLogout creates a new RegistryLogout object with the given configuration. +func NewRegistryLogout(cfg *Configuration) *RegistryLogout { + return &RegistryLogout{ + cfg: cfg, + } +} + +// Run executes the registry logout operation +func (a *RegistryLogout) Run(_ io.Writer, hostname string) error { + return a.cfg.RegistryClient.Logout(hostname) +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/release_testing.go b/vendor/helm.sh/helm/v4/pkg/action/release_testing.go new file mode 100644 index 00000000000..043a412360b --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/release_testing.go @@ -0,0 +1,154 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "context" + "fmt" + "io" + "slices" + "sort" + "time" + + v1 "k8s.io/api/core/v1" + + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/kube" + ri "helm.sh/helm/v4/pkg/release" + release "helm.sh/helm/v4/pkg/release/v1" +) + +const ( + ExcludeNameFilter = "!name" + IncludeNameFilter = "name" +) + +// ReleaseTesting is the action for testing a release. +// +// It provides the implementation of 'helm test'. +type ReleaseTesting struct { + cfg *Configuration + Timeout time.Duration + WaitOptions []kube.WaitOption + // Used for fetching logs from test pods + Namespace string + Filters map[string][]string +} + +// NewReleaseTesting creates a new ReleaseTesting object with the given configuration. +func NewReleaseTesting(cfg *Configuration) *ReleaseTesting { + return &ReleaseTesting{ + cfg: cfg, + Filters: map[string][]string{}, + } +} + +// Run executes 'helm test' against the given release. +func (r *ReleaseTesting) Run(name string) (ri.Releaser, ExecuteShutdownFunc, error) { + if err := r.cfg.KubeClient.IsReachable(); err != nil { + return nil, shutdownNoOp, err + } + + if err := chartutil.ValidateReleaseName(name); err != nil { + return nil, shutdownNoOp, fmt.Errorf("releaseTest: Release name is invalid: %s", name) + } + + // finds the non-deleted release with the given name + reli, err := r.cfg.Releases.Last(name) + if err != nil { + return reli, shutdownNoOp, err + } + + rel, err := releaserToV1Release(reli) + if err != nil { + return reli, shutdownNoOp, err + } + + skippedHooks := []*release.Hook{} + executingHooks := []*release.Hook{} + if len(r.Filters[ExcludeNameFilter]) != 0 { + for _, h := range rel.Hooks { + if slices.Contains(r.Filters[ExcludeNameFilter], h.Name) { + skippedHooks = append(skippedHooks, h) + } else { + executingHooks = append(executingHooks, h) + } + } + rel.Hooks = executingHooks + } + if len(r.Filters[IncludeNameFilter]) != 0 { + executingHooks = nil + for _, h := range rel.Hooks { + if slices.Contains(r.Filters[IncludeNameFilter], h.Name) { + executingHooks = append(executingHooks, h) + } else { + skippedHooks = append(skippedHooks, h) + } + } + rel.Hooks = executingHooks + } + + serverSideApply := rel.ApplyMethod == string(release.ApplyMethodServerSideApply) + shutdown, err := r.cfg.execHookWithDelayedShutdown(rel, release.HookTest, kube.StatusWatcherStrategy, r.WaitOptions, r.Timeout, serverSideApply) + + if err != nil { + rel.Hooks = append(skippedHooks, rel.Hooks...) + r.cfg.Releases.Update(reli) + return reli, shutdown, err + } + + rel.Hooks = append(skippedHooks, rel.Hooks...) + return reli, shutdown, r.cfg.Releases.Update(reli) +} + +// GetPodLogs will write the logs for all test pods in the given release into +// the given writer. These can be immediately output to the user or captured for +// other uses +func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error { + client, err := r.cfg.KubernetesClientSet() + if err != nil { + return fmt.Errorf("unable to get kubernetes client to fetch pod logs: %w", err) + } + + hooksByWight := append([]*release.Hook{}, rel.Hooks...) + sort.Stable(hookByWeight(hooksByWight)) + for _, h := range hooksByWight { + for _, e := range h.Events { + if e == release.HookTest { + if slices.Contains(r.Filters[ExcludeNameFilter], h.Name) { + continue + } + if len(r.Filters[IncludeNameFilter]) > 0 && !slices.Contains(r.Filters[IncludeNameFilter], h.Name) { + continue + } + req := client.CoreV1().Pods(r.Namespace).GetLogs(h.Name, &v1.PodLogOptions{}) + logReader, err := req.Stream(context.Background()) + if err != nil { + return fmt.Errorf("unable to get pod logs for %s: %w", h.Name, err) + } + + fmt.Fprintf(out, "POD LOGS: %s\n", h.Name) + _, err = io.Copy(out, logReader) + fmt.Fprintln(out) + if err != nil { + return fmt.Errorf("unable to write pod logs for %s: %w", h.Name, err) + } + } + } + } + return nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/resource_policy.go b/vendor/helm.sh/helm/v4/pkg/action/resource_policy.go new file mode 100644 index 00000000000..fcea98ad6b4 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/resource_policy.go @@ -0,0 +1,46 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "strings" + + "helm.sh/helm/v4/pkg/kube" + releaseutil "helm.sh/helm/v4/pkg/release/v1/util" +) + +func filterManifestsToKeep(manifests []releaseutil.Manifest) (keep, remaining []releaseutil.Manifest) { + for _, m := range manifests { + if m.Head.Metadata == nil || m.Head.Metadata.Annotations == nil || len(m.Head.Metadata.Annotations) == 0 { + remaining = append(remaining, m) + continue + } + + resourcePolicyType, ok := m.Head.Metadata.Annotations[kube.ResourcePolicyAnno] + if !ok { + remaining = append(remaining, m) + continue + } + + resourcePolicyType = strings.ToLower(strings.TrimSpace(resourcePolicyType)) + if resourcePolicyType == kube.KeepPolicy { + keep = append(keep, m) + } + + } + return keep, remaining +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/rollback.go b/vendor/helm.sh/helm/v4/pkg/action/rollback.go new file mode 100644 index 00000000000..459569781f7 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/rollback.go @@ -0,0 +1,306 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "bytes" + "errors" + "fmt" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/kube" + "helm.sh/helm/v4/pkg/release/common" + release "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage/driver" +) + +// Rollback is the action for rolling back to a given release. +// +// It provides the implementation of 'helm rollback'. +type Rollback struct { + cfg *Configuration + + Version int + Timeout time.Duration + WaitStrategy kube.WaitStrategy + WaitOptions []kube.WaitOption + WaitForJobs bool + DisableHooks bool + // DryRunStrategy can be set to prepare, but not execute the operation and whether or not to interact with the remote cluster + DryRunStrategy DryRunStrategy + // ForceReplace will, if set to `true`, ignore certain warnings and perform the rollback anyway. + // + // This should be used with caution. + ForceReplace bool + // ForceConflicts causes server-side apply to force conflicts ("Overwrite value, become sole manager") + // see: https://kubernetes.io/docs/reference/using-api/server-side-apply/#conflicts + ForceConflicts bool + // ServerSideApply enables changes to be applied via Kubernetes server-side apply + // Can be the string: "true", "false" or "auto" + // When "auto", sever-side usage will be based upon the releases previous usage + // see: https://kubernetes.io/docs/reference/using-api/server-side-apply/ + ServerSideApply string + CleanupOnFail bool + MaxHistory int // MaxHistory limits the maximum number of revisions saved per release +} + +// NewRollback creates a new Rollback object with the given configuration. +func NewRollback(cfg *Configuration) *Rollback { + return &Rollback{ + cfg: cfg, + ServerSideApply: "auto", // Must always match the CLI default. + DryRunStrategy: DryRunNone, + } +} + +// Run executes 'helm rollback' against the given release. +func (r *Rollback) Run(name string) error { + if err := r.cfg.KubeClient.IsReachable(); err != nil { + return err + } + + r.cfg.Releases.MaxHistory = r.MaxHistory + + r.cfg.Logger().Debug("preparing rollback", "name", name) + currentRelease, targetRelease, serverSideApply, err := r.prepareRollback(name) + if err != nil { + return err + } + + if !isDryRun(r.DryRunStrategy) { + r.cfg.Logger().Debug("creating rolled back release", "name", name) + if err := r.cfg.Releases.Create(targetRelease); err != nil { + return err + } + } + + r.cfg.Logger().Debug("performing rollback", "name", name) + if _, err := r.performRollback(currentRelease, targetRelease, serverSideApply); err != nil { + return err + } + + if !isDryRun(r.DryRunStrategy) { + r.cfg.Logger().Debug("updating status for rolled back release", "name", name) + if err := r.cfg.Releases.Update(targetRelease); err != nil { + return err + } + } + return nil +} + +// prepareRollback finds the previous release and prepares a new release object with +// the previous release's configuration +func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Release, bool, error) { + if err := chartutil.ValidateReleaseName(name); err != nil { + return nil, nil, false, fmt.Errorf("prepareRollback: Release name is invalid: %s", name) + } + + if r.Version < 0 { + return nil, nil, false, errInvalidRevision + } + + currentReleasei, err := r.cfg.Releases.Last(name) + if err != nil { + return nil, nil, false, err + } + + currentRelease, err := releaserToV1Release(currentReleasei) + if err != nil { + return nil, nil, false, err + } + + previousVersion := r.Version + if r.Version == 0 { + previousVersion = currentRelease.Version - 1 + } + + historyReleases, err := r.cfg.Releases.History(name) + if err != nil { + return nil, nil, false, err + } + + // Check if the history version to be rolled back exists + previousVersionExist := false + for _, historyReleasei := range historyReleases { + historyRelease, err := releaserToV1Release(historyReleasei) + if err != nil { + return nil, nil, false, err + } + version := historyRelease.Version + if previousVersion == version { + previousVersionExist = true + break + } + } + if !previousVersionExist { + return nil, nil, false, fmt.Errorf("release has no %d version", previousVersion) + } + + r.cfg.Logger().Debug("rolling back", "name", name, "currentVersion", currentRelease.Version, "targetVersion", previousVersion) + + previousReleasei, err := r.cfg.Releases.Get(name, previousVersion) + if err != nil { + return nil, nil, false, err + } + previousRelease, err := releaserToV1Release(previousReleasei) + if err != nil { + return nil, nil, false, err + } + + serverSideApply, err := getUpgradeServerSideValue(r.ServerSideApply, previousRelease.ApplyMethod) + if err != nil { + return nil, nil, false, err + } + + // Store a new release object with previous release's configuration + targetRelease := &release.Release{ + Name: name, + Namespace: currentRelease.Namespace, + Chart: previousRelease.Chart, + Config: previousRelease.Config, + Info: &release.Info{ + FirstDeployed: currentRelease.Info.FirstDeployed, + LastDeployed: time.Now(), + Status: common.StatusPendingRollback, + Notes: previousRelease.Info.Notes, + // Because we lose the reference to previous version elsewhere, we set the + // message here, and only override it later if we experience failure. + Description: fmt.Sprintf("Rollback to %d", previousVersion), + }, + Version: currentRelease.Version + 1, + Labels: previousRelease.Labels, + Manifest: previousRelease.Manifest, + Hooks: previousRelease.Hooks, + ApplyMethod: string(determineReleaseSSApplyMethod(serverSideApply)), + } + + return currentRelease, targetRelease, serverSideApply, nil +} + +func (r *Rollback) performRollback(currentRelease, targetRelease *release.Release, serverSideApply bool) (*release.Release, error) { + if isDryRun(r.DryRunStrategy) { + r.cfg.Logger().Debug("dry run", "name", targetRelease.Name) + return targetRelease, nil + } + + current, err := r.cfg.KubeClient.Build(bytes.NewBufferString(currentRelease.Manifest), false) + if err != nil { + return targetRelease, fmt.Errorf("unable to build kubernetes objects from current release manifest: %w", err) + } + target, err := r.cfg.KubeClient.Build(bytes.NewBufferString(targetRelease.Manifest), false) + if err != nil { + return targetRelease, fmt.Errorf("unable to build kubernetes objects from new release manifest: %w", err) + } + + // pre-rollback hooks + + if !r.DisableHooks { + if err := r.cfg.execHook(targetRelease, release.HookPreRollback, r.WaitStrategy, r.WaitOptions, r.Timeout, serverSideApply); err != nil { + return targetRelease, err + } + } else { + r.cfg.Logger().Debug("rollback hooks disabled", "name", targetRelease.Name) + } + + // It is safe to use "forceOwnership" here because these are resources currently rendered by the chart. + err = target.Visit(setMetadataVisitor(targetRelease.Name, targetRelease.Namespace, true)) + if err != nil { + return targetRelease, fmt.Errorf("unable to set metadata visitor from target release: %w", err) + } + results, err := r.cfg.KubeClient.Update( + current, + target, + kube.ClientUpdateOptionForceReplace(r.ForceReplace), + kube.ClientUpdateOptionServerSideApply(serverSideApply, r.ForceConflicts), + kube.ClientUpdateOptionThreeWayMergeForUnstructured(false), + kube.ClientUpdateOptionUpgradeClientSideFieldManager(true)) + + if err != nil { + msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) + r.cfg.Logger().Warn(msg) + currentRelease.Info.Status = common.StatusSuperseded + targetRelease.Info.Status = common.StatusFailed + targetRelease.Info.Description = msg + r.cfg.recordRelease(currentRelease) + r.cfg.recordRelease(targetRelease) + if r.CleanupOnFail { + r.cfg.Logger().Debug("cleanup on fail set, cleaning up resources", "count", len(results.Created)) + _, errs := r.cfg.KubeClient.Delete(results.Created, metav1.DeletePropagationBackground) + if errs != nil { + return targetRelease, fmt.Errorf( + "an error occurred while cleaning up resources. original rollback error: %w", + fmt.Errorf("unable to cleanup resources: %w", joinErrors(errs, ", "))) + } + r.cfg.Logger().Debug("resource cleanup complete") + } + return targetRelease, err + } + + var waiter kube.Waiter + if c, supportsOptions := r.cfg.KubeClient.(kube.InterfaceWaitOptions); supportsOptions { + waiter, err = c.GetWaiterWithOptions(r.WaitStrategy, r.WaitOptions...) + } else { + waiter, err = r.cfg.KubeClient.GetWaiter(r.WaitStrategy) + } + if err != nil { + return nil, fmt.Errorf("unable to get waiter: %w", err) + } + if r.WaitForJobs { + if err := waiter.WaitWithJobs(target, r.Timeout); err != nil { + targetRelease.SetStatus(common.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error())) + r.cfg.recordRelease(currentRelease) + r.cfg.recordRelease(targetRelease) + return targetRelease, fmt.Errorf("release %s failed: %w", targetRelease.Name, err) + } + } else { + if err := waiter.Wait(target, r.Timeout); err != nil { + targetRelease.SetStatus(common.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error())) + r.cfg.recordRelease(currentRelease) + r.cfg.recordRelease(targetRelease) + return targetRelease, fmt.Errorf("release %s failed: %w", targetRelease.Name, err) + } + } + + // post-rollback hooks + if !r.DisableHooks { + if err := r.cfg.execHook(targetRelease, release.HookPostRollback, r.WaitStrategy, r.WaitOptions, r.Timeout, serverSideApply); err != nil { + return targetRelease, err + } + } + + deployed, err := r.cfg.Releases.DeployedAll(currentRelease.Name) + if err != nil && !errors.Is(err, driver.ErrNoDeployedReleases) { + return nil, err + } + // Supersede all previous deployments, see issue #2941. + for _, reli := range deployed { + rel, err := releaserToV1Release(reli) + if err != nil { + return nil, err + } + r.cfg.Logger().Debug("superseding previous deployment", "version", rel.Version) + rel.Info.Status = common.StatusSuperseded + r.cfg.recordRelease(rel) + } + + targetRelease.Info.Status = common.StatusDeployed + + return targetRelease, nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/show.go b/vendor/helm.sh/helm/v4/pkg/action/show.go new file mode 100644 index 00000000000..4195d69a5ff --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/show.go @@ -0,0 +1,156 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "bytes" + "fmt" + "strings" + + "k8s.io/cli-runtime/pkg/printers" + "sigs.k8s.io/yaml" + + "helm.sh/helm/v4/pkg/chart/common" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/registry" +) + +// ShowOutputFormat is the format of the output of `helm show` +type ShowOutputFormat string + +const ( + // ShowAll is the format which shows all the information of a chart + ShowAll ShowOutputFormat = "all" + // ShowChart is the format which only shows the chart's definition + ShowChart ShowOutputFormat = "chart" + // ShowValues is the format which only shows the chart's values + ShowValues ShowOutputFormat = "values" + // ShowReadme is the format which only shows the chart's README + ShowReadme ShowOutputFormat = "readme" + // ShowCRDs is the format which only shows the chart's CRDs + ShowCRDs ShowOutputFormat = "crds" +) + +var readmeFileNames = []string{"readme.md", "readme.txt", "readme"} + +func (o ShowOutputFormat) String() string { + return string(o) +} + +// Show is the action for checking a given release's information. +// +// It provides the implementation of 'helm show' and its respective subcommands. +type Show struct { + ChartPathOptions + Devel bool + OutputFormat ShowOutputFormat + JSONPathTemplate string + chart *chart.Chart // for testing +} + +// NewShow creates a new Show object with the given configuration. +func NewShow(output ShowOutputFormat, cfg *Configuration) *Show { + sh := &Show{ + OutputFormat: output, + } + sh.registryClient = cfg.RegistryClient + + return sh +} + +// SetRegistryClient sets the registry client to use when pulling a chart from a registry. +func (s *Show) SetRegistryClient(client *registry.Client) { + s.registryClient = client +} + +// Run executes 'helm show' against the given release. +func (s *Show) Run(chartpath string) (string, error) { + if s.chart == nil { + chrt, err := loader.Load(chartpath) + if err != nil { + return "", err + } + s.chart = chrt + } + cf, err := yaml.Marshal(s.chart.Metadata) + if err != nil { + return "", err + } + + var out strings.Builder + if s.OutputFormat == ShowChart || s.OutputFormat == ShowAll { + fmt.Fprintf(&out, "%s\n", cf) + } + + if (s.OutputFormat == ShowValues || s.OutputFormat == ShowAll) && s.chart.Values != nil { + if s.OutputFormat == ShowAll { + fmt.Fprintln(&out, "---") + } + if s.JSONPathTemplate != "" { + printer, err := printers.NewJSONPathPrinter(s.JSONPathTemplate) + if err != nil { + return "", fmt.Errorf("error parsing jsonpath %s: %w", s.JSONPathTemplate, err) + } + printer.Execute(&out, s.chart.Values) + } else { + for _, f := range s.chart.Raw { + if f.Name == chartutil.ValuesfileName { + fmt.Fprintln(&out, string(f.Data)) + } + } + } + } + + if s.OutputFormat == ShowReadme || s.OutputFormat == ShowAll { + readme := findReadme(s.chart.Files) + if readme != nil { + if s.OutputFormat == ShowAll { + fmt.Fprintln(&out, "---") + } + fmt.Fprintf(&out, "%s\n", readme.Data) + } + } + + if s.OutputFormat == ShowCRDs || s.OutputFormat == ShowAll { + crds := s.chart.CRDObjects() + if len(crds) > 0 { + for _, crd := range crds { + if !bytes.HasPrefix(crd.File.Data, []byte("---")) { + fmt.Fprintln(&out, "---") + } + fmt.Fprintf(&out, "%s\n", string(crd.File.Data)) + } + } + } + return out.String(), nil +} + +func findReadme(files []*common.File) (file *common.File) { + for _, file := range files { + for _, n := range readmeFileNames { + if file == nil { + continue + } + if strings.EqualFold(file.Name, n) { + return file + } + } + } + return nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/status.go b/vendor/helm.sh/helm/v4/pkg/action/status.go new file mode 100644 index 00000000000..2e6a1992ca9 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/status.go @@ -0,0 +1,83 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "bytes" + + "helm.sh/helm/v4/pkg/kube" + ri "helm.sh/helm/v4/pkg/release" +) + +// Status is the action for checking the deployment status of releases. +// +// It provides the implementation of 'helm status'. +type Status struct { + cfg *Configuration + + Version int + + // ShowResourcesTable is used with ShowResources. When true this will cause + // the resulting objects to be retrieved as a kind=table. + ShowResourcesTable bool +} + +// NewStatus creates a new Status object with the given configuration. +func NewStatus(cfg *Configuration) *Status { + return &Status{ + cfg: cfg, + } +} + +// Run executes 'helm status' against the given release. +func (s *Status) Run(name string) (ri.Releaser, error) { + if err := s.cfg.KubeClient.IsReachable(); err != nil { + return nil, err + } + + reli, err := s.cfg.releaseContent(name, s.Version) + if err != nil { + return nil, err + } + + rel, err := releaserToV1Release(reli) + if err != nil { + return nil, err + } + + var resources kube.ResourceList + if s.ShowResourcesTable { + resources, err = s.cfg.KubeClient.BuildTable(bytes.NewBufferString(rel.Manifest), false) + if err != nil { + return nil, err + } + } else { + resources, err = s.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), false) + if err != nil { + return nil, err + } + } + + resp, err := s.cfg.KubeClient.Get(resources, true) + if err != nil { + return nil, err + } + + rel.Info.Resources = resp + + return rel, nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/uninstall.go b/vendor/helm.sh/helm/v4/pkg/action/uninstall.go new file mode 100644 index 00000000000..79156991cc3 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/uninstall.go @@ -0,0 +1,303 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "errors" + "fmt" + "log/slog" + "strings" + "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/kube" + releasei "helm.sh/helm/v4/pkg/release" + "helm.sh/helm/v4/pkg/release/common" + release "helm.sh/helm/v4/pkg/release/v1" + releaseutil "helm.sh/helm/v4/pkg/release/v1/util" + "helm.sh/helm/v4/pkg/storage/driver" +) + +// Uninstall is the action for uninstalling releases. +// +// It provides the implementation of 'helm uninstall'. +type Uninstall struct { + cfg *Configuration + + DisableHooks bool + DryRun bool + IgnoreNotFound bool + KeepHistory bool + WaitStrategy kube.WaitStrategy + WaitOptions []kube.WaitOption + DeletionPropagation string + Timeout time.Duration + Description string +} + +// NewUninstall creates a new Uninstall object with the given configuration. +func NewUninstall(cfg *Configuration) *Uninstall { + return &Uninstall{ + cfg: cfg, + } +} + +// Run uninstalls the given release. +func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error) { + if err := u.cfg.KubeClient.IsReachable(); err != nil { + return nil, err + } + + var waiter kube.Waiter + var err error + if c, supportsOptions := u.cfg.KubeClient.(kube.InterfaceWaitOptions); supportsOptions { + waiter, err = c.GetWaiterWithOptions(u.WaitStrategy, u.WaitOptions...) + } else { + waiter, err = u.cfg.KubeClient.GetWaiter(u.WaitStrategy) + } + if err != nil { + return nil, err + } + + if u.DryRun { + ri, err := u.cfg.releaseContent(name, 0) + + if err != nil { + if u.IgnoreNotFound && errors.Is(err, driver.ErrReleaseNotFound) { + return nil, nil + } + return &releasei.UninstallReleaseResponse{}, err + } + r, err := releaserToV1Release(ri) + if err != nil { + return nil, err + } + return &releasei.UninstallReleaseResponse{Release: r}, nil + } + + if err := chartutil.ValidateReleaseName(name); err != nil { + return nil, fmt.Errorf("uninstall: Release name is invalid: %s", name) + } + + relsi, err := u.cfg.Releases.History(name) + if err != nil { + if u.IgnoreNotFound { + return nil, nil + } + return nil, fmt.Errorf("uninstall: Release not loaded: %s: %w", name, err) + } + if len(relsi) < 1 { + return nil, errMissingRelease + } + + rels, err := releaseListToV1List(relsi) + if err != nil { + return nil, err + } + + releaseutil.SortByRevision(rels) + rel := rels[len(rels)-1] + + // TODO: Are there any cases where we want to force a delete even if it's + // already marked deleted? + if rel.Info.Status == common.StatusUninstalled { + if !u.KeepHistory { + if err := u.purgeReleases(rels...); err != nil { + return nil, fmt.Errorf("uninstall: Failed to purge the release: %w", err) + } + return &releasei.UninstallReleaseResponse{Release: rel}, nil + } + return nil, fmt.Errorf("the release named %q is already deleted", name) + } + + u.cfg.Logger().Debug("uninstall: deleting release", "name", name) + rel.Info.Status = common.StatusUninstalling + rel.Info.Deleted = time.Now() + rel.Info.Description = "Deletion in progress (or silently failed)" + res := &releasei.UninstallReleaseResponse{Release: rel} + + if !u.DisableHooks { + serverSideApply := true + if err := u.cfg.execHook(rel, release.HookPreDelete, u.WaitStrategy, u.WaitOptions, u.Timeout, serverSideApply); err != nil { + return res, err + } + } else { + u.cfg.Logger().Debug("delete hooks disabled", "release", name) + } + + // From here on out, the release is currently considered to be in StatusUninstalling + // state. + if err := u.cfg.Releases.Update(rel); err != nil { + u.cfg.Logger().Debug("uninstall: Failed to store updated release", slog.Any("error", err)) + } + + deletedResources, kept, errs := u.deleteRelease(rel) + if errs != nil { + u.cfg.Logger().Debug("uninstall: Failed to delete release", slog.Any("error", errs)) + return nil, fmt.Errorf("failed to delete release: %s", name) + } + + if kept != "" { + kept = "These resources were kept due to the resource policy:\n" + kept + } + res.Info = kept + + if err := waiter.WaitForDelete(deletedResources, u.Timeout); err != nil { + errs = append(errs, err) + } + + if !u.DisableHooks { + serverSideApply := true + if err := u.cfg.execHook(rel, release.HookPostDelete, u.WaitStrategy, u.WaitOptions, u.Timeout, serverSideApply); err != nil { + errs = append(errs, err) + } + } + + rel.Info.Status = common.StatusUninstalled + if len(u.Description) > 0 { + rel.Info.Description = u.Description + } else { + rel.Info.Description = "Uninstallation complete" + } + + if !u.KeepHistory { + u.cfg.Logger().Debug("purge requested", "release", name) + err := u.purgeReleases(rels...) + if err != nil { + errs = append(errs, fmt.Errorf("uninstall: Failed to purge the release: %w", err)) + } + + // Return the errors that occurred while deleting the release, if any + if len(errs) > 0 { + return res, fmt.Errorf("uninstallation completed with %d error(s): %w", len(errs), joinErrors(errs, "; ")) + } + + return res, nil + } + + if err := u.cfg.Releases.Update(rel); err != nil { + u.cfg.Logger().Debug("uninstall: Failed to store updated release", slog.Any("error", err)) + } + + // Supersede all previous deployments, see issue #12556 (which is a + // variation on #2941). + deployed, err := u.cfg.Releases.DeployedAll(name) + if err != nil && !errors.Is(err, driver.ErrNoDeployedReleases) { + return nil, err + } + for _, reli := range deployed { + rel, err := releaserToV1Release(reli) + if err != nil { + return nil, err + } + + u.cfg.Logger().Debug("superseding previous deployment", "version", rel.Version) + rel.Info.Status = common.StatusSuperseded + if err := u.cfg.Releases.Update(rel); err != nil { + u.cfg.Logger().Debug("uninstall: Failed to store updated release", slog.Any("error", err)) + } + } + + if len(errs) > 0 { + return res, fmt.Errorf("uninstallation completed with %d error(s): %w", len(errs), joinErrors(errs, "; ")) + } + return res, nil +} + +func (u *Uninstall) purgeReleases(rels ...*release.Release) error { + for _, rel := range rels { + if _, err := u.cfg.Releases.Delete(rel.Name, rel.Version); err != nil { + return err + } + } + return nil +} + +type joinedErrors struct { + errs []error + sep string +} + +func joinErrors(errs []error, sep string) error { + return &joinedErrors{ + errs: errs, + sep: sep, + } +} + +func (e *joinedErrors) Error() string { + errs := make([]string, 0, len(e.errs)) + for _, err := range e.errs { + errs = append(errs, err.Error()) + } + return strings.Join(errs, e.sep) +} + +func (e *joinedErrors) Unwrap() []error { + return e.errs +} + +// deleteRelease deletes the release and returns list of delete resources and manifests that were kept in the deletion process +func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, string, []error) { + var errs []error + + manifests := releaseutil.SplitManifests(rel.Manifest) + _, files, err := releaseutil.SortManifests(manifests, nil, releaseutil.UninstallOrder) + if err != nil { + // We could instead just delete everything in no particular order. + // FIXME: One way to delete at this point would be to try a label-based + // deletion. The problem with this is that we could get a false positive + // and delete something that was not legitimately part of this release. + return nil, rel.Manifest, []error{fmt.Errorf("corrupted release record. You must manually delete the resources: %w", err)} + } + + filesToKeep, filesToDelete := filterManifestsToKeep(files) + var kept strings.Builder + for _, f := range filesToKeep { + fmt.Fprintf(&kept, "[%s] %s\n", f.Head.Kind, f.Head.Metadata.Name) + } + + var builder strings.Builder + for _, file := range filesToDelete { + builder.WriteString("\n---\n" + file.Content) + } + + resources, err := u.cfg.KubeClient.Build(strings.NewReader(builder.String()), false) + if err != nil { + return nil, "", []error{fmt.Errorf("unable to build kubernetes objects for delete: %w", err)} + } + if len(resources) > 0 { + _, errs = u.cfg.KubeClient.Delete(resources, parseCascadingFlag(u.DeletionPropagation)) + } + return resources, kept.String(), errs +} + +func parseCascadingFlag(cascadingFlag string) v1.DeletionPropagation { + switch cascadingFlag { + case "orphan": + return v1.DeletePropagationOrphan + case "foreground": + return v1.DeletePropagationForeground + case "background": + return v1.DeletePropagationBackground + default: + slog.Debug("uninstall: given cascade value, defaulting to delete propagation background", "value", cascadingFlag) + return v1.DeletePropagationBackground + } +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/upgrade.go b/vendor/helm.sh/helm/v4/pkg/action/upgrade.go new file mode 100644 index 00000000000..4c93855b108 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/upgrade.go @@ -0,0 +1,675 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "bytes" + "context" + "errors" + "fmt" + "log/slog" + "strings" + "sync" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/cli-runtime/pkg/resource" + + "helm.sh/helm/v4/pkg/chart" + "helm.sh/helm/v4/pkg/chart/common" + "helm.sh/helm/v4/pkg/chart/common/util" + chartv2 "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/kube" + "helm.sh/helm/v4/pkg/postrenderer" + "helm.sh/helm/v4/pkg/registry" + ri "helm.sh/helm/v4/pkg/release" + rcommon "helm.sh/helm/v4/pkg/release/common" + release "helm.sh/helm/v4/pkg/release/v1" + releaseutil "helm.sh/helm/v4/pkg/release/v1/util" + "helm.sh/helm/v4/pkg/storage/driver" +) + +// Upgrade is the action for upgrading releases. +// +// It provides the implementation of 'helm upgrade'. +type Upgrade struct { + cfg *Configuration + + ChartPathOptions + + // Install is a purely informative flag that indicates whether this upgrade was done in "install" mode. + // + // Applications may use this to determine whether this Upgrade operation was done as part of a + // pure upgrade (Upgrade.Install == false) or as part of an install-or-upgrade operation + // (Upgrade.Install == true). + // + // Setting this to `true` will NOT cause `Upgrade` to perform an install if the release does not exist. + // That process must be handled by creating an Install action directly. See cmd/upgrade.go for an + // example of how this flag is used. + Install bool + // Devel indicates that the operation is done in devel mode. + Devel bool + // Namespace is the namespace in which this operation should be performed. + Namespace string + // SkipCRDs skips installing CRDs when install flag is enabled during upgrade + SkipCRDs bool + // Timeout is the timeout for this operation + Timeout time.Duration + // WaitStrategy determines what type of waiting should be done + WaitStrategy kube.WaitStrategy + // WaitOptions are additional options for waiting on resources + WaitOptions []kube.WaitOption + // WaitForJobs determines whether the wait operation for the Jobs should be performed after the upgrade is requested. + WaitForJobs bool + // DisableHooks disables hook processing if set to true. + DisableHooks bool + // DryRunStrategy can be set to prepare, but not execute the operation and whether or not to interact with the remote cluster + DryRunStrategy DryRunStrategy + // HideSecret can be set to true when DryRun is enabled in order to hide + // Kubernetes Secrets in the output. It cannot be used outside of DryRun. + HideSecret bool + // ForceReplace will, if set to `true`, ignore certain warnings and perform the upgrade anyway. + // + // This should be used with caution. + ForceReplace bool + // ForceConflicts causes server-side apply to force conflicts ("Overwrite value, become sole manager") + // see: https://kubernetes.io/docs/reference/using-api/server-side-apply/#conflicts + ForceConflicts bool + // ServerSideApply enables changes to be applied via Kubernetes server-side apply + // Can be the string: "true", "false" or "auto" + // When "auto", sever-side usage will be based upon the releases previous usage + // see: https://kubernetes.io/docs/reference/using-api/server-side-apply/ + ServerSideApply string + // ResetValues will reset the values to the chart's built-ins rather than merging with existing. + ResetValues bool + // ReuseValues will reuse the user's last supplied values. + ReuseValues bool + // ResetThenReuseValues will reset the values to the chart's built-ins then merge with user's last supplied values. + ResetThenReuseValues bool + // MaxHistory limits the maximum number of revisions saved per release + MaxHistory int + // RollbackOnFailure enables rolling back the upgraded release on failure + RollbackOnFailure bool + // CleanupOnFail will, if true, cause the upgrade to delete newly-created resources on a failed update. + CleanupOnFail bool + // SubNotes determines whether sub-notes are rendered in the chart. + SubNotes bool + // HideNotes determines whether notes are output during upgrade + HideNotes bool + // SkipSchemaValidation determines if JSON schema validation is disabled. + SkipSchemaValidation bool + // Description is the description of this operation + Description string + Labels map[string]string + // PostRenderer is an optional post-renderer + // + // If this is non-nil, then after templates are rendered, they will be sent to the + // post renderer before sending to the Kubernetes API server. + PostRenderer postrenderer.PostRenderer + // DisableOpenAPIValidation controls whether OpenAPI validation is enforced. + DisableOpenAPIValidation bool + // Get missing dependencies + DependencyUpdate bool + // Lock to control raceconditions when the process receives a SIGTERM + Lock sync.Mutex + // Enable DNS lookups when rendering templates + EnableDNS bool + // TakeOwnership will skip the check for helm annotations and adopt all existing resources. + TakeOwnership bool +} + +type resultMessage struct { + r *release.Release + e error +} + +// NewUpgrade creates a new Upgrade object with the given configuration. +func NewUpgrade(cfg *Configuration) *Upgrade { + up := &Upgrade{ + cfg: cfg, + ServerSideApply: "auto", // Must always match the CLI default. + DryRunStrategy: DryRunNone, + } + up.registryClient = cfg.RegistryClient + + return up +} + +// SetRegistryClient sets the registry client to use when fetching charts. +func (u *Upgrade) SetRegistryClient(client *registry.Client) { + u.registryClient = client +} + +// Run executes the upgrade on the given release. +func (u *Upgrade) Run(name string, chart chart.Charter, vals map[string]interface{}) (ri.Releaser, error) { + ctx := context.Background() + return u.RunWithContext(ctx, name, chart, vals) +} + +// RunWithContext executes the upgrade on the given release with context. +func (u *Upgrade) RunWithContext(ctx context.Context, name string, ch chart.Charter, vals map[string]interface{}) (ri.Releaser, error) { + if err := u.cfg.KubeClient.IsReachable(); err != nil { + return nil, err + } + + var chrt *chartv2.Chart + switch c := ch.(type) { + case *chartv2.Chart: + chrt = c + case chartv2.Chart: + chrt = &c + default: + return nil, errors.New("invalid chart apiVersion") + } + + // Make sure wait is set if RollbackOnFailure. This makes it so + // the user doesn't have to specify both + if u.WaitStrategy == kube.HookOnlyStrategy && u.RollbackOnFailure { + u.WaitStrategy = kube.StatusWatcherStrategy + } + + if err := chartutil.ValidateReleaseName(name); err != nil { + return nil, fmt.Errorf("release name is invalid: %s", name) + } + + u.cfg.Logger().Debug("preparing upgrade", "name", name) + currentRelease, upgradedRelease, serverSideApply, err := u.prepareUpgrade(name, chrt, vals) + if err != nil { + return nil, err + } + + u.cfg.Releases.MaxHistory = u.MaxHistory + + u.cfg.Logger().Debug("performing update", "name", name) + res, err := u.performUpgrade(ctx, currentRelease, upgradedRelease, serverSideApply) + if err != nil { + return res, err + } + + // Do not update for dry runs + if !isDryRun(u.DryRunStrategy) { + u.cfg.Logger().Debug("updating status for upgraded release", "name", name) + if err := u.cfg.Releases.Update(upgradedRelease); err != nil { + return res, err + } + } + + return res, nil +} + +// prepareUpgrade builds an upgraded release for an upgrade operation. +func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[string]interface{}) (*release.Release, *release.Release, bool, error) { + if chart == nil { + return nil, nil, false, errMissingChart + } + + // HideSecret must be used with dry run. Otherwise, return an error. + if !isDryRun(u.DryRunStrategy) && u.HideSecret { + return nil, nil, false, errors.New("hiding Kubernetes secrets requires a dry-run mode") + } + + // finds the last non-deleted release with the given name + lastReleasei, err := u.cfg.Releases.Last(name) + if err != nil { + // to keep existing behavior of returning the "%q has no deployed releases" error when an existing release does not exist + if errors.Is(err, driver.ErrReleaseNotFound) { + return nil, nil, false, driver.NewErrNoDeployedReleases(name) + } + return nil, nil, false, err + } + + lastRelease, err := releaserToV1Release(lastReleasei) + if err != nil { + return nil, nil, false, err + } + + // Concurrent `helm upgrade`s will either fail here with `errPending` or when creating the release with "already exists". This should act as a pessimistic lock. + if lastRelease.Info.Status.IsPending() { + return nil, nil, false, errPending + } + + var currentRelease *release.Release + if lastRelease.Info.Status == rcommon.StatusDeployed { + // no need to retrieve the last deployed release from storage as the last release is deployed + currentRelease = lastRelease + } else { + // finds the deployed release with the given name + currentReleasei, err := u.cfg.Releases.Deployed(name) + var cerr error + currentRelease, cerr = releaserToV1Release(currentReleasei) + if cerr != nil { + return nil, nil, false, err + } + if err != nil { + if errors.Is(err, driver.ErrNoDeployedReleases) && + (lastRelease.Info.Status == rcommon.StatusFailed || lastRelease.Info.Status == rcommon.StatusSuperseded) { + currentRelease = lastRelease + } else { + return nil, nil, false, err + } + } + + } + + // determine if values will be reused + vals, err = u.reuseValues(chart, currentRelease, vals) + if err != nil { + return nil, nil, false, err + } + + if err := chartutil.ProcessDependencies(chart, vals); err != nil { + return nil, nil, false, err + } + + // Increment revision count. This is passed to templates, and also stored on + // the release object. + revision := lastRelease.Version + 1 + + options := common.ReleaseOptions{ + Name: name, + Namespace: currentRelease.Namespace, + Revision: revision, + IsUpgrade: true, + } + + caps, err := u.cfg.getCapabilities() + if err != nil { + return nil, nil, false, err + } + valuesToRender, err := util.ToRenderValuesWithSchemaValidation(chart, vals, options, caps, u.SkipSchemaValidation) + if err != nil { + return nil, nil, false, err + } + + hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithServer(u.DryRunStrategy), u.EnableDNS, u.HideSecret) + if err != nil { + return nil, nil, false, err + } + + if driver.ContainsSystemLabels(u.Labels) { + return nil, nil, false, fmt.Errorf("user supplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()) + } + + serverSideApply, err := getUpgradeServerSideValue(u.ServerSideApply, lastRelease.ApplyMethod) + if err != nil { + return nil, nil, false, err + } + + u.cfg.Logger().Debug("determined release apply method", slog.Bool("server_side_apply", serverSideApply), slog.String("previous_release_apply_method", lastRelease.ApplyMethod)) + + // Store an upgraded release. + upgradedRelease := &release.Release{ + Name: name, + Namespace: currentRelease.Namespace, + Chart: chart, + Config: vals, + Info: &release.Info{ + FirstDeployed: currentRelease.Info.FirstDeployed, + LastDeployed: Timestamper(), + Status: rcommon.StatusPendingUpgrade, + Description: "Preparing upgrade", // This should be overwritten later. + }, + Version: revision, + Manifest: manifestDoc.String(), + Hooks: hooks, + Labels: mergeCustomLabels(lastRelease.Labels, u.Labels), + ApplyMethod: string(determineReleaseSSApplyMethod(serverSideApply)), + } + + if len(notesTxt) > 0 { + upgradedRelease.Info.Notes = notesTxt + } + err = validateManifest(u.cfg.KubeClient, manifestDoc.Bytes(), !u.DisableOpenAPIValidation) + return currentRelease, upgradedRelease, serverSideApply, err +} + +func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedRelease *release.Release, serverSideApply bool) (*release.Release, error) { + current, err := u.cfg.KubeClient.Build(bytes.NewBufferString(originalRelease.Manifest), false) + if err != nil { + // Checking for removed Kubernetes API error so can provide a more informative error message to the user + // Ref: https://github.com/helm/helm/issues/7219 + if strings.Contains(err.Error(), "unable to recognize \"\": no matches for kind") { + return upgradedRelease, fmt.Errorf("current release manifest contains removed kubernetes api(s) for this "+ + "kubernetes version and it is therefore unable to build the kubernetes "+ + "objects for performing the diff. error from kubernetes: %w", err) + } + return upgradedRelease, fmt.Errorf("unable to build kubernetes objects from current release manifest: %w", err) + } + target, err := u.cfg.KubeClient.Build(bytes.NewBufferString(upgradedRelease.Manifest), !u.DisableOpenAPIValidation) + if err != nil { + return upgradedRelease, fmt.Errorf("unable to build kubernetes objects from new release manifest: %w", err) + } + + // It is safe to use force only on target because these are resources currently rendered by the chart. + err = target.Visit(setMetadataVisitor(upgradedRelease.Name, upgradedRelease.Namespace, true)) + if err != nil { + return upgradedRelease, err + } + + // Do a basic diff using gvk + name to figure out what new resources are being created so we can validate they don't already exist + existingResources := make(map[string]bool) + for _, r := range current { + existingResources[objectKey(r)] = true + } + + var toBeCreated kube.ResourceList + for _, r := range target { + if !existingResources[objectKey(r)] { + toBeCreated = append(toBeCreated, r) + } + } + + var toBeUpdated kube.ResourceList + if u.TakeOwnership { + toBeUpdated, err = requireAdoption(toBeCreated) + } else { + toBeUpdated, err = existingResourceConflict(toBeCreated, upgradedRelease.Name, upgradedRelease.Namespace) + } + if err != nil { + return nil, fmt.Errorf("unable to continue with update: %w", err) + } + + toBeUpdated.Visit(func(r *resource.Info, err error) error { + if err != nil { + return err + } + current.Append(r) + return nil + }) + + if isDryRun(u.DryRunStrategy) { + u.cfg.Logger().Debug("dry run for release", "name", upgradedRelease.Name) + if len(u.Description) > 0 { + upgradedRelease.Info.Description = u.Description + } else { + upgradedRelease.Info.Description = "Dry run complete" + } + return upgradedRelease, nil + } + + u.cfg.Logger().Debug("creating upgraded release", "name", upgradedRelease.Name) + if err := u.cfg.Releases.Create(upgradedRelease); err != nil { + return nil, err + } + rChan := make(chan resultMessage) + ctxChan := make(chan resultMessage) + doneChan := make(chan interface{}) + defer close(doneChan) + go u.releasingUpgrade(rChan, upgradedRelease, current, target, originalRelease, serverSideApply) + go u.handleContext(ctx, doneChan, ctxChan, upgradedRelease) + + select { + case result := <-rChan: + return result.r, result.e + case result := <-ctxChan: + return result.r, result.e + } +} + +// Function used to lock the Mutex, this is important for the case when RollbackOnFailure is set. +// In that case the upgrade will finish before the rollback is finished so it is necessary to wait for the rollback to finish. +// The rollback will be trigger by the function failRelease +func (u *Upgrade) reportToPerformUpgrade(c chan<- resultMessage, rel *release.Release, created kube.ResourceList, err error) { + u.Lock.Lock() + if err != nil { + rel, err = u.failRelease(rel, created, err) + } + c <- resultMessage{r: rel, e: err} + u.Lock.Unlock() +} + +// Setup listener for SIGINT and SIGTERM +func (u *Upgrade) handleContext(ctx context.Context, done chan interface{}, c chan<- resultMessage, upgradedRelease *release.Release) { + select { + case <-ctx.Done(): + err := ctx.Err() + + // when RollbackOnFailure is set, the ongoing release finish first and doesn't give time for the rollback happens. + u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, err) + case <-done: + return + } +} + +func isReleaseApplyMethodClientSideApply(applyMethod string) bool { + return applyMethod == "" || applyMethod == string(release.ApplyMethodClientSideApply) +} + +func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *release.Release, current kube.ResourceList, target kube.ResourceList, originalRelease *release.Release, serverSideApply bool) { + // pre-upgrade hooks + + if !u.DisableHooks { + if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.WaitStrategy, u.WaitOptions, u.Timeout, serverSideApply); err != nil { + u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, fmt.Errorf("pre-upgrade hooks failed: %s", err)) + return + } + } else { + u.cfg.Logger().Debug("upgrade hooks disabled", "name", upgradedRelease.Name) + } + + upgradeClientSideFieldManager := isReleaseApplyMethodClientSideApply(originalRelease.ApplyMethod) && serverSideApply // Update client-side field manager if transitioning from client-side to server-side apply + results, err := u.cfg.KubeClient.Update( + current, + target, + kube.ClientUpdateOptionForceReplace(u.ForceReplace), + kube.ClientUpdateOptionServerSideApply(serverSideApply, u.ForceConflicts), + kube.ClientUpdateOptionUpgradeClientSideFieldManager(upgradeClientSideFieldManager)) + if err != nil { + u.cfg.recordRelease(originalRelease) + u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) + return + } + + var waiter kube.Waiter + if c, supportsOptions := u.cfg.KubeClient.(kube.InterfaceWaitOptions); supportsOptions { + waiter, err = c.GetWaiterWithOptions(u.WaitStrategy, u.WaitOptions...) + } else { + waiter, err = u.cfg.KubeClient.GetWaiter(u.WaitStrategy) + } + if err != nil { + u.cfg.recordRelease(originalRelease) + u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) + return + } + if u.WaitForJobs { + if err := waiter.WaitWithJobs(target, u.Timeout); err != nil { + u.cfg.recordRelease(originalRelease) + u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) + return + } + } else { + if err := waiter.Wait(target, u.Timeout); err != nil { + u.cfg.recordRelease(originalRelease) + u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) + return + } + } + + // post-upgrade hooks + if !u.DisableHooks { + if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.WaitStrategy, u.WaitOptions, u.Timeout, serverSideApply); err != nil { + u.reportToPerformUpgrade(c, upgradedRelease, results.Created, fmt.Errorf("post-upgrade hooks failed: %s", err)) + return + } + } + + originalRelease.Info.Status = rcommon.StatusSuperseded + u.cfg.recordRelease(originalRelease) + + upgradedRelease.Info.Status = rcommon.StatusDeployed + if len(u.Description) > 0 { + upgradedRelease.Info.Description = u.Description + } else { + upgradedRelease.Info.Description = "Upgrade complete" + } + u.reportToPerformUpgrade(c, upgradedRelease, nil, nil) +} + +func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, err error) (*release.Release, error) { + msg := fmt.Sprintf("Upgrade %q failed: %s", rel.Name, err) + u.cfg.Logger().Warn( + "upgrade failed", + slog.String("name", rel.Name), + slog.Any("error", err), + ) + + rel.Info.Status = rcommon.StatusFailed + rel.Info.Description = msg + u.cfg.recordRelease(rel) + if u.CleanupOnFail && len(created) > 0 { + u.cfg.Logger().Debug("cleanup on fail set", "cleaning_resources", len(created)) + _, errs := u.cfg.KubeClient.Delete(created, metav1.DeletePropagationBackground) + if errs != nil { + return rel, fmt.Errorf( + "an error occurred while cleaning up resources. original upgrade error: %w: %w", + err, + fmt.Errorf( + "unable to cleanup resources: %w", + joinErrors(errs, ", "), + ), + ) + } + u.cfg.Logger().Debug("resource cleanup complete") + } + + if u.RollbackOnFailure { + u.cfg.Logger().Debug("Upgrade failed and rollback-on-failure is set, rolling back to previous successful release") + + // As a protection, get the last successful release before rollback. + // If there are no successful releases, bail out + hist := NewHistory(u.cfg) + fullHistory, herr := hist.Run(rel.Name) + if herr != nil { + return rel, fmt.Errorf("an error occurred while finding last successful release. original upgrade error: %w: %w", err, herr) + } + + fullHistoryV1, herr := releaseListToV1List(fullHistory) + if herr != nil { + return nil, herr + } + // There isn't a way to tell if a previous release was successful, but + // generally failed releases do not get superseded unless the next + // release is successful, so this should be relatively safe + filteredHistory := releaseutil.FilterFunc(func(r *release.Release) bool { + return r.Info.Status == rcommon.StatusSuperseded || r.Info.Status == rcommon.StatusDeployed + }).Filter(fullHistoryV1) + if len(filteredHistory) == 0 { + return rel, fmt.Errorf("unable to find a previously successful release when attempting to rollback. original upgrade error: %w", err) + } + + releaseutil.Reverse(filteredHistory, releaseutil.SortByRevision) + + rollin := NewRollback(u.cfg) + rollin.Version = filteredHistory[0].Version + rollin.WaitStrategy = u.WaitStrategy + rollin.WaitOptions = u.WaitOptions + rollin.WaitForJobs = u.WaitForJobs + rollin.DisableHooks = u.DisableHooks + rollin.ForceReplace = u.ForceReplace + rollin.ForceConflicts = u.ForceConflicts + rollin.ServerSideApply = u.ServerSideApply + rollin.Timeout = u.Timeout + if rollErr := rollin.Run(rel.Name); rollErr != nil { + return rel, fmt.Errorf("an error occurred while rolling back the release. original upgrade error: %w: %w", err, rollErr) + } + return rel, fmt.Errorf("release %s failed, and has been rolled back due to rollback-on-failure being set: %w", rel.Name, err) + } + + return rel, err +} + +// reuseValues copies values from the current release to a new release if the +// new release does not have any values. +// +// If the request already has values, or if there are no values in the current +// release, this does nothing. +// +// This is skipped if the u.ResetValues flag is set, in which case the +// request values are not altered. +func (u *Upgrade) reuseValues(chart *chartv2.Chart, current *release.Release, newVals map[string]interface{}) (map[string]interface{}, error) { + if u.ResetValues { + // If ResetValues is set, we completely ignore current.Config. + u.cfg.Logger().Debug("resetting values to the chart's original version") + return newVals, nil + } + + // If the ReuseValues flag is set, we always copy the old values over the new config's values. + if u.ReuseValues { + u.cfg.Logger().Debug("reusing the old release's values") + + // We have to regenerate the old coalesced values: + oldVals, err := util.CoalesceValues(current.Chart, current.Config) + if err != nil { + return nil, fmt.Errorf("failed to rebuild old values: %w", err) + } + + newVals = util.CoalesceTables(newVals, current.Config) + + chart.Values = oldVals + + return newVals, nil + } + + // If the ResetThenReuseValues flag is set, we use the new chart's values, but we copy the old config's values over the new config's values. + if u.ResetThenReuseValues { + u.cfg.Logger().Debug("merging values from old release to new values") + + newVals = util.CoalesceTables(newVals, current.Config) + + return newVals, nil + } + + if len(newVals) == 0 && len(current.Config) > 0 { + u.cfg.Logger().Debug("copying values from old release", "name", current.Name, "version", current.Version) + newVals = current.Config + } + return newVals, nil +} + +func validateManifest(c kube.Interface, manifest []byte, openAPIValidation bool) error { + _, err := c.Build(bytes.NewReader(manifest), openAPIValidation) + return err +} + +func objectKey(r *resource.Info) string { + gvk := r.Object.GetObjectKind().GroupVersionKind() + return fmt.Sprintf("%s/%s/%s/%s", gvk.GroupVersion().String(), gvk.Kind, r.Namespace, r.Name) +} + +func mergeCustomLabels(current, desired map[string]string) map[string]string { + labels := mergeStrStrMaps(current, desired) + for k, v := range labels { + if v == "null" { + delete(labels, k) + } + } + return labels +} + +func getUpgradeServerSideValue(serverSideOption string, releaseApplyMethod string) (bool, error) { + switch serverSideOption { + case "auto": + return releaseApplyMethod == "ssa", nil + case "false": + return false, nil + case "true": + return true, nil + default: + return false, fmt.Errorf("invalid/unknown release server-side apply method: %s", serverSideOption) + } +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/validate.go b/vendor/helm.sh/helm/v4/pkg/action/validate.go new file mode 100644 index 00000000000..5c48a628c40 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/validate.go @@ -0,0 +1,233 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "fmt" + "maps" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/resource" + + "helm.sh/helm/v4/pkg/kube" +) + +var accessor = meta.NewAccessor() + +const ( + appManagedByLabel = "app.kubernetes.io/managed-by" + appManagedByHelm = "Helm" + helmReleaseNameAnnotation = "meta.helm.sh/release-name" + helmReleaseNamespaceAnnotation = "meta.helm.sh/release-namespace" +) + +// requireAdoption returns the subset of resources that already exist in the cluster. +func requireAdoption(resources kube.ResourceList) (kube.ResourceList, error) { + var requireUpdate kube.ResourceList + + err := resources.Visit(func(info *resource.Info, err error) error { + if err != nil { + return err + } + + isGenerateName, err := validateNameAndGenerateName(info) + if isGenerateName || err != nil { + return err + } + + helper := resource.NewHelper(info.Client, info.Mapping) + _, err = helper.Get(info.Namespace, info.Name) + if err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return fmt.Errorf("could not get information about the resource %s: %w", resourceString(info), err) + } + + infoCopy := *info + requireUpdate.Append(&infoCopy) + return nil + }) + + return requireUpdate, err +} + +func existingResourceConflict(resources kube.ResourceList, releaseName, releaseNamespace string) (kube.ResourceList, error) { + var requireUpdate kube.ResourceList + + err := resources.Visit(func(info *resource.Info, err error) error { + if err != nil { + return err + } + + isGenerateName, err := validateNameAndGenerateName(info) + if isGenerateName || err != nil { + return err + } + + helper := resource.NewHelper(info.Client, info.Mapping) + existing, err := helper.Get(info.Namespace, info.Name) + if err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return fmt.Errorf("could not get information about the resource %s: %w", resourceString(info), err) + } + + // Allow adoption of the resource if it is managed by Helm and is annotated with correct release name and namespace. + if err := checkOwnership(existing, releaseName, releaseNamespace); err != nil { + return fmt.Errorf("%s exists and cannot be imported into the current release: %s", resourceString(info), err) + } + + infoCopy := *info + requireUpdate.Append(&infoCopy) + return nil + }) + + return requireUpdate, err +} + +func checkOwnership(obj runtime.Object, releaseName, releaseNamespace string) error { + lbls, err := accessor.Labels(obj) + if err != nil { + return err + } + annos, err := accessor.Annotations(obj) + if err != nil { + return err + } + + var errs []error + if err := requireValue(lbls, appManagedByLabel, appManagedByHelm); err != nil { + errs = append(errs, fmt.Errorf("label validation error: %s", err)) + } + if err := requireValue(annos, helmReleaseNameAnnotation, releaseName); err != nil { + errs = append(errs, fmt.Errorf("annotation validation error: %s", err)) + } + if err := requireValue(annos, helmReleaseNamespaceAnnotation, releaseNamespace); err != nil { + errs = append(errs, fmt.Errorf("annotation validation error: %s", err)) + } + + if len(errs) > 0 { + return fmt.Errorf("invalid ownership metadata; %w", joinErrors(errs, "; ")) + } + + return nil +} + +func requireValue(meta map[string]string, k, v string) error { + actual, ok := meta[k] + if !ok { + return fmt.Errorf("missing key %q: must be set to %q", k, v) + } + if actual != v { + return fmt.Errorf("key %q must equal %q: current value is %q", k, v, actual) + } + return nil +} + +// setMetadataVisitor adds release tracking metadata to all resources. If forceOwnership is enabled, existing +// ownership metadata will be overwritten. Otherwise an error will be returned if any resource has an +// existing and conflicting value for the managed by label or Helm release/namespace annotations. +func setMetadataVisitor(releaseName, releaseNamespace string, forceOwnership bool) resource.VisitorFunc { + return func(info *resource.Info, err error) error { + if err != nil { + return err + } + + if !forceOwnership { + if err := checkOwnership(info.Object, releaseName, releaseNamespace); err != nil { + return fmt.Errorf("%s cannot be owned: %s", resourceString(info), err) + } + } + + if err := mergeLabels(info.Object, map[string]string{ + appManagedByLabel: appManagedByHelm, + }); err != nil { + return fmt.Errorf( + "%s labels could not be updated: %s", + resourceString(info), err, + ) + } + + if err := mergeAnnotations(info.Object, map[string]string{ + helmReleaseNameAnnotation: releaseName, + helmReleaseNamespaceAnnotation: releaseNamespace, + }); err != nil { + return fmt.Errorf( + "%s annotations could not be updated: %s", + resourceString(info), err, + ) + } + + return nil + } +} + +func resourceString(info *resource.Info) string { + _, k := info.Mapping.GroupVersionKind.ToAPIVersionAndKind() + return fmt.Sprintf( + "%s %q in namespace %q", + k, info.Name, info.Namespace, + ) +} + +func mergeLabels(obj runtime.Object, labels map[string]string) error { + current, err := accessor.Labels(obj) + if err != nil { + return err + } + return accessor.SetLabels(obj, mergeStrStrMaps(current, labels)) +} + +func mergeAnnotations(obj runtime.Object, annotations map[string]string) error { + current, err := accessor.Annotations(obj) + if err != nil { + return err + } + return accessor.SetAnnotations(obj, mergeStrStrMaps(current, annotations)) +} + +// merge two maps, always taking the value on the right +func mergeStrStrMaps(current, desired map[string]string) map[string]string { + result := make(map[string]string) + maps.Copy(result, current) + maps.Copy(result, desired) + return result +} + +// validateNameAndGenerateName validates that an object only has either `Name` or `GenerateName` set (and not both) +// If `GenerateName` is set, true is returned +// If an invalid combination of `Name` and `GenerateName` are set, an error is returned +func validateNameAndGenerateName(info *resource.Info) (bool, error) { + accessor, err := meta.Accessor(info.Object) + if err != nil { + return false, err + } + + if info.Name == "" && accessor.GetGenerateName() != "" { + return true, nil + } + + if info.Name != "" && accessor.GetGenerateName() != "" { + return true, fmt.Errorf("metadata.name and metadata.generateName cannot both be set") + } + + return false, nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/action/verify.go b/vendor/helm.sh/helm/v4/pkg/action/verify.go new file mode 100644 index 00000000000..6e4562f6140 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/action/verify.go @@ -0,0 +1,53 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "fmt" + "strings" + + "helm.sh/helm/v4/pkg/downloader" +) + +// Verify is the action for building a given chart's Verify tree. +// +// It provides the implementation of 'helm verify'. +type Verify struct { + Keyring string +} + +// NewVerify creates a new Verify object with the given configuration. +func NewVerify() *Verify { + return &Verify{} +} + +// Run executes 'helm verify'. +func (v *Verify) Run(chartfile string) (string, error) { + var out strings.Builder + p, err := downloader.VerifyChart(chartfile, chartfile+".prov", v.Keyring) + if err != nil { + return "", err + } + + for name := range p.SignedBy.Identities { + _, _ = fmt.Fprintf(&out, "Signed by: %v\n", name) + } + _, _ = fmt.Fprintf(&out, "Using Key With Fingerprint: %X\n", p.SignedBy.PrimaryKey.Fingerprint) + _, _ = fmt.Fprintf(&out, "Chart Hash Verified: %s\n", p.FileHash) + + return out.String(), err +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/common.go b/vendor/helm.sh/helm/v4/pkg/chart/common.go new file mode 100644 index 00000000000..cd87e91e78c --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/common.go @@ -0,0 +1,243 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package chart + +import ( + "errors" + "fmt" + "log/slog" + "reflect" + "strings" + + v3chart "helm.sh/helm/v4/internal/chart/v3" + common "helm.sh/helm/v4/pkg/chart/common" + v2chart "helm.sh/helm/v4/pkg/chart/v2" +) + +var NewAccessor func(chrt Charter) (Accessor, error) = NewDefaultAccessor //nolint:revive + +func NewDefaultAccessor(chrt Charter) (Accessor, error) { + switch v := chrt.(type) { + case v2chart.Chart: + return &v2Accessor{&v}, nil + case *v2chart.Chart: + return &v2Accessor{v}, nil + case v3chart.Chart: + return &v3Accessor{&v}, nil + case *v3chart.Chart: + return &v3Accessor{v}, nil + default: + return nil, errors.New("unsupported chart type") + } +} + +type v2Accessor struct { + chrt *v2chart.Chart +} + +func (r *v2Accessor) Name() string { + return r.chrt.Metadata.Name +} + +func (r *v2Accessor) IsRoot() bool { + return r.chrt.IsRoot() +} + +func (r *v2Accessor) MetadataAsMap() map[string]interface{} { + var ret map[string]interface{} + if r.chrt.Metadata == nil { + return ret + } + + ret, err := structToMap(r.chrt.Metadata) + if err != nil { + slog.Error("error converting metadata to map", "error", err) + } + return ret +} + +func (r *v2Accessor) Files() []*common.File { + return r.chrt.Files +} + +func (r *v2Accessor) Templates() []*common.File { + return r.chrt.Templates +} + +func (r *v2Accessor) ChartFullPath() string { + return r.chrt.ChartFullPath() +} + +func (r *v2Accessor) IsLibraryChart() bool { + return strings.EqualFold(r.chrt.Metadata.Type, "library") +} + +func (r *v2Accessor) Dependencies() []Charter { + var deps = make([]Charter, len(r.chrt.Dependencies())) + for i, c := range r.chrt.Dependencies() { + deps[i] = c + } + return deps +} + +func (r *v2Accessor) MetaDependencies() []Dependency { + var deps = make([]Dependency, len(r.chrt.Metadata.Dependencies)) + for i, c := range r.chrt.Metadata.Dependencies { + deps[i] = c + } + return deps +} + +func (r *v2Accessor) Values() map[string]interface{} { + return r.chrt.Values +} + +func (r *v2Accessor) Schema() []byte { + return r.chrt.Schema +} + +func (r *v2Accessor) Deprecated() bool { + return r.chrt.Metadata.Deprecated +} + +type v3Accessor struct { + chrt *v3chart.Chart +} + +func (r *v3Accessor) Name() string { + return r.chrt.Metadata.Name +} + +func (r *v3Accessor) IsRoot() bool { + return r.chrt.IsRoot() +} + +func (r *v3Accessor) MetadataAsMap() map[string]interface{} { + var ret map[string]interface{} + if r.chrt.Metadata == nil { + return ret + } + + ret, err := structToMap(r.chrt.Metadata) + if err != nil { + slog.Error("error converting metadata to map", "error", err) + } + return ret +} + +func (r *v3Accessor) Files() []*common.File { + return r.chrt.Files +} + +func (r *v3Accessor) Templates() []*common.File { + return r.chrt.Templates +} + +func (r *v3Accessor) ChartFullPath() string { + return r.chrt.ChartFullPath() +} + +func (r *v3Accessor) IsLibraryChart() bool { + return strings.EqualFold(r.chrt.Metadata.Type, "library") +} + +func (r *v3Accessor) Dependencies() []Charter { + var deps = make([]Charter, len(r.chrt.Dependencies())) + for i, c := range r.chrt.Dependencies() { + deps[i] = c + } + return deps +} + +func (r *v3Accessor) MetaDependencies() []Dependency { + var deps = make([]Dependency, len(r.chrt.Dependencies())) + for i, c := range r.chrt.Metadata.Dependencies { + deps[i] = c + } + return deps +} + +func (r *v3Accessor) Values() map[string]interface{} { + return r.chrt.Values +} + +func (r *v3Accessor) Schema() []byte { + return r.chrt.Schema +} + +func (r *v3Accessor) Deprecated() bool { + return r.chrt.Metadata.Deprecated +} + +func structToMap(obj interface{}) (map[string]interface{}, error) { + objValue := reflect.ValueOf(obj) + + // If the value is a pointer, dereference it + if objValue.Kind() == reflect.Pointer { + objValue = objValue.Elem() + } + + // Check if the input is a struct + if objValue.Kind() != reflect.Struct { + return nil, fmt.Errorf("input must be a struct or a pointer to a struct") + } + + result := make(map[string]interface{}) + objType := objValue.Type() + + for i := 0; i < objValue.NumField(); i++ { + field := objType.Field(i) + value := objValue.Field(i) + + switch value.Kind() { + case reflect.Struct: + nestedMap, err := structToMap(value.Interface()) + if err != nil { + return nil, err + } + result[field.Name] = nestedMap + case reflect.Pointer: + // Recurse for pointers by dereferencing + if value.IsNil() { + result[field.Name] = nil + } else { + nestedMap, err := structToMap(value.Interface()) + if err != nil { + return nil, err + } + result[field.Name] = nestedMap + } + case reflect.Slice: + sliceOfMaps := make([]interface{}, value.Len()) + for j := 0; j < value.Len(); j++ { + sliceElement := value.Index(j) + if sliceElement.Kind() == reflect.Struct || sliceElement.Kind() == reflect.Pointer { + nestedMap, err := structToMap(sliceElement.Interface()) + if err != nil { + return nil, err + } + sliceOfMaps[j] = nestedMap + } else { + sliceOfMaps[j] = sliceElement.Interface() + } + } + result[field.Name] = sliceOfMaps + default: + result[field.Name] = value.Interface() + } + } + return result, nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/common/capabilities.go b/vendor/helm.sh/helm/v4/pkg/chart/common/capabilities.go new file mode 100644 index 00000000000..18d00de90a2 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/common/capabilities.go @@ -0,0 +1,182 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import ( + "fmt" + "slices" + "strconv" + "strings" + "testing" + + "github.com/Masterminds/semver/v3" + "k8s.io/client-go/kubernetes/scheme" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + k8sversion "k8s.io/apimachinery/pkg/util/version" + + helmversion "helm.sh/helm/v4/internal/version" +) + +const ( + kubeVersionMajorTesting = 1 + kubeVersionMinorTesting = 20 +) + +var ( + // DefaultVersionSet is the default version set, which includes only Core V1 ("v1"). + DefaultVersionSet = allKnownVersions() + + DefaultCapabilities = func() *Capabilities { + caps, err := makeDefaultCapabilities() + if err != nil { + panic(fmt.Sprintf("failed to create default capabilities: %v", err)) + } + return caps + + }() +) + +// Capabilities describes the capabilities of the Kubernetes cluster. +type Capabilities struct { + // KubeVersion is the Kubernetes version. + KubeVersion KubeVersion + // APIVersions are supported Kubernetes API versions. + APIVersions VersionSet + // HelmVersion is the build information for this helm version + HelmVersion helmversion.BuildInfo +} + +func (capabilities *Capabilities) Copy() *Capabilities { + return &Capabilities{ + KubeVersion: capabilities.KubeVersion, + APIVersions: capabilities.APIVersions, + HelmVersion: capabilities.HelmVersion, + } +} + +// KubeVersion is the Kubernetes version. +type KubeVersion struct { + Version string // Full version (e.g., v1.33.4-gke.1245000) + normalizedVersion string // Normalized for constraint checking (e.g., v1.33.4) + Major string // Kubernetes major version + Minor string // Kubernetes minor version +} + +// String implements fmt.Stringer. +// Returns the normalized version used for constraint checking. +func (kv *KubeVersion) String() string { + if kv.normalizedVersion != "" { + return kv.normalizedVersion + } + return kv.Version +} + +// GitVersion returns the full Kubernetes version string. +// +// Deprecated: use KubeVersion.Version. +func (kv *KubeVersion) GitVersion() string { return kv.Version } + +// ParseKubeVersion parses kubernetes version from string +func ParseKubeVersion(version string) (*KubeVersion, error) { + // Based on the original k8s version parser. + // https://github.com/kubernetes/kubernetes/blob/b266ac2c3e42c2c4843f81e20213d2b2f43e450a/staging/src/k8s.io/apimachinery/pkg/util/version/version.go#L137 + sv, err := k8sversion.ParseGeneric(version) + if err != nil { + return nil, err + } + + // Preserve original input (e.g., v1.33.4-gke.1245000) + gitVersion := version + if !strings.HasPrefix(version, "v") { + gitVersion = "v" + version + } + + // Normalize for constraint checking (strips all suffixes) + normalizedVer := "v" + sv.String() + + return &KubeVersion{ + Version: gitVersion, + normalizedVersion: normalizedVer, + Major: strconv.FormatUint(uint64(sv.Major()), 10), + Minor: strconv.FormatUint(uint64(sv.Minor()), 10), + }, nil +} + +// VersionSet is a set of Kubernetes API versions. +type VersionSet []string + +// Has returns true if the version string is in the set. +// +// vs.Has("apps/v1") +func (v VersionSet) Has(apiVersion string) bool { + return slices.Contains(v, apiVersion) +} + +func allKnownVersions() VersionSet { + // We should register the built in extension APIs as well so CRDs are + // supported in the default version set. This has caused problems with `helm + // template` in the past, so let's be safe + apiextensionsv1beta1.AddToScheme(scheme.Scheme) + apiextensionsv1.AddToScheme(scheme.Scheme) + + groups := scheme.Scheme.PrioritizedVersionsAllGroups() + vs := make(VersionSet, 0, len(groups)) + for _, gv := range groups { + vs = append(vs, gv.String()) + } + return vs +} + +func makeDefaultCapabilities() (*Capabilities, error) { + // Test builds don't include debug info / module info + // (And even if they did, we probably want stable capabilities for tests anyway) + // Return a default value for test builds + if testing.Testing() { + return newCapabilities(kubeVersionMajorTesting, kubeVersionMinorTesting) + } + + vstr, err := helmversion.K8sIOClientGoModVersion() + if err != nil { + return nil, fmt.Errorf("failed to retrieve k8s.io/client-go version: %w", err) + } + + v, err := semver.NewVersion(vstr) + if err != nil { + return nil, fmt.Errorf("unable to parse k8s.io/client-go version %q: %v", vstr, err) + } + + kubeVersionMajor := v.Major() + 1 + kubeVersionMinor := v.Minor() + + return newCapabilities(kubeVersionMajor, kubeVersionMinor) +} + +func newCapabilities(kubeVersionMajor, kubeVersionMinor uint64) (*Capabilities, error) { + + version := fmt.Sprintf("v%d.%d.0", kubeVersionMajor, kubeVersionMinor) + return &Capabilities{ + KubeVersion: KubeVersion{ + Version: version, + normalizedVersion: version, + Major: fmt.Sprintf("%d", kubeVersionMajor), + Minor: fmt.Sprintf("%d", kubeVersionMinor), + }, + APIVersions: DefaultVersionSet, + HelmVersion: helmversion.Get(), + }, nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/common/errors.go b/vendor/helm.sh/helm/v4/pkg/chart/common/errors.go new file mode 100644 index 00000000000..b0a2d650e97 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/common/errors.go @@ -0,0 +1,43 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import ( + "fmt" +) + +// ErrNoTable indicates that a chart does not have a matching table. +type ErrNoTable struct { + Key string +} + +func (e ErrNoTable) Error() string { return fmt.Sprintf("%q is not a table", e.Key) } + +// ErrNoValue indicates that Values does not contain a key with a value +type ErrNoValue struct { + Key string +} + +func (e ErrNoValue) Error() string { return fmt.Sprintf("%q is not a value", e.Key) } + +type ErrInvalidChartName struct { + Name string +} + +func (e ErrInvalidChartName) Error() string { + return fmt.Sprintf("%q is not a valid chart name", e.Name) +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/common/file.go b/vendor/helm.sh/helm/v4/pkg/chart/common/file.go new file mode 100644 index 00000000000..1068bf45011 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/common/file.go @@ -0,0 +1,31 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import "time" + +// File represents a file as a name/value pair. +// +// By convention, name is a relative path within the scope of the chart's +// base directory. +type File struct { + // Name is the path-like name of the template. + Name string `json:"name"` + // Data is the template as byte data. + Data []byte `json:"data"` + // ModTime is the file's mod-time + ModTime time.Time `json:"modtime,omitzero"` +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/common/util/coalesce.go b/vendor/helm.sh/helm/v4/pkg/chart/common/util/coalesce.go new file mode 100644 index 00000000000..6c72b3d565e --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/common/util/coalesce.go @@ -0,0 +1,348 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + "log" + "maps" + + "helm.sh/helm/v4/internal/copystructure" + chart "helm.sh/helm/v4/pkg/chart" + "helm.sh/helm/v4/pkg/chart/common" +) + +func concatPrefix(a, b string) string { + if a == "" { + return b + } + return fmt.Sprintf("%s.%s", a, b) +} + +// CoalesceValues coalesces all of the values in a chart (and its subcharts). +// +// Values are coalesced together using the following rules: +// +// - Values in a higher level chart always override values in a lower-level +// dependency chart +// - Scalar values and arrays are replaced, maps are merged +// - A chart has access to all of the variables for it, as well as all of +// the values destined for its dependencies. +func CoalesceValues(chrt chart.Charter, vals map[string]interface{}) (common.Values, error) { + valsCopy, err := copyValues(vals) + if err != nil { + return vals, err + } + return coalesce(log.Printf, chrt, valsCopy, "", false) +} + +// MergeValues is used to merge the values in a chart and its subcharts. This +// is different from Coalescing as nil/null values are preserved. +// +// Values are coalesced together using the following rules: +// +// - Values in a higher level chart always override values in a lower-level +// dependency chart +// - Scalar values and arrays are replaced, maps are merged +// - A chart has access to all of the variables for it, as well as all of +// the values destined for its dependencies. +// +// Retaining Nils is useful when processes early in a Helm action or business +// logic need to retain them for when Coalescing will happen again later in the +// business logic. +func MergeValues(chrt chart.Charter, vals map[string]interface{}) (common.Values, error) { + valsCopy, err := copyValues(vals) + if err != nil { + return vals, err + } + return coalesce(log.Printf, chrt, valsCopy, "", true) +} + +func copyValues(vals map[string]interface{}) (common.Values, error) { + v, err := copystructure.Copy(vals) + if err != nil { + return vals, err + } + + valsCopy := v.(map[string]interface{}) + // if we have an empty map, make sure it is initialized + if valsCopy == nil { + valsCopy = make(map[string]interface{}) + } + + return valsCopy, nil +} + +type printFn func(format string, v ...interface{}) + +// coalesce coalesces the dest values and the chart values, giving priority to the dest values. +// +// This is a helper function for CoalesceValues and MergeValues. +// +// Note, the merge argument specifies whether this is being used by MergeValues +// or CoalesceValues. Coalescing removes null values and their keys in some +// situations while merging keeps the null values. +func coalesce(printf printFn, ch chart.Charter, dest map[string]interface{}, prefix string, merge bool) (map[string]interface{}, error) { + coalesceValues(printf, ch, dest, prefix, merge) + return coalesceDeps(printf, ch, dest, prefix, merge) +} + +// coalesceDeps coalesces the dependencies of the given chart. +func coalesceDeps(printf printFn, chrt chart.Charter, dest map[string]interface{}, prefix string, merge bool) (map[string]interface{}, error) { + ch, err := chart.NewAccessor(chrt) + if err != nil { + return dest, err + } + for _, subchart := range ch.Dependencies() { + sub, err := chart.NewAccessor(subchart) + if err != nil { + return dest, err + } + if c, ok := dest[sub.Name()]; !ok { + // If dest doesn't already have the key, create it. + dest[sub.Name()] = make(map[string]interface{}) + } else if !istable(c) { + return dest, fmt.Errorf("type mismatch on %s: %t", sub.Name(), c) + } + if dv, ok := dest[sub.Name()]; ok { + dvmap := dv.(map[string]interface{}) + subPrefix := concatPrefix(prefix, ch.Name()) + // Get globals out of dest and merge them into dvmap. + coalesceGlobals(printf, dvmap, dest, subPrefix, merge) + // Now coalesce the rest of the values. + var err error + dest[sub.Name()], err = coalesce(printf, subchart, dvmap, subPrefix, merge) + if err != nil { + return dest, err + } + } + } + return dest, nil +} + +// coalesceGlobals copies the globals out of src and merges them into dest. +// +// For convenience, returns dest. +func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix string, _ bool) { + var dg, sg map[string]interface{} + + if destglob, ok := dest[common.GlobalKey]; !ok { + dg = make(map[string]interface{}) + } else if dg, ok = destglob.(map[string]interface{}); !ok { + printf("warning: skipping globals because destination %s is not a table.", common.GlobalKey) + return + } + + if srcglob, ok := src[common.GlobalKey]; !ok { + sg = make(map[string]interface{}) + } else if sg, ok = srcglob.(map[string]interface{}); !ok { + printf("warning: skipping globals because source %s is not a table.", common.GlobalKey) + return + } + + // EXPERIMENTAL: In the past, we have disallowed globals to test tables. This + // reverses that decision. It may somehow be possible to introduce a loop + // here, but I haven't found a way. So for the time being, let's allow + // tables in globals. + for key, val := range sg { + if istable(val) { + vv := copyMap(val.(map[string]interface{})) + if destv, ok := dg[key]; !ok { + // Here there is no merge. We're just adding. + dg[key] = vv + } else { + if destvmap, ok := destv.(map[string]interface{}); !ok { + printf("Conflict: cannot merge map onto non-map for %q. Skipping.", key) + } else { + // Basically, we reverse order of coalesce here to merge + // top-down. + subPrefix := concatPrefix(prefix, key) + // In this location coalesceTablesFullKey should always have + // merge set to true. The output of coalesceGlobals is run + // through coalesce where any nils will be removed. + coalesceTablesFullKey(printf, vv, destvmap, subPrefix, true) + dg[key] = vv + } + } + } else if dv, ok := dg[key]; ok && istable(dv) { + // It's not clear if this condition can actually ever trigger. + printf("key %s is table. Skipping", key) + } else { + // TODO: Do we need to do any additional checking on the value? + dg[key] = val + } + } + dest[common.GlobalKey] = dg +} + +func copyMap(src map[string]interface{}) map[string]interface{} { + m := make(map[string]interface{}, len(src)) + maps.Copy(m, src) + return m +} + +// coalesceValues builds up a values map for a particular chart. +// +// Values in v will override the values in the chart. +func coalesceValues(printf printFn, c chart.Charter, v map[string]interface{}, prefix string, merge bool) { + ch, err := chart.NewAccessor(c) + if err != nil { + return + } + + subPrefix := concatPrefix(prefix, ch.Name()) + + // Using c.Values directly when coalescing a table can cause problems where + // the original c.Values is altered. Creating a deep copy stops the problem. + // This section is fault-tolerant as there is no ability to return an error. + valuesCopy, err := copystructure.Copy(ch.Values()) + var vc map[string]interface{} + var ok bool + if err != nil { + // If there is an error something is wrong with copying c.Values it + // means there is a problem in the deep copying package or something + // wrong with c.Values. In this case we will use c.Values and report + // an error. + printf("warning: unable to copy values, err: %s", err) + vc = ch.Values() + } else { + vc, ok = valuesCopy.(map[string]interface{}) + if !ok { + // c.Values has a map[string]interface{} structure. If the copy of + // it cannot be treated as map[string]interface{} there is something + // strangely wrong. Log it and use c.Values + printf("warning: unable to convert values copy to values type") + vc = ch.Values() + } + } + + for key, val := range vc { + if value, ok := v[key]; ok { + if value == nil && !merge { + // When the YAML value is null and we are coalescing instead of + // merging, we remove the value's key. + // This allows Helm's various sources of values (value files or --set) to + // remove incompatible keys from any previous chart, file, or set values. + delete(v, key) + } else if dest, ok := value.(map[string]interface{}); ok { + // if v[key] is a table, merge nv's val table into v[key]. + src, ok := val.(map[string]interface{}) + if !ok { + // If the original value is nil, there is nothing to coalesce, so we don't print + // the warning + if val != nil { + printf("warning: skipped value for %s.%s: Not a table.", subPrefix, key) + } + } else { + // If the key is a child chart, coalesce tables with Merge set to true + merge := childChartMergeTrue(c, key, merge) + + // Because v has higher precedence than nv, dest values override src + // values. + coalesceTablesFullKey(printf, dest, src, concatPrefix(subPrefix, key), merge) + } + } + } else { + // If the key is not in v, copy it from nv. + v[key] = val + } + } +} + +func childChartMergeTrue(chrt chart.Charter, key string, merge bool) bool { + ch, err := chart.NewAccessor(chrt) + if err != nil { + return merge + } + for _, subchart := range ch.Dependencies() { + sub, err := chart.NewAccessor(subchart) + if err != nil { + return merge + } + if sub.Name() == key { + return true + } + } + return merge +} + +// CoalesceTables merges a source map into a destination map. +// +// dest is considered authoritative. +func CoalesceTables(dst, src map[string]interface{}) map[string]interface{} { + return coalesceTablesFullKey(log.Printf, dst, src, "", false) +} + +func MergeTables(dst, src map[string]interface{}) map[string]interface{} { + return coalesceTablesFullKey(log.Printf, dst, src, "", true) +} + +// coalesceTablesFullKey merges a source map into a destination map. +// +// dest is considered authoritative. +func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, prefix string, merge bool) map[string]interface{} { + // When --reuse-values is set but there are no modifications yet, return new values + if src == nil { + return dst + } + if dst == nil { + return src + } + // Track original non-nil src keys before modifying src + // This lets us distinguish between user nullifying a chart default vs + // user setting nil for a key not in chart defaults. + srcOriginalNonNil := make(map[string]bool) + for key, val := range src { + if val != nil { + srcOriginalNonNil[key] = true + } + } + for key, val := range dst { + if val == nil { + src[key] = nil + } + } + // Because dest has higher precedence than src, dest values override src + // values. + for key, val := range src { + fullkey := concatPrefix(prefix, key) + if dv, ok := dst[key]; ok && !merge && dv == nil && srcOriginalNonNil[key] { + // When coalescing (not merging), if dst has nil and src has a non-nil + // value, the user is nullifying a chart default - remove the key. + // But if src also has nil (or key not in src), preserve the nil + delete(dst, key) + } else if !ok { + // key not in user values, preserve src value (including nil) + dst[key] = val + } else if istable(val) { + if istable(dv) { + coalesceTablesFullKey(printf, dv.(map[string]interface{}), val.(map[string]interface{}), fullkey, merge) + } else { + printf("warning: cannot overwrite table with non table for %s (%v)", fullkey, val) + } + } else if istable(dv) && val != nil { + printf("warning: destination for %s is a table. Ignoring non-table value (%v)", fullkey, val) + } + } + return dst +} + +// istable is a special-purpose function to see if the present thing matches the definition of a YAML table. +func istable(v interface{}) bool { + _, ok := v.(map[string]interface{}) + return ok +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/common/util/jsonschema.go b/vendor/helm.sh/helm/v4/pkg/chart/common/util/jsonschema.go new file mode 100644 index 00000000000..873c08fdd4f --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/common/util/jsonschema.go @@ -0,0 +1,216 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "bytes" + "crypto/tls" + "errors" + "fmt" + "log/slog" + "net/http" + "strings" + "sync" + "time" + + "github.com/santhosh-tekuri/jsonschema/v6" + + "helm.sh/helm/v4/internal/version" + + chart "helm.sh/helm/v4/pkg/chart" + "helm.sh/helm/v4/pkg/chart/common" +) + +// HTTPURLLoader implements a loader for HTTP/HTTPS URLs +type HTTPURLLoader http.Client + +func (l *HTTPURLLoader) Load(urlStr string) (any, error) { + client := (*http.Client)(l) + + req, err := http.NewRequest(http.MethodGet, urlStr, nil) + if err != nil { + return nil, fmt.Errorf("failed to create HTTP request for %s: %w", urlStr, err) + } + req.Header.Set("User-Agent", version.GetUserAgent()) + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("HTTP request failed for %s: %w", urlStr, err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP request to %s returned status %d (%s)", urlStr, resp.StatusCode, http.StatusText(resp.StatusCode)) + } + + return jsonschema.UnmarshalJSON(resp.Body) +} + +// newHTTPURLLoader creates a HTTP URL loader with proxy support. +func newHTTPURLLoader() *HTTPURLLoader { + httpLoader := HTTPURLLoader(http.Client{ + Timeout: 15 * time.Second, + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + TLSClientConfig: &tls.Config{}, + }, + }) + return &httpLoader +} + +// ValidateAgainstSchema checks that values does not violate the structure laid out in schema +func ValidateAgainstSchema(ch chart.Charter, values map[string]interface{}) error { + chrt, err := chart.NewAccessor(ch) + if err != nil { + return err + } + var sb strings.Builder + if chrt.Schema() != nil { + slog.Debug("chart name", "chart-name", chrt.Name()) + err := ValidateAgainstSingleSchema(values, chrt.Schema()) + if err != nil { + fmt.Fprintf(&sb, "%s:\n", chrt.Name()) + sb.WriteString(err.Error()) + } + } + slog.Debug("number of dependencies in the chart", "chart", chrt.Name(), "dependencies", len(chrt.Dependencies())) + // For each dependency, recursively call this function with the coalesced values + for _, subchart := range chrt.Dependencies() { + sub, err := chart.NewAccessor(subchart) + if err != nil { + return err + } + + raw, exists := values[sub.Name()] + if !exists || raw == nil { + // No values provided for this subchart; nothing to validate + continue + } + + subchartValues, ok := raw.(map[string]any) + if !ok { + fmt.Fprintf(&sb, "%s:\ninvalid type for values: expected object (map), got %T\n", + sub.Name(), raw) + continue + } + + if err := ValidateAgainstSchema(subchart, subchartValues); err != nil { + sb.WriteString(err.Error()) + } + } + + if sb.Len() > 0 { + return errors.New(sb.String()) + } + + return nil +} + +// ValidateAgainstSingleSchema checks that values does not violate the structure laid out in this schema +func ValidateAgainstSingleSchema(values common.Values, schemaJSON []byte) (reterr error) { + defer func() { + if r := recover(); r != nil { + reterr = fmt.Errorf("unable to validate schema: %s", r) + } + }() + + // This unmarshal function leverages UseNumber() for number precision. The parser + // used for values does this as well. + schema, err := jsonschema.UnmarshalJSON(bytes.NewReader(schemaJSON)) + if err != nil { + return err + } + slog.Debug("unmarshalled JSON schema", "schema", schemaJSON) + + // Configure compiler with loaders for different URL schemes + loader := jsonschema.SchemeURLLoader{ + "file": jsonschema.FileLoader{}, + "http": newHTTPURLLoader(), + "https": newHTTPURLLoader(), + "urn": urnLoader{}, + } + + compiler := jsonschema.NewCompiler() + compiler.UseLoader(loader) + err = compiler.AddResource("file:///values.schema.json", schema) + if err != nil { + return err + } + + validator, err := compiler.Compile("file:///values.schema.json") + if err != nil { + return err + } + + err = validator.Validate(values.AsMap()) + if err != nil { + return JSONSchemaValidationError{err} + } + + return nil +} + +// URNResolverFunc allows SDK to plug a URN resolver. It must return a +// schema document compatible with the validator (e.g., result of +// jsonschema.UnmarshalJSON). +type URNResolverFunc func(urn string) (any, error) + +// URNResolver is the default resolver used by the URN loader. By default it +// returns a clear error. +var URNResolver URNResolverFunc = func(urn string) (any, error) { + return nil, fmt.Errorf("URN not resolved: %s", urn) +} + +// urnLoader implements resolution for the urn: scheme by delegating to +// URNResolver. If unresolved, it logs a warning and returns a permissive +// boolean-true schema to avoid hard failures (back-compat behavior). +type urnLoader struct{} + +// warnedURNs ensures we log the unresolved-URN warning only once per URN. +var warnedURNs sync.Map + +func (l urnLoader) Load(urlStr string) (any, error) { + if doc, err := URNResolver(urlStr); err == nil && doc != nil { + return doc, nil + } + if _, loaded := warnedURNs.LoadOrStore(urlStr, struct{}{}); !loaded { + slog.Warn("unresolved URN reference ignored; using permissive schema", "urn", urlStr) + } + return jsonschema.UnmarshalJSON(strings.NewReader("true")) +} + +// Note, JSONSchemaValidationError is used to wrap the error from the underlying +// validation package so that Helm has a clean interface and the validation package +// could be replaced without changing the Helm SDK API. + +// JSONSchemaValidationError is the error returned when there is a schema validation +// error. +type JSONSchemaValidationError struct { + embeddedErr error +} + +// Error prints the error message +func (e JSONSchemaValidationError) Error() string { + errStr := e.embeddedErr.Error() + + // This string prefixes all of our error details. Further up the stack of helm error message + // building more detail is provided to users. This is removed. + errStr = strings.TrimPrefix(errStr, "jsonschema validation failed with 'file:///values.schema.json#'\n") + + // The extra new line is needed for when there are sub-charts. + return errStr + "\n" +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/common/util/values.go b/vendor/helm.sh/helm/v4/pkg/chart/common/util/values.go new file mode 100644 index 00000000000..85cb2901268 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/common/util/values.go @@ -0,0 +1,70 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + + "helm.sh/helm/v4/pkg/chart" + "helm.sh/helm/v4/pkg/chart/common" +) + +// ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files +// +// This takes both ReleaseOptions and Capabilities to merge into the render values. +func ToRenderValues(chrt chart.Charter, chrtVals map[string]interface{}, options common.ReleaseOptions, caps *common.Capabilities) (common.Values, error) { + return ToRenderValuesWithSchemaValidation(chrt, chrtVals, options, caps, false) +} + +// ToRenderValuesWithSchemaValidation composes the struct from the data coming from the Releases, Charts and Values files +// +// This takes both ReleaseOptions and Capabilities to merge into the render values. +func ToRenderValuesWithSchemaValidation(chrt chart.Charter, chrtVals map[string]interface{}, options common.ReleaseOptions, caps *common.Capabilities, skipSchemaValidation bool) (common.Values, error) { + if caps == nil { + caps = common.DefaultCapabilities + } + accessor, err := chart.NewAccessor(chrt) + if err != nil { + return nil, err + } + top := map[string]interface{}{ + "Chart": accessor.MetadataAsMap(), + "Capabilities": caps, + "Release": map[string]interface{}{ + "Name": options.Name, + "Namespace": options.Namespace, + "IsUpgrade": options.IsUpgrade, + "IsInstall": options.IsInstall, + "Revision": options.Revision, + "Service": "Helm", + }, + } + + vals, err := CoalesceValues(chrt, chrtVals) + if err != nil { + return common.Values(top), err + } + + if !skipSchemaValidation { + if err := ValidateAgainstSchema(chrt, vals); err != nil { + return top, fmt.Errorf("values don't meet the specifications of the schema(s) in the following chart(s):\n%w", err) + } + } + + top["Values"] = vals + return top, nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/common/values.go b/vendor/helm.sh/helm/v4/pkg/chart/common/values.go new file mode 100644 index 00000000000..94958a7791d --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/common/values.go @@ -0,0 +1,175 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import ( + "errors" + "io" + "os" + "strings" + + "sigs.k8s.io/yaml" +) + +// GlobalKey is the name of the Values key that is used for storing global vars. +const GlobalKey = "global" + +// Values represents a collection of chart values. +type Values map[string]interface{} + +// YAML encodes the Values into a YAML string. +func (v Values) YAML() (string, error) { + b, err := yaml.Marshal(v) + return string(b), err +} + +// Table gets a table (YAML subsection) from a Values object. +// +// The table is returned as a Values. +// +// Compound table names may be specified with dots: +// +// foo.bar +// +// The above will be evaluated as "The table bar inside the table +// foo". +// +// An ErrNoTable is returned if the table does not exist. +func (v Values) Table(name string) (Values, error) { + table := v + var err error + + for _, n := range parsePath(name) { + if table, err = tableLookup(table, n); err != nil { + break + } + } + return table, err +} + +// AsMap is a utility function for converting Values to a map[string]interface{}. +// +// It protects against nil map panics. +func (v Values) AsMap() map[string]interface{} { + if len(v) == 0 { + return map[string]interface{}{} + } + return v +} + +// Encode writes serialized Values information to the given io.Writer. +func (v Values) Encode(w io.Writer) error { + out, err := yaml.Marshal(v) + if err != nil { + return err + } + _, err = w.Write(out) + return err +} + +func tableLookup(v Values, simple string) (Values, error) { + v2, ok := v[simple] + if !ok { + return v, ErrNoTable{simple} + } + if vv, ok := v2.(map[string]interface{}); ok { + return vv, nil + } + + // This catches a case where a value is of type Values, but doesn't (for some + // reason) match the map[string]interface{}. This has been observed in the + // wild, and might be a result of a nil map of type Values. + if vv, ok := v2.(Values); ok { + return vv, nil + } + + return Values{}, ErrNoTable{simple} +} + +// ReadValues will parse YAML byte data into a Values. +func ReadValues(data []byte) (vals Values, err error) { + err = yaml.Unmarshal(data, &vals) + if len(vals) == 0 { + vals = Values{} + } + return vals, err +} + +// ReadValuesFile will parse a YAML file into a map of values. +func ReadValuesFile(filename string) (Values, error) { + data, err := os.ReadFile(filename) + if err != nil { + return map[string]interface{}{}, err + } + return ReadValues(data) +} + +// ReleaseOptions represents the additional release options needed +// for the composition of the final values struct +type ReleaseOptions struct { + Name string + Namespace string + Revision int + IsUpgrade bool + IsInstall bool +} + +// istable is a special-purpose function to see if the present thing matches the definition of a YAML table. +func istable(v interface{}) bool { + _, ok := v.(map[string]interface{}) + return ok +} + +// PathValue takes a path that traverses a YAML structure and returns the value at the end of that path. +// The path starts at the root of the YAML structure and is comprised of YAML keys separated by periods. +// Given the following YAML data the value at path "chapter.one.title" is "Loomings". +// +// chapter: +// one: +// title: "Loomings" +func (v Values) PathValue(path string) (interface{}, error) { + if path == "" { + return nil, errors.New("YAML path cannot be empty") + } + return v.pathValue(parsePath(path)) +} + +func (v Values) pathValue(path []string) (interface{}, error) { + if len(path) == 1 { + // if exists must be root key not table + if _, ok := v[path[0]]; ok && !istable(v[path[0]]) { + return v[path[0]], nil + } + return nil, ErrNoValue{path[0]} + } + + key, path := path[len(path)-1], path[:len(path)-1] + // get our table for table path + t, err := v.Table(joinPath(path...)) + if err != nil { + return nil, ErrNoValue{key} + } + // check table for key and ensure value is not a table + if k, ok := t[key]; ok && !istable(k) { + return k, nil + } + return nil, ErrNoValue{key} +} + +func parsePath(key string) []string { return strings.Split(key, ".") } + +func joinPath(path ...string) string { return strings.Join(path, ".") } diff --git a/vendor/helm.sh/helm/v4/pkg/chart/dependency.go b/vendor/helm.sh/helm/v4/pkg/chart/dependency.go new file mode 100644 index 00000000000..864fe6d2c98 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/dependency.go @@ -0,0 +1,64 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package chart + +import ( + "errors" + + v3chart "helm.sh/helm/v4/internal/chart/v3" + v2chart "helm.sh/helm/v4/pkg/chart/v2" +) + +var NewDependencyAccessor func(dep Dependency) (DependencyAccessor, error) = NewDefaultDependencyAccessor //nolint:revive + +func NewDefaultDependencyAccessor(dep Dependency) (DependencyAccessor, error) { + switch v := dep.(type) { + case v2chart.Dependency: + return &v2DependencyAccessor{&v}, nil + case *v2chart.Dependency: + return &v2DependencyAccessor{v}, nil + case v3chart.Dependency: + return &v3DependencyAccessor{&v}, nil + case *v3chart.Dependency: + return &v3DependencyAccessor{v}, nil + default: + return nil, errors.New("unsupported chart dependency type") + } +} + +type v2DependencyAccessor struct { + dep *v2chart.Dependency +} + +func (r *v2DependencyAccessor) Name() string { + return r.dep.Name +} + +func (r *v2DependencyAccessor) Alias() string { + return r.dep.Alias +} + +type v3DependencyAccessor struct { + dep *v3chart.Dependency +} + +func (r *v3DependencyAccessor) Name() string { + return r.dep.Name +} + +func (r *v3DependencyAccessor) Alias() string { + return r.dep.Alias +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/interfaces.go b/vendor/helm.sh/helm/v4/pkg/chart/interfaces.go new file mode 100644 index 00000000000..4001bc548dc --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/interfaces.go @@ -0,0 +1,44 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package chart + +import ( + common "helm.sh/helm/v4/pkg/chart/common" +) + +type Charter interface{} + +type Dependency interface{} + +type Accessor interface { + Name() string + IsRoot() bool + MetadataAsMap() map[string]interface{} + Files() []*common.File + Templates() []*common.File + ChartFullPath() string + IsLibraryChart() bool + Dependencies() []Charter + MetaDependencies() []Dependency + Values() map[string]interface{} + Schema() []byte + Deprecated() bool +} + +type DependencyAccessor interface { + Name() string + Alias() string +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/loader/archive/archive.go b/vendor/helm.sh/helm/v4/pkg/chart/loader/archive/archive.go new file mode 100644 index 00000000000..e98f5c33331 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/loader/archive/archive.go @@ -0,0 +1,197 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// archive provides utility functions for working with Helm chart archive files +package archive + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "errors" + "fmt" + "io" + "net/http" + "os" + "path" + "regexp" + "strings" + "time" +) + +// MaxDecompressedChartSize is the maximum size of a chart archive that will be +// decompressed. This is the decompressed size of all the files. +// The default value is 100 MiB. +var MaxDecompressedChartSize int64 = 100 * 1024 * 1024 // Default 100 MiB + +// MaxDecompressedFileSize is the size of the largest file that Helm will attempt to load. +// The size of the file is the decompressed version of it when it is stored in an archive. +var MaxDecompressedFileSize int64 = 5 * 1024 * 1024 // Default 5 MiB + +var drivePathPattern = regexp.MustCompile(`^[a-zA-Z]:/`) + +var utf8bom = []byte{0xEF, 0xBB, 0xBF} + +// BufferedFile represents an archive file buffered for later processing. +type BufferedFile struct { + Name string + ModTime time.Time + Data []byte +} + +// LoadArchiveFiles reads in files out of an archive into memory. This function +// performs important path security checks and should always be used before +// expanding a tarball +func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) { + unzipped, err := gzip.NewReader(in) + if err != nil { + return nil, err + } + defer unzipped.Close() + + files := []*BufferedFile{} + tr := tar.NewReader(unzipped) + remainingSize := MaxDecompressedChartSize + for { + b := bytes.NewBuffer(nil) + hd, err := tr.Next() + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return nil, err + } + + if hd.FileInfo().IsDir() { + // Use this instead of hd.Typeflag because we don't have to do any + // inference chasing. + continue + } + + switch hd.Typeflag { + // We don't want to process these extension header files. + case tar.TypeXGlobalHeader, tar.TypeXHeader: + continue + } + + // Archive could contain \ if generated on Windows + delimiter := "/" + if strings.ContainsRune(hd.Name, '\\') { + delimiter = "\\" + } + + parts := strings.Split(hd.Name, delimiter) + n := strings.Join(parts[1:], delimiter) + + // Normalize the path to the / delimiter + n = strings.ReplaceAll(n, delimiter, "/") + + if path.IsAbs(n) { + return nil, errors.New("chart illegally contains absolute paths") + } + + n = path.Clean(n) + if n == "." { + // In this case, the original path was relative when it should have been absolute. + return nil, fmt.Errorf("chart illegally contains content outside the base directory: %q", hd.Name) + } + if strings.HasPrefix(n, "..") { + return nil, errors.New("chart illegally references parent directory") + } + + // In some particularly arcane acts of path creativity, it is possible to intermix + // UNIX and Windows style paths in such a way that you produce a result of the form + // c:/foo even after all the built-in absolute path checks. So we explicitly check + // for this condition. + if drivePathPattern.MatchString(n) { + return nil, errors.New("chart contains illegally named files") + } + + if parts[0] == "Chart.yaml" { + return nil, errors.New("chart yaml not in base directory") + } + + if hd.Size > remainingSize { + return nil, fmt.Errorf("decompressed chart is larger than the maximum size %d", MaxDecompressedChartSize) + } + + if hd.Size > MaxDecompressedFileSize { + return nil, fmt.Errorf("decompressed chart file %q is larger than the maximum file size %d", hd.Name, MaxDecompressedFileSize) + } + + limitedReader := io.LimitReader(tr, remainingSize) + + bytesWritten, err := io.Copy(b, limitedReader) + if err != nil { + return nil, err + } + + remainingSize -= bytesWritten + // When the bytesWritten are less than the file size it means the limit reader ended + // copying early. Here we report that error. This is important if the last file extracted + // is the one that goes over the limit. It assumes the Size stored in the tar header + // is correct, something many applications do. + if bytesWritten < hd.Size || remainingSize <= 0 { + return nil, fmt.Errorf("decompressed chart is larger than the maximum size %d", MaxDecompressedChartSize) + } + + data := bytes.TrimPrefix(b.Bytes(), utf8bom) + + files = append(files, &BufferedFile{Name: n, ModTime: hd.ModTime, Data: data}) + b.Reset() + } + + if len(files) == 0 { + return nil, errors.New("no files in chart archive") + } + return files, nil +} + +// ensureArchive's job is to return an informative error if the file does not appear to be a gzipped archive. +// +// Sometimes users will provide a values.yaml for an argument where a chart is expected. One common occurrence +// of this is invoking `helm template values.yaml mychart` which would otherwise produce a confusing error +// if we didn't check for this. +func EnsureArchive(name string, raw *os.File) error { + defer raw.Seek(0, 0) // reset read offset to allow archive loading to proceed. + + // Check the file format to give us a chance to provide the user with more actionable feedback. + buffer := make([]byte, 512) + _, err := raw.Read(buffer) + if err != nil && err != io.EOF { + return fmt.Errorf("file '%s' cannot be read: %s", name, err) + } + + // Helm may identify achieve of the application/x-gzip as application/vnd.ms-fontobject. + // Fix for: https://github.com/helm/helm/issues/12261 + if contentType := http.DetectContentType(buffer); contentType != "application/x-gzip" && !isGZipApplication(buffer) { + // TODO: Is there a way to reliably test if a file content is YAML? ghodss/yaml accepts a wide + // variety of content (Makefile, .zshrc) as valid YAML without errors. + + // Wrong content type. Let's check if it's yaml and give an extra hint? + if strings.HasSuffix(name, ".yml") || strings.HasSuffix(name, ".yaml") { + return fmt.Errorf("file '%s' seems to be a YAML file, but expected a gzipped archive", name) + } + return fmt.Errorf("file '%s' does not appear to be a gzipped archive; got '%s'", name, contentType) + } + return nil +} + +// isGZipApplication checks whether the archive is of the application/x-gzip type. +func isGZipApplication(data []byte) bool { + sig := []byte("\x1F\x8B\x08") + return bytes.HasPrefix(data, sig) +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/loader/load.go b/vendor/helm.sh/helm/v4/pkg/chart/loader/load.go new file mode 100644 index 00000000000..3fd381825a8 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/loader/load.go @@ -0,0 +1,196 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package loader + +import ( + "compress/gzip" + "errors" + "fmt" + "io" + "os" + "path/filepath" + + "sigs.k8s.io/yaml" + + c3 "helm.sh/helm/v4/internal/chart/v3" + c3load "helm.sh/helm/v4/internal/chart/v3/loader" + "helm.sh/helm/v4/pkg/chart" + "helm.sh/helm/v4/pkg/chart/loader/archive" + c2 "helm.sh/helm/v4/pkg/chart/v2" + c2load "helm.sh/helm/v4/pkg/chart/v2/loader" +) + +// ChartLoader loads a chart. +type ChartLoader interface { + Load() (chart.Charter, error) +} + +// Loader returns a new ChartLoader appropriate for the given chart name +func Loader(name string) (ChartLoader, error) { + fi, err := os.Stat(name) + if err != nil { + return nil, err + } + if fi.IsDir() { + return DirLoader(name), nil + } + return FileLoader(name), nil +} + +// Load takes a string name, tries to resolve it to a file or directory, and then loads it. +// +// This is the preferred way to load a chart. It will discover the chart encoding +// and hand off to the appropriate chart reader. +// +// If a .helmignore file is present, the directory loader will skip loading any files +// matching it. But .helmignore is not evaluated when reading out of an archive. +func Load(name string) (chart.Charter, error) { + l, err := Loader(name) + if err != nil { + return nil, err + } + return l.Load() +} + +// DirLoader loads a chart from a directory +type DirLoader string + +// Load loads the chart +func (l DirLoader) Load() (chart.Charter, error) { + return LoadDir(string(l)) +} + +func LoadDir(dir string) (chart.Charter, error) { + topdir, err := filepath.Abs(dir) + if err != nil { + return nil, err + } + + name := filepath.Join(topdir, "Chart.yaml") + data, err := os.ReadFile(name) + if err != nil { + return nil, fmt.Errorf("unable to detect chart at %s: %w", name, err) + } + + c := new(chartBase) + err = yaml.Unmarshal(data, c) + if err != nil { + return nil, fmt.Errorf("cannot load Chart.yaml: %w", err) + } + + switch c.APIVersion { + case c2.APIVersionV1, c2.APIVersionV2, "": + return c2load.Load(dir) + case c3.APIVersionV3: + return c3load.Load(dir) + default: + return nil, errors.New("unsupported chart version") + } + +} + +// FileLoader loads a chart from a file +type FileLoader string + +// Load loads a chart +func (l FileLoader) Load() (chart.Charter, error) { + return LoadFile(string(l)) +} + +func LoadFile(name string) (chart.Charter, error) { + if fi, err := os.Stat(name); err != nil { + return nil, err + } else if fi.IsDir() { + return nil, errors.New("cannot load a directory") + } + + raw, err := os.Open(name) + if err != nil { + return nil, err + } + defer raw.Close() + + err = archive.EnsureArchive(name, raw) + if err != nil { + return nil, err + } + + files, err := archive.LoadArchiveFiles(raw) + if err != nil { + if errors.Is(err, gzip.ErrHeader) { + return nil, fmt.Errorf("file '%s' does not appear to be a valid chart file (details: %w)", name, err) + } + return nil, errors.New("unable to load chart archive") + } + + for _, f := range files { + if f.Name == "Chart.yaml" { + c := new(chartBase) + if err := yaml.Unmarshal(f.Data, c); err != nil { + return c, fmt.Errorf("cannot load Chart.yaml: %w", err) + } + switch c.APIVersion { + case c2.APIVersionV1, c2.APIVersionV2, "": + return c2load.Load(name) + case c3.APIVersionV3: + return c3load.Load(name) + default: + return nil, errors.New("unsupported chart version") + } + } + } + + return nil, errors.New("unable to detect chart version, no Chart.yaml found") +} + +// LoadArchive loads from a reader containing a compressed tar archive. +func LoadArchive(in io.Reader) (chart.Charter, error) { + // Note: This function is for use by SDK users such as Flux. + + files, err := archive.LoadArchiveFiles(in) + if err != nil { + if errors.Is(err, gzip.ErrHeader) { + return nil, fmt.Errorf("stream does not appear to be a valid chart file (details: %w)", err) + } + return nil, fmt.Errorf("unable to load chart archive: %w", err) + } + + for _, f := range files { + if f.Name == "Chart.yaml" { + c := new(chartBase) + if err := yaml.Unmarshal(f.Data, c); err != nil { + return c, fmt.Errorf("cannot load Chart.yaml: %w", err) + } + switch c.APIVersion { + case c2.APIVersionV1, c2.APIVersionV2, "": + return c2load.LoadFiles(files) + case c3.APIVersionV3: + return c3load.LoadFiles(files) + default: + return nil, errors.New("unsupported chart version") + } + } + } + + return nil, errors.New("unable to detect chart version, no Chart.yaml found") +} + +// chartBase is used to detect the API Version for the chart to run it through the +// loader for that type. +type chartBase struct { + APIVersion string `json:"apiVersion,omitempty"` +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/chart.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/chart.go new file mode 100644 index 00000000000..d77a53ddca0 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/chart.go @@ -0,0 +1,182 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "path/filepath" + "regexp" + "strings" + "time" + + "helm.sh/helm/v4/pkg/chart/common" +) + +// APIVersionV1 is the API version number for version 1. +const APIVersionV1 = "v1" + +// APIVersionV2 is the API version number for version 2. +const APIVersionV2 = "v2" + +// aliasNameFormat defines the characters that are legal in an alias name. +var aliasNameFormat = regexp.MustCompile("^[a-zA-Z0-9_-]+$") + +// Chart is a helm package that contains metadata, a default config, zero or more +// optionally parameterizable templates, and zero or more charts (dependencies). +type Chart struct { + // Raw contains the raw contents of the files originally contained in the chart archive. + // + // This should not be used except in special cases like `helm show values`, + // where we want to display the raw values, comments and all. + Raw []*common.File `json:"-"` + // Metadata is the contents of the Chartfile. + Metadata *Metadata `json:"metadata"` + // Lock is the contents of Chart.lock. + Lock *Lock `json:"lock"` + // Templates for this chart. + Templates []*common.File `json:"templates"` + // Values are default config for this chart. + Values map[string]interface{} `json:"values"` + // Schema is an optional JSON schema for imposing structure on Values + Schema []byte `json:"schema"` + // SchemaModTime the schema was last modified + SchemaModTime time.Time `json:"schemamodtime,omitempty"` + // Files are miscellaneous files in a chart archive, + // e.g. README, LICENSE, etc. + Files []*common.File `json:"files"` + // ModTime the chart metadata was last modified + ModTime time.Time `json:"modtime,omitzero"` + + parent *Chart + dependencies []*Chart +} + +type CRD struct { + // Name is the File.Name for the crd file + Name string + // Filename is the File obj Name including (sub-)chart.ChartFullPath + Filename string + // File is the File obj for the crd + File *common.File +} + +// SetDependencies replaces the chart dependencies. +func (ch *Chart) SetDependencies(charts ...*Chart) { + ch.dependencies = nil + ch.AddDependency(charts...) +} + +// Name returns the name of the chart. +func (ch *Chart) Name() string { + if ch.Metadata == nil { + return "" + } + return ch.Metadata.Name +} + +// AddDependency determines if the chart is a subchart. +func (ch *Chart) AddDependency(charts ...*Chart) { + for i, x := range charts { + charts[i].parent = ch + ch.dependencies = append(ch.dependencies, x) + } +} + +// Root finds the root chart. +func (ch *Chart) Root() *Chart { + if ch.IsRoot() { + return ch + } + return ch.Parent().Root() +} + +// Dependencies are the charts that this chart depends on. +func (ch *Chart) Dependencies() []*Chart { return ch.dependencies } + +// IsRoot determines if the chart is the root chart. +func (ch *Chart) IsRoot() bool { return ch.parent == nil } + +// Parent returns a subchart's parent chart. +func (ch *Chart) Parent() *Chart { return ch.parent } + +// ChartPath returns the full path to this chart in dot notation. +func (ch *Chart) ChartPath() string { + if !ch.IsRoot() { + return ch.Parent().ChartPath() + "." + ch.Name() + } + return ch.Name() +} + +// ChartFullPath returns the full path to this chart. +// Note that the path may not correspond to the path where the file can be found on the file system if the path +// points to an aliased subchart. +func (ch *Chart) ChartFullPath() string { + if !ch.IsRoot() { + return ch.Parent().ChartFullPath() + "/charts/" + ch.Name() + } + return ch.Name() +} + +// Validate validates the metadata. +func (ch *Chart) Validate() error { + return ch.Metadata.Validate() +} + +// AppVersion returns the appversion of the chart. +func (ch *Chart) AppVersion() string { + if ch.Metadata == nil { + return "" + } + return ch.Metadata.AppVersion +} + +// CRDs returns a list of File objects in the 'crds/' directory of a Helm chart. +// Deprecated: use CRDObjects() +func (ch *Chart) CRDs() []*common.File { + files := []*common.File{} + // Find all resources in the crds/ directory + for _, f := range ch.Files { + if strings.HasPrefix(f.Name, "crds/") && hasManifestExtension(f.Name) { + files = append(files, f) + } + } + // Get CRDs from dependencies, too. + for _, dep := range ch.Dependencies() { + files = append(files, dep.CRDs()...) + } + return files +} + +// CRDObjects returns a list of CRD objects in the 'crds/' directory of a Helm chart & subcharts +func (ch *Chart) CRDObjects() []CRD { + crds := []CRD{} + // Find all resources in the crds/ directory + for _, f := range ch.Files { + if strings.HasPrefix(f.Name, "crds/") && hasManifestExtension(f.Name) { + mycrd := CRD{Name: f.Name, Filename: filepath.Join(ch.ChartFullPath(), f.Name), File: f} + crds = append(crds, mycrd) + } + } + // Get CRDs from dependencies, too. + for _, dep := range ch.Dependencies() { + crds = append(crds, dep.CRDObjects()...) + } + return crds +} + +func hasManifestExtension(fname string) bool { + ext := filepath.Ext(fname) + return strings.EqualFold(ext, ".yaml") || strings.EqualFold(ext, ".yml") || strings.EqualFold(ext, ".json") +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/dependency.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/dependency.go new file mode 100644 index 00000000000..8a590a03652 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/dependency.go @@ -0,0 +1,82 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import "time" + +// Dependency describes a chart upon which another chart depends. +// +// Dependencies can be used to express developer intent, or to capture the state +// of a chart. +type Dependency struct { + // Name is the name of the dependency. + // + // This must mach the name in the dependency's Chart.yaml. + Name string `json:"name" yaml:"name"` + // Version is the version (range) of this chart. + // + // A lock file will always produce a single version, while a dependency + // may contain a semantic version range. + Version string `json:"version,omitempty" yaml:"version,omitempty"` + // The URL to the repository. + // + // Appending `index.yaml` to this string should result in a URL that can be + // used to fetch the repository index. + Repository string `json:"repository" yaml:"repository"` + // A yaml path that resolves to a boolean, used for enabling/disabling charts (e.g. subchart1.enabled ) + Condition string `json:"condition,omitempty" yaml:"condition,omitempty"` + // Tags can be used to group charts for enabling/disabling together + Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` + // Enabled bool determines if chart should be loaded + Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` + // ImportValues holds the mapping of source values to parent key to be imported. Each item can be a + // string or pair of child/parent sublist items. + ImportValues []interface{} `json:"import-values,omitempty" yaml:"import-values,omitempty"` + // Alias usable alias to be used for the chart + Alias string `json:"alias,omitempty" yaml:"alias,omitempty"` +} + +// Validate checks for common problems with the dependency datastructure in +// the chart. This check must be done at load time before the dependency's charts are +// loaded. +func (d *Dependency) Validate() error { + if d == nil { + return ValidationError("dependencies must not contain empty or null nodes") + } + d.Name = sanitizeString(d.Name) + d.Version = sanitizeString(d.Version) + d.Repository = sanitizeString(d.Repository) + d.Condition = sanitizeString(d.Condition) + for i := range d.Tags { + d.Tags[i] = sanitizeString(d.Tags[i]) + } + if d.Alias != "" && !aliasNameFormat.MatchString(d.Alias) { + return ValidationErrorf("dependency %q has disallowed characters in the alias", d.Name) + } + return nil +} + +// Lock is a lock file for dependencies. +// +// It represents the state that the dependencies should be in. +type Lock struct { + // Generated is the date the lock file was last generated. + Generated time.Time `json:"generated"` + // Digest is a hash of the dependencies in Chart.yaml. + Digest string `json:"digest"` + // Dependencies is the list of dependencies that this lock file has locked. + Dependencies []*Dependency `json:"dependencies"` +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/doc.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/doc.go new file mode 100644 index 00000000000..d36ca3ec40b --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/doc.go @@ -0,0 +1,23 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package v2 provides chart handling for apiVersion v1 and v2 charts + +This package and its sub-packages provide handling for apiVersion v1 and v2 charts. +The changes from v1 to v2 charts are minor and were able to be handled with minor +switches based on characteristics. +*/ +package v2 diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/errors.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/errors.go new file mode 100644 index 00000000000..eeef75315bb --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/errors.go @@ -0,0 +1,30 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import "fmt" + +// ValidationError represents a data validation error. +type ValidationError string + +func (v ValidationError) Error() string { + return "validation: " + string(v) +} + +// ValidationErrorf takes a message and formatting options and creates a ValidationError +func ValidationErrorf(msg string, args ...interface{}) ValidationError { + return ValidationError(fmt.Sprintf(msg, args...)) +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/lint.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/lint.go new file mode 100644 index 00000000000..1c871d936b4 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/lint.go @@ -0,0 +1,71 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package lint // import "helm.sh/helm/v4/pkg/chart/v2/lint" + +import ( + "path/filepath" + + "helm.sh/helm/v4/pkg/chart/common" + "helm.sh/helm/v4/pkg/chart/v2/lint/rules" + "helm.sh/helm/v4/pkg/chart/v2/lint/support" +) + +type linterOptions struct { + KubeVersion *common.KubeVersion + SkipSchemaValidation bool +} + +type LinterOption func(lo *linterOptions) + +func WithKubeVersion(kubeVersion *common.KubeVersion) LinterOption { + return func(lo *linterOptions) { + lo.KubeVersion = kubeVersion + } +} + +func WithSkipSchemaValidation(skipSchemaValidation bool) LinterOption { + return func(lo *linterOptions) { + lo.SkipSchemaValidation = skipSchemaValidation + } +} + +func RunAll(baseDir string, values map[string]interface{}, namespace string, options ...LinterOption) support.Linter { + + chartDir, _ := filepath.Abs(baseDir) + + lo := linterOptions{} + for _, option := range options { + option(&lo) + } + + result := support.Linter{ + ChartDir: chartDir, + } + + rules.Chartfile(&result) + rules.ValuesWithOverrides(&result, values, lo.SkipSchemaValidation) + rules.Templates( + &result, + namespace, + values, + rules.TemplateLinterKubeVersion(lo.KubeVersion), + rules.TemplateLinterSkipSchemaValidation(lo.SkipSchemaValidation)) + rules.Dependencies(&result) + rules.Crds(&result) + + return result +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/rules/chartfile.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/rules/chartfile.go new file mode 100644 index 00000000000..80636347701 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/rules/chartfile.go @@ -0,0 +1,236 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rules // import "helm.sh/helm/v4/pkg/chart/v2/lint/rules" + +import ( + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/Masterminds/semver/v3" + "github.com/asaskevich/govalidator" + "sigs.k8s.io/yaml" + + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/lint/support" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" +) + +// Chartfile runs a set of linter rules related to Chart.yaml file +func Chartfile(linter *support.Linter) { + chartFileName := "Chart.yaml" + chartPath := filepath.Join(linter.ChartDir, chartFileName) + + linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartYamlNotDirectory(chartPath)) + + chartFile, err := chartutil.LoadChartfile(chartPath) + validChartFile := linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartYamlFormat(err)) + + // Guard clause. Following linter rules require a parsable ChartFile + if !validChartFile { + return + } + + _, err = chartutil.StrictLoadChartfile(chartPath) + linter.RunLinterRule(support.WarningSev, chartFileName, validateChartYamlStrictFormat(err)) + + // type check for Chart.yaml . ignoring error as any parse + // errors would already be caught in the above load function + chartFileForTypeCheck, _ := loadChartFileForTypeCheck(chartPath) + + linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartName(chartFile)) + + // Chart metadata + linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartAPIVersion(chartFile)) + + linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartVersionType(chartFileForTypeCheck)) + linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartVersion(chartFile)) + linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartAppVersionType(chartFileForTypeCheck)) + linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartMaintainer(chartFile)) + linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartSources(chartFile)) + linter.RunLinterRule(support.InfoSev, chartFileName, validateChartIconPresence(chartFile)) + linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartIconURL(chartFile)) + linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartType(chartFile)) + linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartDependencies(chartFile)) + linter.RunLinterRule(support.WarningSev, chartFileName, validateChartVersionStrictSemVerV2(chartFile)) +} + +func validateChartVersionType(data map[string]interface{}) error { + return isStringValue(data, "version") +} + +func validateChartAppVersionType(data map[string]interface{}) error { + return isStringValue(data, "appVersion") +} + +func isStringValue(data map[string]interface{}, key string) error { + value, ok := data[key] + if !ok { + return nil + } + valueType := fmt.Sprintf("%T", value) + if valueType != "string" { + return fmt.Errorf("%s should be of type string but it's of type %s", key, valueType) + } + return nil +} + +func validateChartYamlNotDirectory(chartPath string) error { + fi, err := os.Stat(chartPath) + + if err == nil && fi.IsDir() { + return errors.New("should be a file, not a directory") + } + return nil +} + +func validateChartYamlFormat(chartFileError error) error { + if chartFileError != nil { + return fmt.Errorf("unable to parse YAML\n\t%w", chartFileError) + } + return nil +} + +func validateChartYamlStrictFormat(chartFileError error) error { + if chartFileError != nil { + return fmt.Errorf("failed to strictly parse chart metadata file\n\t%w", chartFileError) + } + return nil +} + +func validateChartName(cf *chart.Metadata) error { + if cf.Name == "" { + return errors.New("name is required") + } + name := filepath.Base(cf.Name) + if name != cf.Name { + return fmt.Errorf("chart name %q is invalid", cf.Name) + } + return nil +} + +func validateChartAPIVersion(cf *chart.Metadata) error { + if cf.APIVersion == "" { + return errors.New("apiVersion is required. The value must be either \"v1\" or \"v2\"") + } + + if cf.APIVersion != chart.APIVersionV1 && cf.APIVersion != chart.APIVersionV2 { + return fmt.Errorf("apiVersion '%s' is not valid. The value must be either \"v1\" or \"v2\"", cf.APIVersion) + } + + return nil +} + +func validateChartVersion(cf *chart.Metadata) error { + if cf.Version == "" { + return errors.New("version is required") + } + + version, err := semver.NewVersion(cf.Version) + if err != nil { + return fmt.Errorf("version '%s' is not a valid SemVer", cf.Version) + } + + c, err := semver.NewConstraint(">0.0.0-0") + if err != nil { + return err + } + valid, msg := c.Validate(version) + + if !valid && len(msg) > 0 { + return fmt.Errorf("version %v", msg[0]) + } + + return nil +} + +func validateChartVersionStrictSemVerV2(cf *chart.Metadata) error { + _, err := semver.StrictNewVersion(cf.Version) + + if err != nil { + return fmt.Errorf("version '%s' is not a valid SemVerV2", cf.Version) + } + + return nil +} + +func validateChartMaintainer(cf *chart.Metadata) error { + for _, maintainer := range cf.Maintainers { + if maintainer == nil { + return errors.New("a maintainer entry is empty") + } + if maintainer.Name == "" { + return errors.New("each maintainer requires a name") + } else if maintainer.Email != "" && !govalidator.IsEmail(maintainer.Email) { + return fmt.Errorf("invalid email '%s' for maintainer '%s'", maintainer.Email, maintainer.Name) + } else if maintainer.URL != "" && !govalidator.IsURL(maintainer.URL) { + return fmt.Errorf("invalid url '%s' for maintainer '%s'", maintainer.URL, maintainer.Name) + } + } + return nil +} + +func validateChartSources(cf *chart.Metadata) error { + for _, source := range cf.Sources { + if source == "" || !govalidator.IsRequestURL(source) { + return fmt.Errorf("invalid source URL '%s'", source) + } + } + return nil +} + +func validateChartIconPresence(cf *chart.Metadata) error { + if cf.Icon == "" { + return errors.New("icon is recommended") + } + return nil +} + +func validateChartIconURL(cf *chart.Metadata) error { + if cf.Icon != "" && !govalidator.IsRequestURL(cf.Icon) { + return fmt.Errorf("invalid icon URL '%s'", cf.Icon) + } + return nil +} + +func validateChartDependencies(cf *chart.Metadata) error { + if len(cf.Dependencies) > 0 && cf.APIVersion != chart.APIVersionV2 { + return fmt.Errorf("dependencies are not valid in the Chart file with apiVersion '%s'. They are valid in apiVersion '%s'", cf.APIVersion, chart.APIVersionV2) + } + return nil +} + +func validateChartType(cf *chart.Metadata) error { + if len(cf.Type) > 0 && cf.APIVersion != chart.APIVersionV2 { + return fmt.Errorf("chart type is not valid in apiVersion '%s'. It is valid in apiVersion '%s'", cf.APIVersion, chart.APIVersionV2) + } + return nil +} + +// loadChartFileForTypeCheck loads the Chart.yaml +// in a generic form of a map[string]interface{}, so that the type +// of the values can be checked +func loadChartFileForTypeCheck(filename string) (map[string]interface{}, error) { + b, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + y := make(map[string]interface{}) + err = yaml.Unmarshal(b, &y) + return y, err +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/rules/crds.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/rules/crds.go new file mode 100644 index 00000000000..4bb4d370b3d --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/rules/crds.go @@ -0,0 +1,115 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rules + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "strings" + + "k8s.io/apimachinery/pkg/util/yaml" + + "helm.sh/helm/v4/pkg/chart/v2/lint/support" + "helm.sh/helm/v4/pkg/chart/v2/loader" +) + +// Crds lints the CRDs in the Linter. +func Crds(linter *support.Linter) { + fpath := "crds/" + crdsPath := filepath.Join(linter.ChartDir, fpath) + + // crds directory is optional + if _, err := os.Stat(crdsPath); errors.Is(err, fs.ErrNotExist) { + return + } + + crdsDirValid := linter.RunLinterRule(support.ErrorSev, fpath, validateCrdsDir(crdsPath)) + if !crdsDirValid { + return + } + + // Load chart and parse CRDs + chart, err := loader.Load(linter.ChartDir) + + chartLoaded := linter.RunLinterRule(support.ErrorSev, fpath, err) + + if !chartLoaded { + return + } + + /* Iterate over all the CRDs to check: + 1. It is a YAML file and not a template + 2. The API version is apiextensions.k8s.io + 3. The kind is CustomResourceDefinition + */ + for _, crd := range chart.CRDObjects() { + fileName := crd.Name + fpath = fileName + + decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(crd.File.Data), 4096) + for { + var yamlStruct *k8sYamlStruct + + err := decoder.Decode(&yamlStruct) + if errors.Is(err, io.EOF) { + break + } + + // If YAML parsing fails here, it will always fail in the next block as well, so we should return here. + // This also confirms the YAML is not a template, since templates can't be decoded into a K8sYamlStruct. + if !linter.RunLinterRule(support.ErrorSev, fpath, validateYamlContent(err)) { + return + } + + if yamlStruct != nil { + linter.RunLinterRule(support.ErrorSev, fpath, validateCrdAPIVersion(yamlStruct)) + linter.RunLinterRule(support.ErrorSev, fpath, validateCrdKind(yamlStruct)) + } + } + } +} + +// Validation functions +func validateCrdsDir(crdsPath string) error { + fi, err := os.Stat(crdsPath) + if err != nil { + return err + } + if !fi.IsDir() { + return errors.New("not a directory") + } + return nil +} + +func validateCrdAPIVersion(obj *k8sYamlStruct) error { + if !strings.HasPrefix(obj.APIVersion, "apiextensions.k8s.io") { + return fmt.Errorf("apiVersion is not in 'apiextensions.k8s.io'") + } + return nil +} + +func validateCrdKind(obj *k8sYamlStruct) error { + if obj.Kind != "CustomResourceDefinition" { + return fmt.Errorf("object kind is not 'CustomResourceDefinition'") + } + return nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/rules/dependencies.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/rules/dependencies.go new file mode 100644 index 00000000000..d944a016d6f --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/rules/dependencies.go @@ -0,0 +1,101 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rules // import "helm.sh/helm/v4/pkg/chart/v2/lint/rules" + +import ( + "fmt" + "strings" + + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/lint/support" + "helm.sh/helm/v4/pkg/chart/v2/loader" +) + +// Dependencies runs lints against a chart's dependencies +// +// See https://github.com/helm/helm/issues/7910 +func Dependencies(linter *support.Linter) { + c, err := loader.LoadDir(linter.ChartDir) + if !linter.RunLinterRule(support.ErrorSev, "", validateChartFormat(err)) { + return + } + + linter.RunLinterRule(support.ErrorSev, linter.ChartDir, validateDependencyInMetadata(c)) + linter.RunLinterRule(support.ErrorSev, linter.ChartDir, validateDependenciesUnique(c)) + linter.RunLinterRule(support.WarningSev, linter.ChartDir, validateDependencyInChartsDir(c)) +} + +func validateChartFormat(chartError error) error { + if chartError != nil { + return fmt.Errorf("unable to load chart\n\t%w", chartError) + } + return nil +} + +func validateDependencyInChartsDir(c *chart.Chart) (err error) { + dependencies := map[string]struct{}{} + missing := []string{} + for _, dep := range c.Dependencies() { + dependencies[dep.Metadata.Name] = struct{}{} + } + for _, dep := range c.Metadata.Dependencies { + if _, ok := dependencies[dep.Name]; !ok { + missing = append(missing, dep.Name) + } + } + if len(missing) > 0 { + err = fmt.Errorf("chart directory is missing these dependencies: %s", strings.Join(missing, ",")) + } + return err +} + +func validateDependencyInMetadata(c *chart.Chart) (err error) { + dependencies := map[string]struct{}{} + missing := []string{} + for _, dep := range c.Metadata.Dependencies { + dependencies[dep.Name] = struct{}{} + } + for _, dep := range c.Dependencies() { + if _, ok := dependencies[dep.Metadata.Name]; !ok { + missing = append(missing, dep.Metadata.Name) + } + } + if len(missing) > 0 { + err = fmt.Errorf("chart metadata is missing these dependencies: %s", strings.Join(missing, ",")) + } + return err +} + +func validateDependenciesUnique(c *chart.Chart) (err error) { + dependencies := map[string]*chart.Dependency{} + shadowing := []string{} + + for _, dep := range c.Metadata.Dependencies { + key := dep.Name + if dep.Alias != "" { + key = dep.Alias + } + if dependencies[key] != nil { + shadowing = append(shadowing, key) + } + dependencies[key] = dep + } + if len(shadowing) > 0 { + err = fmt.Errorf("multiple dependencies with name or alias: %s", strings.Join(shadowing, ",")) + } + return err +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/rules/deprecations.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/rules/deprecations.go new file mode 100644 index 00000000000..7d52458694b --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/rules/deprecations.go @@ -0,0 +1,94 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rules // import "helm.sh/helm/v4/pkg/chart/v2/lint/rules" + +import ( + "fmt" + "strconv" + + "helm.sh/helm/v4/pkg/chart/common" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/endpoints/deprecation" + kscheme "k8s.io/client-go/kubernetes/scheme" +) + +// deprecatedAPIError indicates than an API is deprecated in Kubernetes +type deprecatedAPIError struct { + Deprecated string + Message string +} + +func (e deprecatedAPIError) Error() string { + msg := e.Message + return msg +} + +func validateNoDeprecations(resource *k8sYamlStruct, kubeVersion *common.KubeVersion) error { + // if `resource` does not have an APIVersion or Kind, we cannot test it for deprecation + if resource.APIVersion == "" { + return nil + } + if resource.Kind == "" { + return nil + } + + if kubeVersion == nil { + kubeVersion = &common.DefaultCapabilities.KubeVersion + } + + runtimeObject, err := resourceToRuntimeObject(resource) + if err != nil { + // do not error for non-kubernetes resources + if runtime.IsNotRegisteredError(err) { + return nil + } + return err + } + + kubeVersionMajor, err := strconv.Atoi(kubeVersion.Major) + if err != nil { + return err + } + kubeVersionMinor, err := strconv.Atoi(kubeVersion.Minor) + if err != nil { + return err + } + + if !deprecation.IsDeprecated(runtimeObject, kubeVersionMajor, kubeVersionMinor) { + return nil + } + gvk := fmt.Sprintf("%s %s", resource.APIVersion, resource.Kind) + return deprecatedAPIError{ + Deprecated: gvk, + Message: deprecation.WarningMessage(runtimeObject), + } +} + +func resourceToRuntimeObject(resource *k8sYamlStruct) (runtime.Object, error) { + scheme := runtime.NewScheme() + kscheme.AddToScheme(scheme) + + gvk := schema.FromAPIVersionAndKind(resource.APIVersion, resource.Kind) + out, err := scheme.New(gvk) + if err != nil { + return nil, err + } + out.GetObjectKind().SetGroupVersionKind(gvk) + return out, nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/rules/template.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/rules/template.go new file mode 100644 index 00000000000..43665aa3a23 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/rules/template.go @@ -0,0 +1,384 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rules + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "os" + "path" + "path/filepath" + "slices" + "strings" + + "k8s.io/apimachinery/pkg/api/validation" + apipath "k8s.io/apimachinery/pkg/api/validation/path" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apimachinery/pkg/util/yaml" + + "helm.sh/helm/v4/pkg/chart/common" + "helm.sh/helm/v4/pkg/chart/common/util" + "helm.sh/helm/v4/pkg/chart/v2/lint/support" + "helm.sh/helm/v4/pkg/chart/v2/loader" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/engine" +) + +// Templates lints the templates in the Linter. +func Templates(linter *support.Linter, namespace string, values map[string]any, options ...TemplateLinterOption) { + templateLinter := newTemplateLinter(linter, namespace, values, options...) + templateLinter.Lint() +} + +type TemplateLinterOption func(*templateLinter) + +func TemplateLinterKubeVersion(kubeVersion *common.KubeVersion) TemplateLinterOption { + return func(tl *templateLinter) { + tl.kubeVersion = kubeVersion + } +} + +func TemplateLinterSkipSchemaValidation(skipSchemaValidation bool) TemplateLinterOption { + return func(tl *templateLinter) { + tl.skipSchemaValidation = skipSchemaValidation + } +} + +func newTemplateLinter(linter *support.Linter, namespace string, values map[string]any, options ...TemplateLinterOption) templateLinter { + + result := templateLinter{ + linter: linter, + values: values, + namespace: namespace, + } + + for _, o := range options { + o(&result) + } + + return result +} + +type templateLinter struct { + linter *support.Linter + values map[string]any + namespace string + kubeVersion *common.KubeVersion + skipSchemaValidation bool +} + +func (t *templateLinter) Lint() { + templatesDir := "templates/" + templatesPath := filepath.Join(t.linter.ChartDir, templatesDir) + + templatesDirExists := t.linter.RunLinterRule(support.WarningSev, templatesDir, templatesDirExists(templatesPath)) + if !templatesDirExists { + return + } + + validTemplatesDir := t.linter.RunLinterRule(support.ErrorSev, templatesDir, validateTemplatesDir(templatesPath)) + if !validTemplatesDir { + return + } + + // Load chart and parse templates + chart, err := loader.Load(t.linter.ChartDir) + + chartLoaded := t.linter.RunLinterRule(support.ErrorSev, templatesDir, err) + + if !chartLoaded { + return + } + + options := common.ReleaseOptions{ + Name: "test-release", + Namespace: t.namespace, + } + + caps := common.DefaultCapabilities.Copy() + if t.kubeVersion != nil { + caps.KubeVersion = *t.kubeVersion + } + + // lint ignores import-values + // See https://github.com/helm/helm/issues/9658 + if err := chartutil.ProcessDependencies(chart, t.values); err != nil { + return + } + + cvals, err := util.CoalesceValues(chart, t.values) + if err != nil { + return + } + + valuesToRender, err := util.ToRenderValuesWithSchemaValidation(chart, cvals, options, caps, t.skipSchemaValidation) + if err != nil { + t.linter.RunLinterRule(support.ErrorSev, templatesDir, err) + return + } + var e engine.Engine + e.LintMode = true + renderedContentMap, err := e.Render(chart, valuesToRender) + + renderOk := t.linter.RunLinterRule(support.ErrorSev, templatesDir, err) + + if !renderOk { + return + } + + /* Iterate over all the templates to check: + - It is a .yaml file + - All the values in the template file is defined + - {{}} include | quote + - Generated content is a valid Yaml file + - Metadata.Namespace is not set + */ + for _, template := range chart.Templates { + fileName := template.Name + + t.linter.RunLinterRule(support.ErrorSev, fileName, validateAllowedExtension(fileName)) + + // We only apply the following lint rules to yaml files + if !isYamlFileExtension(fileName) { + continue + } + + // NOTE: disabled for now, Refs https://github.com/helm/helm/issues/1463 + // Check that all the templates have a matching value + // linter.RunLinterRule(support.WarningSev, fpath, validateNoMissingValues(templatesPath, valuesToRender, preExecutedTemplate)) + + // NOTE: disabled for now, Refs https://github.com/helm/helm/issues/1037 + // linter.RunLinterRule(support.WarningSev, fpath, validateQuotes(string(preExecutedTemplate))) + + renderedContent := renderedContentMap[path.Join(chart.Name(), fileName)] + if strings.TrimSpace(renderedContent) != "" { + t.linter.RunLinterRule(support.WarningSev, fileName, validateTopIndentLevel(renderedContent)) + + decoder := yaml.NewYAMLOrJSONDecoder(strings.NewReader(renderedContent), 4096) + + // Lint all resources if the file contains multiple documents separated by --- + for { + // Even though k8sYamlStruct only defines a few fields, an error in any other + // key will be raised as well + var yamlStruct *k8sYamlStruct + + err := decoder.Decode(&yamlStruct) + if errors.Is(err, io.EOF) { + break + } + + // If YAML linting fails here, it will always fail in the next block as well, so we should return here. + // fix https://github.com/helm/helm/issues/11391 + if !t.linter.RunLinterRule(support.ErrorSev, fileName, validateYamlContent(err)) { + return + } + if yamlStruct != nil { + // NOTE: set to warnings to allow users to support out-of-date kubernetes + // Refs https://github.com/helm/helm/issues/8596 + t.linter.RunLinterRule(support.WarningSev, fileName, validateMetadataName(yamlStruct)) + t.linter.RunLinterRule(support.WarningSev, fileName, validateNoDeprecations(yamlStruct, t.kubeVersion)) + + t.linter.RunLinterRule(support.ErrorSev, fileName, validateMatchSelector(yamlStruct, renderedContent)) + t.linter.RunLinterRule(support.ErrorSev, fileName, validateListAnnotations(yamlStruct, renderedContent)) + } + } + } + } +} + +// validateTopIndentLevel checks that the content does not start with an indent level > 0. +// +// This error can occur when a template accidentally inserts space. It can cause +// unpredictable errors depending on whether the text is normalized before being passed +// into the YAML parser. So we trap it here. +// +// See https://github.com/helm/helm/issues/8467 +func validateTopIndentLevel(content string) error { + // Read lines until we get to a non-empty one + scanner := bufio.NewScanner(bytes.NewBufferString(content)) + for scanner.Scan() { + line := scanner.Text() + // If line is empty, skip + if strings.TrimSpace(line) == "" { + continue + } + // If it starts with one or more spaces, this is an error + if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") { + return fmt.Errorf("document starts with an illegal indent: %q, which may cause parsing problems", line) + } + // Any other condition passes. + return nil + } + return scanner.Err() +} + +// Validation functions +func templatesDirExists(templatesPath string) error { + _, err := os.Stat(templatesPath) + if errors.Is(err, os.ErrNotExist) { + return errors.New("directory does not exist") + } + return nil +} + +func validateTemplatesDir(templatesPath string) error { + fi, err := os.Stat(templatesPath) + if err != nil { + return err + } + if !fi.IsDir() { + return errors.New("not a directory") + } + return nil +} + +func validateAllowedExtension(fileName string) error { + ext := filepath.Ext(fileName) + validExtensions := []string{".yaml", ".yml", ".tpl", ".txt"} + + if slices.Contains(validExtensions, ext) { + return nil + } + + return fmt.Errorf("file extension '%s' not valid. Valid extensions are .yaml, .yml, .tpl, or .txt", ext) +} + +func validateYamlContent(err error) error { + if err != nil { + return fmt.Errorf("unable to parse YAML: %w", err) + } + + return nil +} + +// validateMetadataName uses the correct validation function for the object +// Kind, or if not set, defaults to the standard definition of a subdomain in +// DNS (RFC 1123), used by most resources. +func validateMetadataName(obj *k8sYamlStruct) error { + fn := validateMetadataNameFunc(obj) + allErrs := field.ErrorList{} + for _, msg := range fn(obj.Metadata.Name, false) { + allErrs = append(allErrs, field.Invalid(field.NewPath("metadata").Child("name"), obj.Metadata.Name, msg)) + } + if len(allErrs) > 0 { + return fmt.Errorf("object name does not conform to Kubernetes naming requirements: %q: %w", obj.Metadata.Name, allErrs.ToAggregate()) + } + return nil +} + +// validateMetadataNameFunc will return a name validation function for the +// object kind, if defined below. +// +// Rules should match those set in the various api validations: +// https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/core/validation/validation.go#L205-L274 +// https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/apps/validation/validation.go#L39 +// ... +// +// Implementing here to avoid importing k/k. +// +// If no mapping is defined, returns NameIsDNSSubdomain. This is used by object +// kinds that don't have special requirements, so is the most likely to work if +// new kinds are added. +func validateMetadataNameFunc(obj *k8sYamlStruct) validation.ValidateNameFunc { + switch strings.ToLower(obj.Kind) { + case "pod", "node", "secret", "endpoints", "resourcequota", // core + "controllerrevision", "daemonset", "deployment", "replicaset", "statefulset", // apps + "autoscaler", // autoscaler + "cronjob", "job", // batch + "lease", // coordination + "endpointslice", // discovery + "networkpolicy", "ingress", // networking + "podsecuritypolicy", // policy + "priorityclass", // scheduling + "podpreset", // settings + "storageclass", "volumeattachment", "csinode": // storage + return validation.NameIsDNSSubdomain + case "service": + return validation.NameIsDNS1035Label + case "namespace": + return validation.ValidateNamespaceName + case "serviceaccount": + return validation.ValidateServiceAccountName + case "certificatesigningrequest": + // No validation. + // https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/certificates/validation/validation.go#L137-L140 + return func(_ string, _ bool) []string { return nil } + case "role", "clusterrole", "rolebinding", "clusterrolebinding": + // https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/rbac/validation/validation.go#L32-L34 + return func(name string, _ bool) []string { + return apipath.IsValidPathSegmentName(name) + } + default: + return validation.NameIsDNSSubdomain + } +} + +// validateMatchSelector ensures that template specs have a selector declared. +// See https://github.com/helm/helm/issues/1990 +func validateMatchSelector(yamlStruct *k8sYamlStruct, manifest string) error { + switch yamlStruct.Kind { + case "Deployment", "ReplicaSet", "DaemonSet", "StatefulSet": + // verify that matchLabels or matchExpressions is present + if !strings.Contains(manifest, "matchLabels") && !strings.Contains(manifest, "matchExpressions") { + return fmt.Errorf("a %s must contain matchLabels or matchExpressions, and %q does not", yamlStruct.Kind, yamlStruct.Metadata.Name) + } + } + return nil +} + +func validateListAnnotations(yamlStruct *k8sYamlStruct, manifest string) error { + if yamlStruct.Kind == "List" { + m := struct { + Items []struct { + Metadata struct { + Annotations map[string]string + } + } + }{} + + if err := yaml.Unmarshal([]byte(manifest), &m); err != nil { + return validateYamlContent(err) + } + + for _, i := range m.Items { + if _, ok := i.Metadata.Annotations["helm.sh/resource-policy"]; ok { + return errors.New("annotation 'helm.sh/resource-policy' within List objects are ignored") + } + } + } + return nil +} + +func isYamlFileExtension(fileName string) bool { + ext := strings.ToLower(filepath.Ext(fileName)) + return ext == ".yaml" || ext == ".yml" +} + +// k8sYamlStruct stubs a Kubernetes YAML file. +type k8sYamlStruct struct { + APIVersion string `json:"apiVersion"` + Kind string + Metadata k8sYamlMetadata +} + +type k8sYamlMetadata struct { + Namespace string + Name string +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/rules/values.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/rules/values.go new file mode 100644 index 00000000000..994a6a46314 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/rules/values.go @@ -0,0 +1,84 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rules + +import ( + "fmt" + "os" + "path/filepath" + + "helm.sh/helm/v4/pkg/chart/common" + "helm.sh/helm/v4/pkg/chart/common/util" + "helm.sh/helm/v4/pkg/chart/v2/lint/support" +) + +// ValuesWithOverrides tests the values.yaml file. +// +// If a schema is present in the chart, values are tested against that. Otherwise, +// they are only tested for well-formedness. +// +// If additional values are supplied, they are coalesced into the values in values.yaml. +func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]interface{}, skipSchemaValidation bool) { + file := "values.yaml" + vf := filepath.Join(linter.ChartDir, file) + fileExists := linter.RunLinterRule(support.InfoSev, file, validateValuesFileExistence(vf)) + + if !fileExists { + return + } + + linter.RunLinterRule(support.ErrorSev, file, validateValuesFile(vf, valueOverrides, skipSchemaValidation)) +} + +func validateValuesFileExistence(valuesPath string) error { + _, err := os.Stat(valuesPath) + if err != nil { + return fmt.Errorf("file does not exist") + } + return nil +} + +func validateValuesFile(valuesPath string, overrides map[string]interface{}, skipSchemaValidation bool) error { + values, err := common.ReadValuesFile(valuesPath) + if err != nil { + return fmt.Errorf("unable to parse YAML: %w", err) + } + + // Helm 3.0.0 carried over the values linting from Helm 2.x, which only tests the top + // level values against the top-level expectations. Subchart values are not linted. + // We could change that. For now, though, we retain that strategy, and thus can + // coalesce tables (like reuse-values does) instead of doing the full chart + // CoalesceValues + coalescedValues := util.CoalesceTables(make(map[string]interface{}, len(overrides)), overrides) + coalescedValues = util.CoalesceTables(coalescedValues, values) + + ext := filepath.Ext(valuesPath) + schemaPath := valuesPath[:len(valuesPath)-len(ext)] + ".schema.json" + schema, err := os.ReadFile(schemaPath) + if len(schema) == 0 { + return nil + } + if err != nil { + return err + } + + if !skipSchemaValidation { + return util.ValidateAgainstSingleSchema(coalescedValues, schema) + } + + return nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/support/doc.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/support/doc.go new file mode 100644 index 00000000000..7e050b8c2d6 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/support/doc.go @@ -0,0 +1,23 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package support contains tools for linting charts. + +Linting is the process of testing charts for errors or warnings regarding +formatting, compilation, or standards compliance. +*/ +package support // import "helm.sh/helm/v4/pkg/chart/v2/lint/support" diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/support/message.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/support/message.go new file mode 100644 index 00000000000..5efbc7a61ca --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/lint/support/message.go @@ -0,0 +1,76 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package support + +import "fmt" + +// Severity indicates the severity of a Message. +const ( + // UnknownSev indicates that the severity of the error is unknown, and should not stop processing. + UnknownSev = iota + // InfoSev indicates information, for example missing values.yaml file + InfoSev + // WarningSev indicates that something does not meet code standards, but will likely function. + WarningSev + // ErrorSev indicates that something will not likely function. + ErrorSev +) + +// sev matches the *Sev states. +var sev = []string{"UNKNOWN", "INFO", "WARNING", "ERROR"} + +// Linter encapsulates a linting run of a particular chart. +type Linter struct { + Messages []Message + // The highest severity of all the failing lint rules + HighestSeverity int + ChartDir string +} + +// Message describes an error encountered while linting. +type Message struct { + // Severity is one of the *Sev constants + Severity int + Path string + Err error +} + +func (m Message) Error() string { + return fmt.Sprintf("[%s] %s: %s", sev[m.Severity], m.Path, m.Err.Error()) +} + +// NewMessage creates a new Message struct +func NewMessage(severity int, path string, err error) Message { + return Message{Severity: severity, Path: path, Err: err} +} + +// RunLinterRule returns true if the validation passed +func (l *Linter) RunLinterRule(severity int, path string, err error) bool { + // severity is out of bound + if severity < 0 || severity >= len(sev) { + return false + } + + if err != nil { + l.Messages = append(l.Messages, NewMessage(severity, path, err)) + + if severity > l.HighestSeverity { + l.HighestSeverity = severity + } + } + return err == nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/loader/archive.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/loader/archive.go new file mode 100644 index 00000000000..c6885e12596 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/loader/archive.go @@ -0,0 +1,74 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package loader + +import ( + "compress/gzip" + "errors" + "fmt" + "io" + "os" + + "helm.sh/helm/v4/pkg/chart/loader/archive" + chart "helm.sh/helm/v4/pkg/chart/v2" +) + +// FileLoader loads a chart from a file +type FileLoader string + +// Load loads a chart +func (l FileLoader) Load() (*chart.Chart, error) { + return LoadFile(string(l)) +} + +// LoadFile loads from an archive file. +func LoadFile(name string) (*chart.Chart, error) { + if fi, err := os.Stat(name); err != nil { + return nil, err + } else if fi.IsDir() { + return nil, errors.New("cannot load a directory") + } + + raw, err := os.Open(name) + if err != nil { + return nil, err + } + defer raw.Close() + + err = archive.EnsureArchive(name, raw) + if err != nil { + return nil, err + } + + c, err := LoadArchive(raw) + if err != nil { + if errors.Is(err, gzip.ErrHeader) { + return nil, fmt.Errorf("file '%s' does not appear to be a valid chart file (details: %w)", name, err) + } + } + return c, err +} + +// LoadArchive loads from a reader containing a compressed tar archive. +func LoadArchive(in io.Reader) (*chart.Chart, error) { + files, err := archive.LoadArchiveFiles(in) + if err != nil { + return nil, err + } + + return LoadFiles(files) +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/loader/directory.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/loader/directory.go new file mode 100644 index 00000000000..82578d92410 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/loader/directory.go @@ -0,0 +1,122 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package loader + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "strings" + + "helm.sh/helm/v4/internal/sympath" + "helm.sh/helm/v4/pkg/chart/loader/archive" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/ignore" +) + +var utf8bom = []byte{0xEF, 0xBB, 0xBF} + +// DirLoader loads a chart from a directory +type DirLoader string + +// Load loads the chart +func (l DirLoader) Load() (*chart.Chart, error) { + return LoadDir(string(l)) +} + +// LoadDir loads from a directory. +// +// This loads charts only from directories. +func LoadDir(dir string) (*chart.Chart, error) { + topdir, err := filepath.Abs(dir) + if err != nil { + return nil, err + } + + // Just used for errors. + c := &chart.Chart{} + + rules := ignore.Empty() + ifile := filepath.Join(topdir, ignore.HelmIgnore) + if _, err := os.Stat(ifile); err == nil { + r, err := ignore.ParseFile(ifile) + if err != nil { + return c, err + } + rules = r + } + rules.AddDefaults() + + files := []*archive.BufferedFile{} + topdir += string(filepath.Separator) + + walk := func(name string, fi os.FileInfo, err error) error { + n := strings.TrimPrefix(name, topdir) + if n == "" { + // No need to process top level. Avoid bug with helmignore .* matching + // empty names. See issue 1779. + return nil + } + + // Normalize to / since it will also work on Windows + n = filepath.ToSlash(n) + + if err != nil { + return err + } + if fi.IsDir() { + // Directory-based ignore rules should involve skipping the entire + // contents of that directory. + if rules.Ignore(n, fi) { + return filepath.SkipDir + } + return nil + } + + // If a .helmignore file matches, skip this file. + if rules.Ignore(n, fi) { + return nil + } + + // Irregular files include devices, sockets, and other uses of files that + // are not regular files. In Go they have a file mode type bit set. + // See https://golang.org/pkg/os/#FileMode for examples. + if !fi.Mode().IsRegular() { + return fmt.Errorf("cannot load irregular file %s as it has file mode type bits set", name) + } + + if fi.Size() > archive.MaxDecompressedFileSize { + return fmt.Errorf("chart file %q is larger than the maximum file size %d", fi.Name(), archive.MaxDecompressedFileSize) + } + + data, err := os.ReadFile(name) + if err != nil { + return fmt.Errorf("error reading %s: %w", n, err) + } + + data = bytes.TrimPrefix(data, utf8bom) + + files = append(files, &archive.BufferedFile{Name: n, ModTime: fi.ModTime(), Data: data}) + return nil + } + if err = sympath.Walk(topdir, walk); err != nil { + return c, err + } + + return LoadFiles(files) +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/loader/load.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/loader/load.go new file mode 100644 index 00000000000..d466e247c94 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/loader/load.go @@ -0,0 +1,249 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package loader + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "log" + "maps" + "os" + "path/filepath" + "strings" + + utilyaml "k8s.io/apimachinery/pkg/util/yaml" + "sigs.k8s.io/yaml" + + "helm.sh/helm/v4/pkg/chart/common" + "helm.sh/helm/v4/pkg/chart/loader/archive" + chart "helm.sh/helm/v4/pkg/chart/v2" +) + +// ChartLoader loads a chart. +type ChartLoader interface { + Load() (*chart.Chart, error) +} + +// Loader returns a new ChartLoader appropriate for the given chart name +func Loader(name string) (ChartLoader, error) { + fi, err := os.Stat(name) + if err != nil { + return nil, err + } + if fi.IsDir() { + return DirLoader(name), nil + } + return FileLoader(name), nil +} + +// Load takes a string name, tries to resolve it to a file or directory, and then loads it. +// +// This is the preferred way to load a chart. It will discover the chart encoding +// and hand off to the appropriate chart reader. +// +// If a .helmignore file is present, the directory loader will skip loading any files +// matching it. But .helmignore is not evaluated when reading out of an archive. +func Load(name string) (*chart.Chart, error) { + l, err := Loader(name) + if err != nil { + return nil, err + } + return l.Load() +} + +// LoadFiles loads from in-memory files. +func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { + c := new(chart.Chart) + subcharts := make(map[string][]*archive.BufferedFile) + + // do not rely on assumed ordering of files in the chart and crash + // if Chart.yaml was not coming early enough to initialize metadata + for _, f := range files { + c.Raw = append(c.Raw, &common.File{Name: f.Name, ModTime: f.ModTime, Data: f.Data}) + if f.Name == "Chart.yaml" { + if c.Metadata == nil { + c.Metadata = new(chart.Metadata) + } + if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil { + return c, fmt.Errorf("cannot load Chart.yaml: %w", err) + } + // NOTE(bacongobbler): while the chart specification says that APIVersion must be set, + // Helm 2 accepted charts that did not provide an APIVersion in their chart metadata. + // Because of that, if APIVersion is unset, we should assume we're loading a v1 chart. + if c.Metadata.APIVersion == "" { + c.Metadata.APIVersion = chart.APIVersionV1 + } + c.ModTime = f.ModTime + } + } + for _, f := range files { + switch { + case f.Name == "Chart.yaml": + // already processed + continue + case f.Name == "Chart.lock": + c.Lock = new(chart.Lock) + if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil { + return c, fmt.Errorf("cannot load Chart.lock: %w", err) + } + case f.Name == "values.yaml": + values, err := LoadValues(bytes.NewReader(f.Data)) + if err != nil { + return c, fmt.Errorf("cannot load values.yaml: %w", err) + } + c.Values = values + case f.Name == "values.schema.json": + c.Schema = f.Data + c.SchemaModTime = f.ModTime + + // Deprecated: requirements.yaml is deprecated use Chart.yaml. + // We will handle it for you because we are nice people + case f.Name == "requirements.yaml": + if c.Metadata == nil { + c.Metadata = new(chart.Metadata) + } + if c.Metadata.APIVersion != chart.APIVersionV1 { + log.Printf("Warning: Dependencies are handled in Chart.yaml since apiVersion \"v2\". We recommend migrating dependencies to Chart.yaml.") + } + if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil { + return c, fmt.Errorf("cannot load requirements.yaml: %w", err) + } + if c.Metadata.APIVersion == chart.APIVersionV1 { + c.Files = append(c.Files, &common.File{Name: f.Name, ModTime: f.ModTime, Data: f.Data}) + } + // Deprecated: requirements.lock is deprecated use Chart.lock. + case f.Name == "requirements.lock": + c.Lock = new(chart.Lock) + if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil { + return c, fmt.Errorf("cannot load requirements.lock: %w", err) + } + if c.Metadata == nil { + c.Metadata = new(chart.Metadata) + } + if c.Metadata.APIVersion != chart.APIVersionV1 { + log.Printf("Warning: Dependency locking is handled in Chart.lock since apiVersion \"v2\". We recommend migrating to Chart.lock.") + } + if c.Metadata.APIVersion == chart.APIVersionV1 { + c.Files = append(c.Files, &common.File{Name: f.Name, ModTime: f.ModTime, Data: f.Data}) + } + + case strings.HasPrefix(f.Name, "templates/"): + c.Templates = append(c.Templates, &common.File{Name: f.Name, ModTime: f.ModTime, Data: f.Data}) + case strings.HasPrefix(f.Name, "charts/"): + if filepath.Ext(f.Name) == ".prov" { + c.Files = append(c.Files, &common.File{Name: f.Name, ModTime: f.ModTime, Data: f.Data}) + continue + } + + fname := strings.TrimPrefix(f.Name, "charts/") + cname := strings.SplitN(fname, "/", 2)[0] + subcharts[cname] = append(subcharts[cname], &archive.BufferedFile{Name: fname, ModTime: f.ModTime, Data: f.Data}) + default: + c.Files = append(c.Files, &common.File{Name: f.Name, ModTime: f.ModTime, Data: f.Data}) + } + } + + if c.Metadata == nil { + return c, errors.New("Chart.yaml file is missing") //nolint:staticcheck + } + + if err := c.Validate(); err != nil { + return c, err + } + + for n, files := range subcharts { + var sc *chart.Chart + var err error + switch { + case strings.IndexAny(n, "_.") == 0: + continue + case filepath.Ext(n) == ".tgz": + file := files[0] + if file.Name != n { + return c, fmt.Errorf("error unpacking subchart tar in %s: expected %s, got %s", c.Name(), n, file.Name) + } + // Untar the chart and add to c.Dependencies + sc, err = LoadArchive(bytes.NewBuffer(file.Data)) + default: + // We have to trim the prefix off of every file, and ignore any file + // that is in charts/, but isn't actually a chart. + buff := make([]*archive.BufferedFile, 0, len(files)) + for _, f := range files { + parts := strings.SplitN(f.Name, "/", 2) + if len(parts) < 2 { + continue + } + f.Name = parts[1] + buff = append(buff, f) + } + sc, err = LoadFiles(buff) + } + + if err != nil { + return c, fmt.Errorf("error unpacking subchart %s in %s: %w", n, c.Name(), err) + } + c.AddDependency(sc) + } + + return c, nil +} + +// LoadValues loads values from a reader. +// +// The reader is expected to contain one or more YAML documents, the values of which are merged. +// And the values can be either a chart's default values or user-supplied values. +func LoadValues(data io.Reader) (map[string]interface{}, error) { + values := map[string]interface{}{} + reader := utilyaml.NewYAMLReader(bufio.NewReader(data)) + for { + currentMap := map[string]interface{}{} + raw, err := reader.Read() + if err != nil { + if errors.Is(err, io.EOF) { + break + } + return nil, fmt.Errorf("error reading yaml document: %w", err) + } + if err := yaml.Unmarshal(raw, ¤tMap); err != nil { + return nil, fmt.Errorf("cannot unmarshal yaml document: %w", err) + } + values = MergeMaps(values, currentMap) + } + return values, nil +} + +// MergeMaps merges two maps. If a key exists in both maps, the value from b will be used. +// If the value is a map, the maps will be merged recursively. +func MergeMaps(a, b map[string]interface{}) map[string]interface{} { + out := make(map[string]interface{}, len(a)) + maps.Copy(out, a) + for k, v := range b { + if v, ok := v.(map[string]interface{}); ok { + if bv, ok := out[k]; ok { + if bv, ok := bv.(map[string]interface{}); ok { + out[k] = MergeMaps(bv, v) + continue + } + } + } + out[k] = v + } + return out +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/metadata.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/metadata.go new file mode 100644 index 00000000000..5df3073219c --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/metadata.go @@ -0,0 +1,181 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "path/filepath" + "strings" + "unicode" + + "github.com/Masterminds/semver/v3" +) + +// Maintainer describes a Chart maintainer. +type Maintainer struct { + // Name is a user name or organization name + Name string `json:"name,omitempty"` + // Email is an optional email address to contact the named maintainer + Email string `json:"email,omitempty"` + // URL is an optional URL to an address for the named maintainer + URL string `json:"url,omitempty"` +} + +// Validate checks valid data and sanitizes string characters. +func (m *Maintainer) Validate() error { + if m == nil { + return ValidationError("maintainers must not contain empty or null nodes") + } + m.Name = sanitizeString(m.Name) + m.Email = sanitizeString(m.Email) + m.URL = sanitizeString(m.URL) + return nil +} + +// Metadata for a Chart file. This models the structure of a Chart.yaml file. +type Metadata struct { + // The name of the chart. Required. + Name string `json:"name,omitempty"` + // The URL to a relevant project page, git repo, or contact person + Home string `json:"home,omitempty"` + // Source is the URL to the source code of this chart + Sources []string `json:"sources,omitempty"` + // A version string of the chart. Required. + Version string `json:"version,omitempty"` + // A one-sentence description of the chart + Description string `json:"description,omitempty"` + // A list of string keywords + Keywords []string `json:"keywords,omitempty"` + // A list of name and URL/email address combinations for the maintainer(s) + Maintainers []*Maintainer `json:"maintainers,omitempty"` + // The URL to an icon file. + Icon string `json:"icon,omitempty"` + // The API Version of this chart. Required. + APIVersion string `json:"apiVersion,omitempty"` + // The condition to check to enable chart + Condition string `json:"condition,omitempty"` + // The tags to check to enable chart + Tags string `json:"tags,omitempty"` + // The version of the application enclosed inside of this chart. + AppVersion string `json:"appVersion,omitempty"` + // Whether or not this chart is deprecated + Deprecated bool `json:"deprecated,omitempty"` + // Annotations are additional mappings uninterpreted by Helm, + // made available for inspection by other applications. + Annotations map[string]string `json:"annotations,omitempty"` + // KubeVersion is a SemVer constraint specifying the version of Kubernetes required. + KubeVersion string `json:"kubeVersion,omitempty"` + // Dependencies are a list of dependencies for a chart. + Dependencies []*Dependency `json:"dependencies,omitempty"` + // Specifies the chart type: application or library + Type string `json:"type,omitempty"` +} + +// Validate checks the metadata for known issues and sanitizes string +// characters. +func (md *Metadata) Validate() error { + if md == nil { + return ValidationError("chart.metadata is required") + } + + md.Name = sanitizeString(md.Name) + md.Description = sanitizeString(md.Description) + md.Home = sanitizeString(md.Home) + md.Icon = sanitizeString(md.Icon) + md.Condition = sanitizeString(md.Condition) + md.Tags = sanitizeString(md.Tags) + md.AppVersion = sanitizeString(md.AppVersion) + md.KubeVersion = sanitizeString(md.KubeVersion) + for i := range md.Sources { + md.Sources[i] = sanitizeString(md.Sources[i]) + } + for i := range md.Keywords { + md.Keywords[i] = sanitizeString(md.Keywords[i]) + } + + if md.APIVersion == "" { + return ValidationError("chart.metadata.apiVersion is required") + } + if md.Name == "" { + return ValidationError("chart.metadata.name is required") + } + + if md.Name == "." || md.Name == ".." { + return ValidationErrorf("chart.metadata.name %q is not allowed", md.Name) + } + if md.Name != filepath.Base(md.Name) { + return ValidationErrorf("chart.metadata.name %q is invalid", md.Name) + } + + if md.Version == "" { + return ValidationError("chart.metadata.version is required") + } + if !isValidSemver(md.Version) { + return ValidationErrorf("chart.metadata.version %q is invalid", md.Version) + } + if !isValidChartType(md.Type) { + return ValidationError("chart.metadata.type must be application or library") + } + + for _, m := range md.Maintainers { + if err := m.Validate(); err != nil { + return err + } + } + + // Aliases need to be validated here to make sure that the alias name does + // not contain any illegal characters. + dependencies := map[string]*Dependency{} + for _, dependency := range md.Dependencies { + if err := dependency.Validate(); err != nil { + return err + } + key := dependency.Name + if dependency.Alias != "" { + key = dependency.Alias + } + if dependencies[key] != nil { + return ValidationErrorf("more than one dependency with name or alias %q", key) + } + dependencies[key] = dependency + } + return nil +} + +func isValidChartType(in string) bool { + switch in { + case "", "application", "library": + return true + } + return false +} + +func isValidSemver(v string) bool { + _, err := semver.NewVersion(v) + return err == nil +} + +// sanitizeString normalize spaces and removes non-printable characters. +func sanitizeString(str string) string { + return strings.Map(func(r rune) rune { + if unicode.IsSpace(r) { + return ' ' + } + if unicode.IsPrint(r) { + return r + } + return -1 + }, str) +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/util/chartfile.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/util/chartfile.go new file mode 100644 index 00000000000..1f9c712b2df --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/util/chartfile.go @@ -0,0 +1,105 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + + "sigs.k8s.io/yaml" + + chart "helm.sh/helm/v4/pkg/chart/v2" +) + +// LoadChartfile loads a Chart.yaml file into a *chart.Metadata. +func LoadChartfile(filename string) (*chart.Metadata, error) { + b, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + y := new(chart.Metadata) + err = yaml.Unmarshal(b, y) + return y, err +} + +// StrictLoadChartfile loads a Chart.yaml into a *chart.Metadata using a strict unmarshaling +func StrictLoadChartfile(filename string) (*chart.Metadata, error) { + b, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + y := new(chart.Metadata) + err = yaml.UnmarshalStrict(b, y) + return y, err +} + +// SaveChartfile saves the given metadata as a Chart.yaml file at the given path. +// +// 'filename' should be the complete path and filename ('foo/Chart.yaml') +func SaveChartfile(filename string, cf *chart.Metadata) error { + // Pull out the dependencies of a v1 Chart, since there's no way + // to tell the serializer to skip a field for just this use case + savedDependencies := cf.Dependencies + if cf.APIVersion == chart.APIVersionV1 { + cf.Dependencies = nil + } + out, err := yaml.Marshal(cf) + if cf.APIVersion == chart.APIVersionV1 { + cf.Dependencies = savedDependencies + } + if err != nil { + return err + } + return os.WriteFile(filename, out, 0644) +} + +// IsChartDir validate a chart directory. +// +// Checks for a valid Chart.yaml. +func IsChartDir(dirName string) (bool, error) { + if fi, err := os.Stat(dirName); err != nil { + return false, err + } else if !fi.IsDir() { + return false, fmt.Errorf("%q is not a directory", dirName) + } + + chartYaml := filepath.Join(dirName, ChartfileName) + if _, err := os.Stat(chartYaml); errors.Is(err, fs.ErrNotExist) { + return false, fmt.Errorf("no %s exists in directory %q", ChartfileName, dirName) + } + + chartYamlContent, err := os.ReadFile(chartYaml) + if err != nil { + return false, fmt.Errorf("cannot read %s in directory %q", ChartfileName, dirName) + } + + chartContent := new(chart.Metadata) + if err := yaml.Unmarshal(chartYamlContent, &chartContent); err != nil { + return false, err + } + if chartContent == nil { + return false, fmt.Errorf("chart metadata (%s) missing", ChartfileName) + } + if chartContent.Name == "" { + return false, fmt.Errorf("invalid chart (%s): name must not be empty", ChartfileName) + } + + return true, nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/util/compatible.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/util/compatible.go new file mode 100644 index 00000000000..d384d2d4528 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/util/compatible.go @@ -0,0 +1,34 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import "github.com/Masterminds/semver/v3" + +// IsCompatibleRange compares a version to a constraint. +// It returns true if the version matches the constraint, and false in all other cases. +func IsCompatibleRange(constraint, ver string) bool { + sv, err := semver.NewVersion(ver) + if err != nil { + return false + } + + c, err := semver.NewConstraint(constraint) + if err != nil { + return false + } + return c.Check(sv) +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/util/create.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/util/create.go new file mode 100644 index 00000000000..bf572c7074e --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/util/create.go @@ -0,0 +1,833 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + "io" + "os" + "path/filepath" + "regexp" + "strings" + + "sigs.k8s.io/yaml" + + "helm.sh/helm/v4/pkg/chart/common" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" +) + +// chartName is a regular expression for testing the supplied name of a chart. +// This regular expression is probably stricter than it needs to be. We can relax it +// somewhat. Newline characters, as well as $, quotes, +, parens, and % are known to be +// problematic. +var chartName = regexp.MustCompile("^[a-zA-Z0-9._-]+$") + +const ( + // ChartfileName is the default Chart file name. + ChartfileName = "Chart.yaml" + // ValuesfileName is the default values file name. + ValuesfileName = "values.yaml" + // SchemafileName is the default values schema file name. + SchemafileName = "values.schema.json" + // TemplatesDir is the relative directory name for templates. + TemplatesDir = "templates" + // ChartsDir is the relative directory name for charts dependencies. + ChartsDir = "charts" + // TemplatesTestsDir is the relative directory name for tests. + TemplatesTestsDir = TemplatesDir + sep + "tests" + // IgnorefileName is the name of the Helm ignore file. + IgnorefileName = ".helmignore" + // IngressFileName is the name of the example ingress file. + IngressFileName = TemplatesDir + sep + "ingress.yaml" + // HTTPRouteFileName is the name of the example HTTPRoute file. + HTTPRouteFileName = TemplatesDir + sep + "httproute.yaml" + // DeploymentName is the name of the example deployment file. + DeploymentName = TemplatesDir + sep + "deployment.yaml" + // ServiceName is the name of the example service file. + ServiceName = TemplatesDir + sep + "service.yaml" + // ServiceAccountName is the name of the example serviceaccount file. + ServiceAccountName = TemplatesDir + sep + "serviceaccount.yaml" + // HorizontalPodAutoscalerName is the name of the example hpa file. + HorizontalPodAutoscalerName = TemplatesDir + sep + "hpa.yaml" + // NotesName is the name of the example NOTES.txt file. + NotesName = TemplatesDir + sep + "NOTES.txt" + // HelpersName is the name of the example helpers file. + HelpersName = TemplatesDir + sep + "_helpers.tpl" + // TestConnectionName is the name of the example test file. + TestConnectionName = TemplatesTestsDir + sep + "test-connection.yaml" +) + +// maxChartNameLength is lower than the limits we know of with certain file systems, +// and with certain Kubernetes fields. +const maxChartNameLength = 250 + +const sep = string(filepath.Separator) + +const defaultChartfile = `apiVersion: v2 +name: %s +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" +` + +const defaultValues = `# Default values for %s. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/ +replicaCount: 1 + +# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/ +image: + repository: nginx + # This sets the pull policy for images. + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ +imagePullSecrets: [] +# This is to override the chart name. +nameOverride: "" +fullnameOverride: "" + +# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/ +serviceAccount: + # Specifies whether a service account should be created. + create: true + # Automatically mount a ServiceAccount's API credentials? + automount: true + # Annotations to add to the service account. + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template. + name: "" + +# This is for setting Kubernetes Annotations to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +podAnnotations: {} +# This is for setting Kubernetes Labels to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +podLabels: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/ +service: + # This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + type: ClusterIP + # This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports + port: 80 + +# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/ +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +# -- Expose the service via gateway-api HTTPRoute +# Requires Gateway API resources and suitable controller installed within the cluster +# (see: https://gateway-api.sigs.k8s.io/guides/) +httpRoute: + # HTTPRoute enabled. + enabled: false + # HTTPRoute annotations. + annotations: {} + # Which Gateways this Route is attached to. + parentRefs: + - name: gateway + sectionName: http + # namespace: default + # Hostnames matching HTTP header. + hostnames: + - chart-example.local + # List of rules and filters applied. + rules: + - matches: + - path: + type: PathPrefix + value: /headers + # filters: + # - type: RequestHeaderModifier + # requestHeaderModifier: + # set: + # - name: My-Overwrite-Header + # value: this-is-the-only-value + # remove: + # - User-Agent + # - matches: + # - path: + # type: PathPrefix + # value: /echo + # headers: + # - name: version + # value: v2 + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ +livenessProbe: + httpGet: + path: / + port: http +readinessProbe: + httpGet: + path: / + port: http + +# This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +# Additional volumes on the output Deployment definition. +volumes: [] + # - name: foo + # secret: + # secretName: mysecret + # optional: false + +# Additional volumeMounts on the output Deployment definition. +volumeMounts: [] + # - name: foo + # mountPath: "/etc/foo" + # readOnly: true + +nodeSelector: {} + +tolerations: [] + +affinity: {} +` + +const defaultIgnore = `# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ +` + +const defaultIngress = `{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include ".fullname" . }} + labels: + {{- include ".labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- with .pathType }} + pathType: {{ . }} + {{- end }} + backend: + service: + name: {{ include ".fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} +` + +const defaultHTTPRoute = `{{- if .Values.httpRoute.enabled -}} +{{- $fullName := include ".fullname" . -}} +{{- $svcPort := .Values.service.port -}} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ $fullName }} + labels: + {{- include ".labels" . | nindent 4 }} + {{- with .Values.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- with .Values.httpRoute.parentRefs }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.httpRoute.hostnames }} + hostnames: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range .Values.httpRoute.rules }} + {{- with .matches }} + - matches: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .filters }} + filters: + {{- toYaml . | nindent 8 }} + {{- end }} + backendRefs: + - name: {{ $fullName }} + port: {{ $svcPort }} + weight: 1 + {{- end }} +{{- end }} +` + +const defaultDeployment = `apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include ".fullname" . }} + labels: + {{- include ".labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include ".selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include ".labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include ".serviceAccountName" . }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + {{- with .Values.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +` + +const defaultService = `apiVersion: v1 +kind: Service +metadata: + name: {{ include ".fullname" . }} + labels: + {{- include ".labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include ".selectorLabels" . | nindent 4 }} +` + +const defaultServiceAccount = `{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include ".serviceAccountName" . }} + labels: + {{- include ".labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} +` + +const defaultHorizontalPodAutoscaler = `{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include ".fullname" . }} + labels: + {{- include ".labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include ".fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} +` + +const defaultNotes = `1. Get the application URL by running these commands: +{{- if .Values.httpRoute.enabled }} +{{- if .Values.httpRoute.hostnames }} + export APP_HOSTNAME={{ .Values.httpRoute.hostnames | first }} +{{- else }} + export APP_HOSTNAME=$(kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o jsonpath="{.spec.listeners[0].hostname}") + {{- end }} +{{- if and .Values.httpRoute.rules (first .Values.httpRoute.rules).matches (first (first .Values.httpRoute.rules).matches).path.value }} + echo "Visit http://$APP_HOSTNAME{{ (first (first .Values.httpRoute.rules).matches).path.value }} to use your application" + + NOTE: Your HTTPRoute depends on the listener configuration of your gateway and your HTTPRoute rules. + The rules can be set for path, method, header and query parameters. + You can check the gateway configuration with 'kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o yaml' +{{- end }} +{{- else if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include ".fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include ".fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include ".fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include ".name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} +` + +const defaultHelpers = `{{/* +Expand the name of the chart. +*/}} +{{- define ".name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define ".fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define ".chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define ".labels" -}} +helm.sh/chart: {{ include ".chart" . }} +{{ include ".selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define ".selectorLabels" -}} +app.kubernetes.io/name: {{ include ".name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define ".serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include ".fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} +` + +const defaultTestConnection = `apiVersion: v1 +kind: Pod +metadata: + name: "{{ include ".fullname" . }}-test-connection" + labels: + {{- include ".labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include ".fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never +` + +// Stderr is an io.Writer to which error messages can be written +// +// In Helm 4, this will be replaced. It is needed in Helm 3 to preserve API backward +// compatibility. +var Stderr io.Writer = os.Stderr + +// CreateFrom creates a new chart, but scaffolds it from the src chart. +func CreateFrom(chartfile *chart.Metadata, dest, src string) error { + schart, err := loader.Load(src) + if err != nil { + return fmt.Errorf("could not load %s: %w", src, err) + } + + schart.Metadata = chartfile + + var updatedTemplates []*common.File + + for _, template := range schart.Templates { + newData := transform(string(template.Data), schart.Name()) + updatedTemplates = append(updatedTemplates, &common.File{Name: template.Name, ModTime: template.ModTime, Data: newData}) + } + + schart.Templates = updatedTemplates + b, err := yaml.Marshal(schart.Values) + if err != nil { + return fmt.Errorf("reading values file: %w", err) + } + + var m map[string]interface{} + if err := yaml.Unmarshal(transform(string(b), schart.Name()), &m); err != nil { + return fmt.Errorf("transforming values file: %w", err) + } + schart.Values = m + + // SaveDir looks for the file values.yaml when saving rather than the values + // key in order to preserve the comments in the YAML. The name placeholder + // needs to be replaced on that file. + for _, f := range schart.Raw { + if f.Name == ValuesfileName { + f.Data = transform(string(f.Data), schart.Name()) + } + } + + return SaveDir(schart, dest) +} + +// Create creates a new chart in a directory. +// +// Inside of dir, this will create a directory based on the name of +// chartfile.Name. It will then write the Chart.yaml into this directory and +// create the (empty) appropriate directories. +// +// The returned string will point to the newly created directory. It will be +// an absolute path, even if the provided base directory was relative. +// +// If dir does not exist, this will return an error. +// If Chart.yaml or any directories cannot be created, this will return an +// error. In such a case, this will attempt to clean up by removing the +// new chart directory. +func Create(name, dir string) (string, error) { + + // Sanity-check the name of a chart so user doesn't create one that causes problems. + if err := validateChartName(name); err != nil { + return "", err + } + + path, err := filepath.Abs(dir) + if err != nil { + return path, err + } + + if fi, err := os.Stat(path); err != nil { + return path, err + } else if !fi.IsDir() { + return path, fmt.Errorf("no such directory %s", path) + } + + cdir := filepath.Join(path, name) + if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() { + return cdir, fmt.Errorf("file %s already exists and is not a directory", cdir) + } + + // Note: If adding a new template below (i.e., to `helm create`) which is disabled by default (similar to hpa and + // ingress below); or making an existing template disabled by default, add the enabling condition in + // `TestHelmCreateChart_CheckDeprecatedWarnings` in `pkg/lint/lint_test.go` to make it run through deprecation checks + // with latest Kubernetes version. + files := []struct { + path string + content []byte + }{ + { + // Chart.yaml + path: filepath.Join(cdir, ChartfileName), + content: fmt.Appendf(nil, defaultChartfile, name), + }, + { + // values.yaml + path: filepath.Join(cdir, ValuesfileName), + content: fmt.Appendf(nil, defaultValues, name), + }, + { + // .helmignore + path: filepath.Join(cdir, IgnorefileName), + content: []byte(defaultIgnore), + }, + { + // ingress.yaml + path: filepath.Join(cdir, IngressFileName), + content: transform(defaultIngress, name), + }, + { + // httproute.yaml + path: filepath.Join(cdir, HTTPRouteFileName), + content: transform(defaultHTTPRoute, name), + }, + { + // deployment.yaml + path: filepath.Join(cdir, DeploymentName), + content: transform(defaultDeployment, name), + }, + { + // service.yaml + path: filepath.Join(cdir, ServiceName), + content: transform(defaultService, name), + }, + { + // serviceaccount.yaml + path: filepath.Join(cdir, ServiceAccountName), + content: transform(defaultServiceAccount, name), + }, + { + // hpa.yaml + path: filepath.Join(cdir, HorizontalPodAutoscalerName), + content: transform(defaultHorizontalPodAutoscaler, name), + }, + { + // NOTES.txt + path: filepath.Join(cdir, NotesName), + content: transform(defaultNotes, name), + }, + { + // _helpers.tpl + path: filepath.Join(cdir, HelpersName), + content: transform(defaultHelpers, name), + }, + { + // test-connection.yaml + path: filepath.Join(cdir, TestConnectionName), + content: transform(defaultTestConnection, name), + }, + } + + for _, file := range files { + if _, err := os.Stat(file.path); err == nil { + // There is no handle to a preferred output stream here. + fmt.Fprintf(Stderr, "WARNING: File %q already exists. Overwriting.\n", file.path) + } + if err := writeFile(file.path, file.content); err != nil { + return cdir, err + } + } + // Need to add the ChartsDir explicitly as it does not contain any file OOTB + if err := os.MkdirAll(filepath.Join(cdir, ChartsDir), 0755); err != nil { + return cdir, err + } + return cdir, nil +} + +// transform performs a string replacement of the specified source for +// a given key with the replacement string +func transform(src, replacement string) []byte { + return []byte(strings.ReplaceAll(src, "", replacement)) +} + +func writeFile(name string, content []byte) error { + if err := os.MkdirAll(filepath.Dir(name), 0755); err != nil { + return err + } + return os.WriteFile(name, content, 0644) +} + +func validateChartName(name string) error { + if name == "" || len(name) > maxChartNameLength { + return fmt.Errorf("chart name must be between 1 and %d characters", maxChartNameLength) + } + if !chartName.MatchString(name) { + return fmt.Errorf("chart name must match the regular expression %q", chartName.String()) + } + return nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/util/dependencies.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/util/dependencies.go new file mode 100644 index 00000000000..c7bb6621ed9 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/util/dependencies.go @@ -0,0 +1,380 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + "log/slog" + "strings" + + "helm.sh/helm/v4/internal/copystructure" + "helm.sh/helm/v4/pkg/chart/common" + "helm.sh/helm/v4/pkg/chart/common/util" + chart "helm.sh/helm/v4/pkg/chart/v2" +) + +// ProcessDependencies checks through this chart's dependencies, processing accordingly. +func ProcessDependencies(c *chart.Chart, v common.Values) error { + if err := processDependencyEnabled(c, v, ""); err != nil { + return err + } + return processDependencyImportValues(c, true) +} + +// processDependencyConditions disables charts based on condition path value in values +func processDependencyConditions(reqs []*chart.Dependency, cvals common.Values, cpath string) { + if reqs == nil { + return + } + for _, r := range reqs { + for c := range strings.SplitSeq(strings.TrimSpace(r.Condition), ",") { + if len(c) > 0 { + // retrieve value + vv, err := cvals.PathValue(cpath + c) + if err == nil { + // if not bool, warn + if bv, ok := vv.(bool); ok { + r.Enabled = bv + break + } + slog.Warn("returned non-bool value", "path", c, "chart", r.Name) + } else if _, ok := err.(common.ErrNoValue); !ok { + // this is a real error + slog.Warn("the method PathValue returned error", slog.Any("error", err)) + } + } + } + } +} + +// processDependencyTags disables charts based on tags in values +func processDependencyTags(reqs []*chart.Dependency, cvals common.Values) { + if reqs == nil { + return + } + vt, err := cvals.Table("tags") + if err != nil { + return + } + for _, r := range reqs { + var hasTrue, hasFalse bool + for _, k := range r.Tags { + if b, ok := vt[k]; ok { + // if not bool, warn + if bv, ok := b.(bool); ok { + if bv { + hasTrue = true + } else { + hasFalse = true + } + } else { + slog.Warn("returned non-bool value", "tag", k, "chart", r.Name) + } + } + } + if !hasTrue && hasFalse { + r.Enabled = false + } else if hasTrue || !hasTrue && !hasFalse { + r.Enabled = true + } + } +} + +// getAliasDependency finds the chart for an alias dependency and copies parts that will be modified +func getAliasDependency(charts []*chart.Chart, dep *chart.Dependency) *chart.Chart { + for _, c := range charts { + if c == nil { + continue + } + if c.Name() != dep.Name { + continue + } + if !IsCompatibleRange(dep.Version, c.Metadata.Version) { + continue + } + + out := *c + out.Metadata = copyMetadata(c.Metadata) + + // empty dependencies and shallow copy all dependencies, otherwise parent info may be corrupted if + // there is more than one dependency aliasing this chart + out.SetDependencies() + for _, dependency := range c.Dependencies() { + cpy := *dependency + out.AddDependency(&cpy) + } + + if dep.Alias != "" { + out.Metadata.Name = dep.Alias + } + return &out + } + return nil +} + +func copyMetadata(metadata *chart.Metadata) *chart.Metadata { + md := *metadata + + if md.Dependencies != nil { + dependencies := make([]*chart.Dependency, len(md.Dependencies)) + for i := range md.Dependencies { + dependency := *md.Dependencies[i] + dependencies[i] = &dependency + } + md.Dependencies = dependencies + } + return &md +} + +// processDependencyEnabled removes disabled charts from dependencies +func processDependencyEnabled(c *chart.Chart, v map[string]interface{}, path string) error { + if c.Metadata.Dependencies == nil { + return nil + } + + var chartDependencies []*chart.Chart + // If any dependency is not a part of Chart.yaml + // then this should be added to chartDependencies. + // However, if the dependency is already specified in Chart.yaml + // we should not add it, as it would be processed from Chart.yaml anyway. + +Loop: + for _, existing := range c.Dependencies() { + for _, req := range c.Metadata.Dependencies { + if existing.Name() == req.Name && IsCompatibleRange(req.Version, existing.Metadata.Version) { + continue Loop + } + } + chartDependencies = append(chartDependencies, existing) + } + + for _, req := range c.Metadata.Dependencies { + if req == nil { + continue + } + if chartDependency := getAliasDependency(c.Dependencies(), req); chartDependency != nil { + chartDependencies = append(chartDependencies, chartDependency) + } + if req.Alias != "" { + req.Name = req.Alias + } + } + c.SetDependencies(chartDependencies...) + + // set all to true + for _, lr := range c.Metadata.Dependencies { + lr.Enabled = true + } + cvals, err := util.CoalesceValues(c, v) + if err != nil { + return err + } + // flag dependencies as enabled/disabled + processDependencyTags(c.Metadata.Dependencies, cvals) + processDependencyConditions(c.Metadata.Dependencies, cvals, path) + // make a map of charts to remove + rm := map[string]struct{}{} + for _, r := range c.Metadata.Dependencies { + if !r.Enabled { + // remove disabled chart + rm[r.Name] = struct{}{} + } + } + // don't keep disabled charts in new slice + cd := []*chart.Chart{} + copy(cd, c.Dependencies()[:0]) + for _, n := range c.Dependencies() { + if _, ok := rm[n.Metadata.Name]; !ok { + cd = append(cd, n) + } + } + // don't keep disabled charts in metadata + cdMetadata := []*chart.Dependency{} + copy(cdMetadata, c.Metadata.Dependencies[:0]) + for _, n := range c.Metadata.Dependencies { + if _, ok := rm[n.Name]; !ok { + cdMetadata = append(cdMetadata, n) + } + } + + // recursively call self to process sub dependencies + for _, t := range cd { + subpath := path + t.Metadata.Name + "." + if err := processDependencyEnabled(t, cvals, subpath); err != nil { + return err + } + } + // set the correct dependencies in metadata + c.Metadata.Dependencies = nil + c.Metadata.Dependencies = append(c.Metadata.Dependencies, cdMetadata...) + c.SetDependencies(cd...) + + return nil +} + +// pathToMap creates a nested map given a YAML path in dot notation. +func pathToMap(path string, data map[string]interface{}) map[string]interface{} { + if path == "." { + return data + } + return set(parsePath(path), data) +} + +func parsePath(key string) []string { return strings.Split(key, ".") } + +func set(path []string, data map[string]interface{}) map[string]interface{} { + if len(path) == 0 { + return nil + } + cur := data + for i := len(path) - 1; i >= 0; i-- { + cur = map[string]interface{}{path[i]: cur} + } + return cur +} + +// processImportValues merges values from child to parent based on the chart's dependencies' ImportValues field. +func processImportValues(c *chart.Chart, merge bool) error { + if c.Metadata.Dependencies == nil { + return nil + } + // combine chart values and empty config to get Values + var cvals common.Values + var err error + if merge { + cvals, err = util.MergeValues(c, nil) + } else { + cvals, err = util.CoalesceValues(c, nil) + } + if err != nil { + return err + } + b := make(map[string]interface{}) + // import values from each dependency if specified in import-values + for _, r := range c.Metadata.Dependencies { + var outiv []interface{} + for _, riv := range r.ImportValues { + switch iv := riv.(type) { + case map[string]interface{}: + child := fmt.Sprintf("%v", iv["child"]) + parent := fmt.Sprintf("%v", iv["parent"]) + + outiv = append(outiv, map[string]string{ + "child": child, + "parent": parent, + }) + + // get child table + vv, err := cvals.Table(r.Name + "." + child) + if err != nil { + slog.Warn( + "ImportValues missing table from chart", + slog.String("chart", r.Name), + slog.Any("error", err), + ) + continue + } + // create value map from child to be merged into parent + if merge { + b = util.MergeTables(b, pathToMap(parent, vv.AsMap())) + } else { + b = util.CoalesceTables(b, pathToMap(parent, vv.AsMap())) + } + case string: + child := "exports." + iv + outiv = append(outiv, map[string]string{ + "child": child, + "parent": ".", + }) + vm, err := cvals.Table(r.Name + "." + child) + if err != nil { + slog.Warn("ImportValues missing table", slog.Any("error", err)) + continue + } + if merge { + b = util.MergeTables(b, vm.AsMap()) + } else { + b = util.CoalesceTables(b, vm.AsMap()) + } + } + } + r.ImportValues = outiv + } + + // Imported values from a child to a parent chart have a lower priority than + // the parents values. This enables parent charts to import a large section + // from a child and then override select parts. This is why b is merged into + // cvals in the code below and not the other way around. + if merge { + // deep copying the cvals as there are cases where pointers can end + // up in the cvals when they are copied onto b in ways that break things. + cvals = deepCopyMap(cvals) + c.Values = util.MergeTables(cvals, b) + } else { + // Trimming the nil values from cvals is needed for backwards compatibility. + // Previously, the b value had been populated with cvals along with some + // overrides. This caused the coalescing functionality to remove the + // nil/null values. This trimming is for backwards compat. + cvals = trimNilValues(cvals) + c.Values = util.CoalesceTables(cvals, b) + } + + return nil +} + +func deepCopyMap(vals map[string]interface{}) map[string]interface{} { + valsCopy, err := copystructure.Copy(vals) + if err != nil { + return vals + } + return valsCopy.(map[string]interface{}) +} + +func trimNilValues(vals map[string]interface{}) map[string]interface{} { + valsCopy, err := copystructure.Copy(vals) + if err != nil { + return vals + } + valsCopyMap := valsCopy.(map[string]interface{}) + for key, val := range valsCopyMap { + if val == nil { + // Iterate over the values and remove nil keys + delete(valsCopyMap, key) + } else if istable(val) { + // Recursively call into ourselves to remove keys from inner tables + valsCopyMap[key] = trimNilValues(val.(map[string]interface{})) + } + } + + return valsCopyMap +} + +// istable is a special-purpose function to see if the present thing matches the definition of a YAML table. +func istable(v interface{}) bool { + _, ok := v.(map[string]interface{}) + return ok +} + +// processDependencyImportValues imports specified chart values from child to parent. +func processDependencyImportValues(c *chart.Chart, merge bool) error { + for _, d := range c.Dependencies() { + // recurse + if err := processDependencyImportValues(d, merge); err != nil { + return err + } + } + return processImportValues(c, merge) +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/util/doc.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/util/doc.go new file mode 100644 index 00000000000..1410620740f --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/util/doc.go @@ -0,0 +1,45 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +package util contains tools for working with charts. + +Charts are described in the chart package (pkg/chart). +This package provides utilities for serializing and deserializing charts. + +A chart can be represented on the file system in one of two ways: + + - As a directory that contains a Chart.yaml file and other chart things. + - As a tarred gzipped file containing a directory that then contains a + Chart.yaml file. + +This package provides utilities for working with those file formats. + +The preferred way of loading a chart is using 'loader.Load`: + + chart, err := loader.Load(filename) + +This will attempt to discover whether the file at 'filename' is a directory or +a chart archive. It will then load accordingly. + +For accepting raw compressed tar file data from an io.Reader, the +'loader.LoadArchive()' will read in the data, uncompress it, and unpack it +into a Chart. + +When creating charts in memory, use the 'helm.sh/helm/pkg/chart' +package directly. +*/ +package util // import chartutil "helm.sh/helm/v4/pkg/chart/v2/util" diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/util/expand.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/util/expand.go new file mode 100644 index 00000000000..6022d5869e0 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/util/expand.go @@ -0,0 +1,111 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + + securejoin "github.com/cyphar/filepath-securejoin" + "sigs.k8s.io/yaml" + + "helm.sh/helm/v4/pkg/chart/loader/archive" + chart "helm.sh/helm/v4/pkg/chart/v2" +) + +// Expand uncompresses and extracts a chart into the specified directory. +func Expand(dir string, r io.Reader) error { + files, err := archive.LoadArchiveFiles(r) + if err != nil { + return err + } + + // Get the name of the chart + var chartName string + for _, file := range files { + if file.Name == "Chart.yaml" { + ch := &chart.Metadata{} + if err := yaml.Unmarshal(file.Data, ch); err != nil { + return fmt.Errorf("cannot load Chart.yaml: %w", err) + } + chartName = ch.Name + } + } + if chartName == "" { + return errors.New("chart name not specified") + } + + // Reject chart names that are POSIX path dot-segments or dot-dot segments or contain path separators. + // A dot-segment name (e.g. ".") causes SecureJoin to resolve to the root + // directory and extraction then to write files directly into that extraction root + // instead of a per-chart subdirectory. + if chartName == "." || chartName == ".." { + return fmt.Errorf("chart name %q is not allowed", chartName) + } + if chartName != filepath.Base(chartName) { + return fmt.Errorf("chart name %q must not contain path separators", chartName) + } + + // Find the base directory + // The directory needs to be cleaned prior to passing to SecureJoin or the location may end up + // being wrong or returning an error. This was introduced in v0.4.0. + dir = filepath.Clean(dir) + chartdir, err := securejoin.SecureJoin(dir, chartName) + if err != nil { + return err + } + + // Defense-in-depth: the chart directory must be a subdirectory of dir, + // never dir itself. + if chartdir == dir { + return fmt.Errorf("chart name %q resolves to the extraction root", chartName) + } + + // Copy all files verbatim. We don't parse these files because parsing can remove + // comments. + for _, file := range files { + outpath, err := securejoin.SecureJoin(chartdir, file.Name) + if err != nil { + return err + } + + // Make sure the necessary subdirs get created. + basedir := filepath.Dir(outpath) + if err := os.MkdirAll(basedir, 0755); err != nil { + return err + } + + if err := os.WriteFile(outpath, file.Data, 0644); err != nil { + return err + } + } + + return nil +} + +// ExpandFile expands the src file into the dest directory. +func ExpandFile(dest, src string) error { + h, err := os.Open(src) + if err != nil { + return err + } + defer h.Close() + return Expand(dest, h) +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/util/save.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/util/save.go new file mode 100644 index 00000000000..e66d86991ca --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/util/save.go @@ -0,0 +1,269 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "archive/tar" + "compress/gzip" + "encoding/json" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "time" + + "sigs.k8s.io/yaml" + + "helm.sh/helm/v4/pkg/chart/common" + chart "helm.sh/helm/v4/pkg/chart/v2" +) + +var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") + +// SaveDir saves a chart as files in a directory. +// +// This takes the chart name, and creates a new subdirectory inside of the given dest +// directory, writing the chart's contents to that subdirectory. +func SaveDir(c *chart.Chart, dest string) error { + // Create the chart directory + err := validateName(c.Name()) + if err != nil { + return err + } + outdir := filepath.Join(dest, c.Name()) + if fi, err := os.Stat(outdir); err == nil && !fi.IsDir() { + return fmt.Errorf("file %s already exists and is not a directory", outdir) + } + if err := os.MkdirAll(outdir, 0755); err != nil { + return err + } + + // Save the chart file. + if err := SaveChartfile(filepath.Join(outdir, ChartfileName), c.Metadata); err != nil { + return err + } + + // Save values.yaml + for _, f := range c.Raw { + if f.Name == ValuesfileName { + vf := filepath.Join(outdir, ValuesfileName) + if err := writeFile(vf, f.Data); err != nil { + return err + } + } + } + + // Save values.schema.json if it exists + if c.Schema != nil { + filename := filepath.Join(outdir, SchemafileName) + if err := writeFile(filename, c.Schema); err != nil { + return err + } + } + + // Save templates and files + for _, o := range [][]*common.File{c.Templates, c.Files} { + for _, f := range o { + n := filepath.Join(outdir, f.Name) + if err := writeFile(n, f.Data); err != nil { + return err + } + } + } + + // Save dependencies + base := filepath.Join(outdir, ChartsDir) + for _, dep := range c.Dependencies() { + // Here, we write each dependency as a tar file. + if _, err := Save(dep, base); err != nil { + return fmt.Errorf("saving %s: %w", dep.ChartFullPath(), err) + } + } + return nil +} + +// Save creates an archived chart to the given directory. +// +// This takes an existing chart and a destination directory. +// +// If the directory is /foo, and the chart is named bar, with version 1.0.0, this +// will generate /foo/bar-1.0.0.tgz. +// +// This returns the absolute path to the chart archive file. +func Save(c *chart.Chart, outDir string) (string, error) { + if err := c.Validate(); err != nil { + return "", fmt.Errorf("chart validation: %w", err) + } + + filename := fmt.Sprintf("%s-%s.tgz", c.Name(), c.Metadata.Version) + filename = filepath.Join(outDir, filename) + dir := filepath.Dir(filename) + if stat, err := os.Stat(dir); err != nil { + if errors.Is(err, fs.ErrNotExist) { + if err2 := os.MkdirAll(dir, 0755); err2 != nil { + return "", err2 + } + } else { + return "", fmt.Errorf("stat %s: %w", dir, err) + } + } else if !stat.IsDir() { + return "", fmt.Errorf("is not a directory: %s", dir) + } + + f, err := os.Create(filename) + if err != nil { + return "", err + } + + // Wrap in gzip writer + zipper := gzip.NewWriter(f) + zipper.Extra = headerBytes + zipper.Comment = "Helm" + + // Wrap in tar writer + twriter := tar.NewWriter(zipper) + rollback := false + defer func() { + twriter.Close() + zipper.Close() + f.Close() + if rollback { + os.Remove(filename) + } + }() + + if err := writeTarContents(twriter, c, ""); err != nil { + rollback = true + return filename, err + } + return filename, nil +} + +func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error { + err := validateName(c.Name()) + if err != nil { + return err + } + base := filepath.Join(prefix, c.Name()) + + // Pull out the dependencies of a v1 Chart, since there's no way + // to tell the serializer to skip a field for just this use case + savedDependencies := c.Metadata.Dependencies + if c.Metadata.APIVersion == chart.APIVersionV1 { + c.Metadata.Dependencies = nil + } + // Save Chart.yaml + cdata, err := yaml.Marshal(c.Metadata) + if c.Metadata.APIVersion == chart.APIVersionV1 { + c.Metadata.Dependencies = savedDependencies + } + if err != nil { + return err + } + if err := writeToTar(out, filepath.Join(base, ChartfileName), cdata, c.ModTime); err != nil { + return err + } + + // Save Chart.lock + // TODO: remove the APIVersion check when APIVersionV1 is not used anymore + if c.Metadata.APIVersion == chart.APIVersionV2 { + if c.Lock != nil { + ldata, err := yaml.Marshal(c.Lock) + if err != nil { + return err + } + if err := writeToTar(out, filepath.Join(base, "Chart.lock"), ldata, c.Lock.Generated); err != nil { + return err + } + } + } + + // Save values.yaml + for _, f := range c.Raw { + if f.Name == ValuesfileName { + if err := writeToTar(out, filepath.Join(base, ValuesfileName), f.Data, f.ModTime); err != nil { + return err + } + } + } + + // Save values.schema.json if it exists + if c.Schema != nil { + if !json.Valid(c.Schema) { + return errors.New("invalid JSON in " + SchemafileName) + } + if err := writeToTar(out, filepath.Join(base, SchemafileName), c.Schema, c.SchemaModTime); err != nil { + return err + } + } + + // Save templates + for _, f := range c.Templates { + n := filepath.Join(base, f.Name) + if err := writeToTar(out, n, f.Data, f.ModTime); err != nil { + return err + } + } + + // Save files + for _, f := range c.Files { + n := filepath.Join(base, f.Name) + if err := writeToTar(out, n, f.Data, f.ModTime); err != nil { + return err + } + } + + // Save dependencies + for _, dep := range c.Dependencies() { + if err := writeTarContents(out, dep, filepath.Join(base, ChartsDir)); err != nil { + return err + } + } + return nil +} + +// writeToTar writes a single file to a tar archive. +func writeToTar(out *tar.Writer, name string, body []byte, modTime time.Time) error { + // TODO: Do we need to create dummy parent directory names if none exist? + h := &tar.Header{ + Name: filepath.ToSlash(name), + Mode: 0644, + Size: int64(len(body)), + ModTime: modTime, + } + if h.ModTime.IsZero() { + h.ModTime = time.Now() + } + if err := out.WriteHeader(h); err != nil { + return err + } + _, err := out.Write(body) + return err +} + +// If the name has directory name has characters which would change the location +// they need to be removed. +func validateName(name string) error { + nname := filepath.Base(name) + + if nname != name { + return common.ErrInvalidChartName{Name: name} + } + + return nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/chart/v2/util/validate_name.go b/vendor/helm.sh/helm/v4/pkg/chart/v2/util/validate_name.go new file mode 100644 index 00000000000..6595e085d6d --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/chart/v2/util/validate_name.go @@ -0,0 +1,111 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "errors" + "fmt" + "regexp" +) + +// validName is a regular expression for resource names. +// +// According to the Kubernetes help text, the regular expression it uses is: +// +// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* +// +// This follows the above regular expression (but requires a full string match, not partial). +// +// The Kubernetes documentation is here, though it is not entirely correct: +// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +var validName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`) + +var ( + // errMissingName indicates that a release (name) was not provided. + errMissingName = errors.New("no name provided") + + // errInvalidName indicates that an invalid release name was provided + errInvalidName = fmt.Errorf( + "invalid release name, must match regex %s and the length must not be longer than 53", + validName.String()) + + // errInvalidKubernetesName indicates that the name does not meet the Kubernetes + // restrictions on metadata names. + errInvalidKubernetesName = fmt.Errorf( + "invalid metadata name, must match regex %s and the length must not be longer than 253", + validName.String()) +) + +const ( + // According to the Kubernetes docs (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names) + // some resource names have a max length of 63 characters while others have a max + // length of 253 characters. As we cannot be sure the resources used in a chart, we + // therefore need to limit it to 63 chars and reserve 10 chars for additional part to name + // of the resource. The reason is that chart maintainers can use release name as part of + // the resource name (and some additional chars). + maxReleaseNameLen = 53 + // maxMetadataNameLen is the maximum length Kubernetes allows for any name. + maxMetadataNameLen = 253 +) + +// ValidateReleaseName performs checks for an entry for a Helm release name +// +// For Helm to allow a name, it must be below a certain character count (53) and also match +// a regular expression. +// +// According to the Kubernetes help text, the regular expression it uses is: +// +// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* +// +// This follows the above regular expression (but requires a full string match, not partial). +// +// The Kubernetes documentation is here, though it is not entirely correct: +// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +func ValidateReleaseName(name string) error { + // This case is preserved for backwards compatibility + if name == "" { + return errMissingName + + } + if len(name) > maxReleaseNameLen || !validName.MatchString(name) { + return errInvalidName + } + return nil +} + +// ValidateMetadataName validates the name field of a Kubernetes metadata object. +// +// Empty strings, strings longer than 253 chars, or strings that don't match the regexp +// will fail. +// +// According to the Kubernetes help text, the regular expression it uses is: +// +// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* +// +// This follows the above regular expression (but requires a full string match, not partial). +// +// The Kubernetes documentation is here, though it is not entirely correct: +// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +// +// Deprecated: remove in Helm 4. Name validation now uses rules defined in +// pkg/lint/rules.validateMetadataNameFunc() +func ValidateMetadataName(name string) error { + if name == "" || len(name) > maxMetadataNameLen || !validName.MatchString(name) { + return errInvalidKubernetesName + } + return nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/cli/environment.go b/vendor/helm.sh/helm/v4/pkg/cli/environment.go new file mode 100644 index 00000000000..5c19734aafa --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/cli/environment.go @@ -0,0 +1,302 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package cli describes the operating environment for the Helm CLI. + +Helm's environment encapsulates all of the service dependencies Helm has. +These dependencies are expressed as interfaces so that alternate implementations +(mocks, etc.) can be easily generated. +*/ +package cli + +import ( + "fmt" + "net/http" + "os" + "strconv" + "strings" + + "github.com/spf13/pflag" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/client-go/rest" + + "helm.sh/helm/v4/internal/version" + "helm.sh/helm/v4/pkg/helmpath" + "helm.sh/helm/v4/pkg/kube" +) + +// defaultMaxHistory sets the maximum number of releases to 0: unlimited +const defaultMaxHistory = 10 + +// defaultBurstLimit sets the default client-side throttling limit +const defaultBurstLimit = 100 + +// defaultQPS sets the default QPS value to 0 to use library defaults unless specified +const defaultQPS = float32(0) + +// EnvSettings describes all of the environment settings. +type EnvSettings struct { + namespace string + config *genericclioptions.ConfigFlags + + // KubeConfig is the path to the kubeconfig file + KubeConfig string + // KubeContext is the name of the kubeconfig context. + KubeContext string + // Bearer KubeToken used for authentication + KubeToken string + // Username to impersonate for the operation + KubeAsUser string + // Groups to impersonate for the operation, multiple groups parsed from a comma delimited list + KubeAsGroups []string + // Kubernetes API Server Endpoint for authentication + KubeAPIServer string + // Custom certificate authority file. + KubeCaFile string + // KubeInsecureSkipTLSVerify indicates if server's certificate will not be checked for validity. + // This makes the HTTPS connections insecure + KubeInsecureSkipTLSVerify bool + // KubeTLSServerName overrides the name to use for server certificate validation. + // If it is not provided, the hostname used to contact the server is used + KubeTLSServerName string + // Debug indicates whether or not Helm is running in Debug mode. + Debug bool + // RegistryConfig is the path to the registry config file. + RegistryConfig string + // RepositoryConfig is the path to the repositories file. + RepositoryConfig string + // RepositoryCache is the path to the repository cache directory. + RepositoryCache string + // PluginsDirectory is the path to the plugins directory. + PluginsDirectory string + // MaxHistory is the max release history maintained. + MaxHistory int + // BurstLimit is the default client-side throttling limit. + BurstLimit int + // QPS is queries per second which may be used to avoid throttling. + QPS float32 + // ColorMode controls colorized output (never, auto, always) + ColorMode string + // ContentCache is the location where cached charts are stored + ContentCache string +} + +func New() *EnvSettings { + env := &EnvSettings{ + namespace: os.Getenv("HELM_NAMESPACE"), + MaxHistory: envIntOr("HELM_MAX_HISTORY", defaultMaxHistory), + KubeConfig: os.Getenv("KUBECONFIG"), + KubeContext: os.Getenv("HELM_KUBECONTEXT"), + KubeToken: os.Getenv("HELM_KUBETOKEN"), + KubeAsUser: os.Getenv("HELM_KUBEASUSER"), + KubeAsGroups: envCSV("HELM_KUBEASGROUPS"), + KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"), + KubeCaFile: os.Getenv("HELM_KUBECAFILE"), + KubeTLSServerName: os.Getenv("HELM_KUBETLS_SERVER_NAME"), + KubeInsecureSkipTLSVerify: envBoolOr("HELM_KUBEINSECURE_SKIP_TLS_VERIFY", false), + PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")), + RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry/config.json")), + RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")), + RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")), + ContentCache: envOr("HELM_CONTENT_CACHE", helmpath.CachePath("content")), + BurstLimit: envIntOr("HELM_BURST_LIMIT", defaultBurstLimit), + QPS: envFloat32Or("HELM_QPS", defaultQPS), + ColorMode: envColorMode(), + } + env.Debug, _ = strconv.ParseBool(os.Getenv("HELM_DEBUG")) + + // bind to kubernetes config flags + config := &genericclioptions.ConfigFlags{ + Namespace: &env.namespace, + Context: &env.KubeContext, + BearerToken: &env.KubeToken, + APIServer: &env.KubeAPIServer, + CAFile: &env.KubeCaFile, + KubeConfig: &env.KubeConfig, + Impersonate: &env.KubeAsUser, + Insecure: &env.KubeInsecureSkipTLSVerify, + TLSServerName: &env.KubeTLSServerName, + ImpersonateGroup: &env.KubeAsGroups, + WrapConfigFn: func(config *rest.Config) *rest.Config { + config.Burst = env.BurstLimit + config.QPS = env.QPS + config.Wrap(func(rt http.RoundTripper) http.RoundTripper { + return &kube.RetryingRoundTripper{Wrapped: rt} + }) + config.UserAgent = version.GetUserAgent() + return config + }, + } + if env.BurstLimit != defaultBurstLimit { + config = config.WithDiscoveryBurst(env.BurstLimit) + } + env.config = config + + return env +} + +// AddFlags binds flags to the given flagset. +func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) { + fs.StringVarP(&s.namespace, "namespace", "n", s.namespace, "namespace scope for this request") + fs.StringVar(&s.KubeConfig, "kubeconfig", "", "path to the kubeconfig file") + fs.StringVar(&s.KubeContext, "kube-context", s.KubeContext, "name of the kubeconfig context to use") + fs.StringVar(&s.KubeToken, "kube-token", s.KubeToken, "bearer token used for authentication") + fs.StringVar(&s.KubeAsUser, "kube-as-user", s.KubeAsUser, "username to impersonate for the operation") + fs.StringArrayVar(&s.KubeAsGroups, "kube-as-group", s.KubeAsGroups, "group to impersonate for the operation, this flag can be repeated to specify multiple groups.") + fs.StringVar(&s.KubeAPIServer, "kube-apiserver", s.KubeAPIServer, "the address and the port for the Kubernetes API server") + fs.StringVar(&s.KubeCaFile, "kube-ca-file", s.KubeCaFile, "the certificate authority file for the Kubernetes API server connection") + fs.StringVar(&s.KubeTLSServerName, "kube-tls-server-name", s.KubeTLSServerName, "server name to use for Kubernetes API server certificate validation. If it is not provided, the hostname used to contact the server is used") + fs.BoolVar(&s.KubeInsecureSkipTLSVerify, "kube-insecure-skip-tls-verify", s.KubeInsecureSkipTLSVerify, "if true, the Kubernetes API server's certificate will not be checked for validity. This will make your HTTPS connections insecure") + fs.BoolVar(&s.Debug, "debug", s.Debug, "enable verbose output") + fs.StringVar(&s.RegistryConfig, "registry-config", s.RegistryConfig, "path to the registry config file") + fs.StringVar(&s.RepositoryConfig, "repository-config", s.RepositoryConfig, "path to the file containing repository names and URLs") + fs.StringVar(&s.RepositoryCache, "repository-cache", s.RepositoryCache, "path to the directory containing cached repository indexes") + fs.StringVar(&s.ContentCache, "content-cache", s.ContentCache, "path to the directory containing cached content (e.g. charts)") + fs.IntVar(&s.BurstLimit, "burst-limit", s.BurstLimit, "client-side default throttling limit") + fs.Float32Var(&s.QPS, "qps", s.QPS, "queries per second used when communicating with the Kubernetes API, not including bursting") + fs.StringVar(&s.ColorMode, "color", s.ColorMode, "use colored output (never, auto, always)") + fs.StringVar(&s.ColorMode, "colour", s.ColorMode, "use colored output (never, auto, always)") +} + +func envOr(name, def string) string { + if v, ok := os.LookupEnv(name); ok { + return v + } + return def +} + +func envBoolOr(name string, def bool) bool { + if name == "" { + return def + } + envVal := envOr(name, strconv.FormatBool(def)) + ret, err := strconv.ParseBool(envVal) + if err != nil { + return def + } + return ret +} + +func envIntOr(name string, def int) int { + if name == "" { + return def + } + envVal := envOr(name, strconv.Itoa(def)) + ret, err := strconv.Atoi(envVal) + if err != nil { + return def + } + return ret +} + +func envFloat32Or(name string, def float32) float32 { + if name == "" { + return def + } + envVal := envOr(name, strconv.FormatFloat(float64(def), 'f', 2, 32)) + ret, err := strconv.ParseFloat(envVal, 32) + if err != nil { + return def + } + return float32(ret) +} + +func envCSV(name string) (ls []string) { + trimmed := strings.Trim(os.Getenv(name), ", ") + if trimmed != "" { + ls = strings.Split(trimmed, ",") + } + return +} + +func envColorMode() string { + // Check NO_COLOR environment variable first (standard) + if v, ok := os.LookupEnv("NO_COLOR"); ok && v != "" { + return "never" + } + // Check HELM_COLOR environment variable + if v, ok := os.LookupEnv("HELM_COLOR"); ok { + v = strings.ToLower(v) + switch v { + case "never", "auto", "always": + return v + } + } + // Default to auto + return "auto" +} + +func (s *EnvSettings) EnvVars() map[string]string { + envvars := map[string]string{ + "HELM_BIN": os.Args[0], + "HELM_CACHE_HOME": helmpath.CachePath(""), + "HELM_CONFIG_HOME": helmpath.ConfigPath(""), + "HELM_DATA_HOME": helmpath.DataPath(""), + "HELM_DEBUG": fmt.Sprint(s.Debug), + "HELM_PLUGINS": s.PluginsDirectory, + "HELM_REGISTRY_CONFIG": s.RegistryConfig, + "HELM_REPOSITORY_CACHE": s.RepositoryCache, + "HELM_CONTENT_CACHE": s.ContentCache, + "HELM_REPOSITORY_CONFIG": s.RepositoryConfig, + "HELM_NAMESPACE": s.Namespace(), + "HELM_MAX_HISTORY": strconv.Itoa(s.MaxHistory), + "HELM_BURST_LIMIT": strconv.Itoa(s.BurstLimit), + "HELM_QPS": strconv.FormatFloat(float64(s.QPS), 'f', 2, 32), + + // broken, these are populated from helm flags and not kubeconfig. + "HELM_KUBECONTEXT": s.KubeContext, + "HELM_KUBETOKEN": s.KubeToken, + "HELM_KUBEASUSER": s.KubeAsUser, + "HELM_KUBEASGROUPS": strings.Join(s.KubeAsGroups, ","), + "HELM_KUBEAPISERVER": s.KubeAPIServer, + "HELM_KUBECAFILE": s.KubeCaFile, + "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": strconv.FormatBool(s.KubeInsecureSkipTLSVerify), + "HELM_KUBETLS_SERVER_NAME": s.KubeTLSServerName, + } + if s.KubeConfig != "" { + envvars["KUBECONFIG"] = s.KubeConfig + } + return envvars +} + +// Namespace gets the namespace from the configuration +func (s *EnvSettings) Namespace() string { + if s.config != nil { + if ns, _, err := s.config.ToRawKubeConfigLoader().Namespace(); err == nil { + return ns + } + } + if s.namespace != "" { + return s.namespace + } + return "default" +} + +// SetNamespace sets the namespace in the configuration +func (s *EnvSettings) SetNamespace(namespace string) { + s.namespace = namespace +} + +// RESTClientGetter gets the kubeconfig from EnvSettings +func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter { + return s.config +} + +// ShouldDisableColor returns true if color output should be disabled +func (s *EnvSettings) ShouldDisableColor() bool { + return s.ColorMode == "never" +} diff --git a/vendor/helm.sh/helm/v4/pkg/downloader/cache.go b/vendor/helm.sh/helm/v4/pkg/downloader/cache.go new file mode 100644 index 00000000000..cecfc8bd7ef --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/downloader/cache.go @@ -0,0 +1,89 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package downloader + +import ( + "crypto/sha256" + "errors" + "fmt" + "io" + "log/slog" + "os" + "path/filepath" + + "helm.sh/helm/v4/internal/fileutil" +) + +// Cache describes a cache that can get and put chart data. +// The cache key is the sha256 has of the content. sha256 is used in Helm for +// digests in index files providing a common key for checking content. +type Cache interface { + // Get returns a reader for the given key. + Get(key [sha256.Size]byte, cacheType string) (string, error) + // Put stores the given reader for the given key. + Put(key [sha256.Size]byte, data io.Reader, cacheType string) (string, error) +} + +// CacheChart specifies the content is a chart +var CacheChart = ".chart" + +// CacheProv specifies the content is a provenance file +var CacheProv = ".prov" + +// TODO: The cache assumes files because much of Helm assumes files. Convert +// Helm to pass content around instead of file locations. + +// DiskCache is a cache that stores data on disk. +type DiskCache struct { + Root string +} + +// Get returns a reader for the given key. +func (c *DiskCache) Get(key [sha256.Size]byte, cacheType string) (string, error) { + p := c.fileName(key, cacheType) + fi, err := os.Stat(p) + if err != nil { + return "", err + } + // Empty files treated as not exist because there is no content. + if fi.Size() == 0 { + return p, os.ErrNotExist + } + // directories should never happen unless something outside helm is operating + // on this content. + if fi.IsDir() { + return p, errors.New("is a directory") + } + return p, nil +} + +// Put stores the given reader for the given key. +// It returns the path to the stored file. +func (c *DiskCache) Put(key [sha256.Size]byte, data io.Reader, cacheType string) (string, error) { + // TODO: verify the key and digest of the key are the same. + p := c.fileName(key, cacheType) + if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil { + slog.Error("failed to create cache directory") + return p, err + } + return p, fileutil.AtomicWriteFile(p, data, 0644) +} + +// fileName generates the filename in a structured manner where the first part is the +// directory and the full hash is the filename. +func (c *DiskCache) fileName(id [sha256.Size]byte, cacheType string) string { + return filepath.Join(c.Root, fmt.Sprintf("%02x", id[0]), fmt.Sprintf("%x", id)+cacheType) +} diff --git a/vendor/helm.sh/helm/v4/pkg/downloader/chart_downloader.go b/vendor/helm.sh/helm/v4/pkg/downloader/chart_downloader.go new file mode 100644 index 00000000000..23a4d623674 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/downloader/chart_downloader.go @@ -0,0 +1,604 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package downloader + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "io" + "io/fs" + "log/slog" + "net/url" + "os" + "path/filepath" + "strings" + + "helm.sh/helm/v4/internal/fileutil" + ifs "helm.sh/helm/v4/internal/third_party/dep/fs" + "helm.sh/helm/v4/internal/urlutil" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/helmpath" + "helm.sh/helm/v4/pkg/provenance" + "helm.sh/helm/v4/pkg/registry" + "helm.sh/helm/v4/pkg/repo/v1" +) + +// VerificationStrategy describes a strategy for determining whether to verify a chart. +type VerificationStrategy int + +const ( + // VerifyNever will skip all verification of a chart. + VerifyNever VerificationStrategy = iota + // VerifyIfPossible will attempt a verification, it will not error if verification + // data is missing. But it will not stop processing if verification fails. + VerifyIfPossible + // VerifyAlways will always attempt a verification, and will fail if the + // verification fails. + VerifyAlways + // VerifyLater will fetch verification data, but not do any verification. + // This is to accommodate the case where another step of the process will + // perform verification. + VerifyLater +) + +// ErrNoOwnerRepo indicates that a given chart URL can't be found in any repos. +var ErrNoOwnerRepo = errors.New("could not find a repo containing the given URL") + +// ChartDownloader handles downloading a chart. +// +// It is capable of performing verifications on charts as well. +type ChartDownloader struct { + // Out is the location to write warning and info messages. + Out io.Writer + // Verify indicates what verification strategy to use. + Verify VerificationStrategy + // Keyring is the keyring file used for verification. + Keyring string + // Getter collection for the operation + Getters getter.Providers + // Options provide parameters to be passed along to the Getter being initialized. + Options []getter.Option + RegistryClient *registry.Client + RepositoryConfig string + RepositoryCache string + + // ContentCache is the location where Cache stores its files by default + // In previous versions of Helm the charts were put in the RepositoryCache. The + // repositories and charts are stored in 2 different caches. + ContentCache string + + // Cache specifies the cache implementation to use. + Cache Cache +} + +// DownloadTo retrieves a chart. Depending on the settings, it may also download a provenance file. +// +// If Verify is set to VerifyNever, the verification will be nil. +// If Verify is set to VerifyIfPossible, this will return a verification (or nil on failure), and print a warning on failure. +// If Verify is set to VerifyAlways, this will return a verification or an error if the verification fails. +// If Verify is set to VerifyLater, this will download the prov file (if it exists), but not verify it. +// +// For VerifyNever and VerifyIfPossible, the Verification may be empty. +// +// Returns a string path to the location where the file was downloaded and a verification +// (if provenance was verified), or an error if something bad happened. +func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *provenance.Verification, error) { + if c.Cache == nil { + if c.ContentCache == "" { + return "", nil, errors.New("content cache must be set") + } + c.Cache = &DiskCache{Root: c.ContentCache} + slog.Debug("set up default downloader cache") + } + hash, u, err := c.ResolveChartVersion(ref, version) + if err != nil { + return "", nil, err + } + + g, err := c.Getters.ByScheme(u.Scheme) + if err != nil { + return "", nil, err + } + + // Check the cache for the content. Otherwise download it. + // Note, this process will pull from the cache but does not automatically populate + // the cache with the file it downloads. + var data *bytes.Buffer + var found bool + var digest []byte + var digest32 [32]byte + if hash != "" { + // if there is a hash, populate the other formats + // Strip the algorithm prefix (e.g., "sha256:") if present + digest, err = hex.DecodeString(stripDigestAlgorithm(hash)) + if err != nil { + return "", nil, err + } + if len(digest) != 32 { + return "", nil, fmt.Errorf("invalid digest length: %d", len(digest)) + } + + copy(digest32[:], digest) + if pth, err := c.Cache.Get(digest32, CacheChart); err == nil { + fdata, err := os.ReadFile(pth) + if err == nil { + found = true + data = bytes.NewBuffer(fdata) + slog.Debug("found chart in cache", "id", hash) + } + } + } + + if !found { + c.Options = append(c.Options, getter.WithAcceptHeader("application/gzip,application/octet-stream")) + + data, err = g.Get(u.String(), c.Options...) + if err != nil { + return "", nil, err + } + } + + name := filepath.Base(u.Path) + if u.Scheme == registry.OCIScheme { + idx := strings.LastIndexByte(name, ':') + name = fmt.Sprintf("%s-%s.tgz", name[:idx], name[idx+1:]) + } + + destfile := filepath.Join(dest, name) + + // Use PlatformAtomicWriteFile to handle platform-specific concurrency concerns + // (Windows requires locking to avoid "Access Denied" errors when multiple + // processes write the same file) + if err := fileutil.PlatformAtomicWriteFile(destfile, data, 0644); err != nil { + return destfile, nil, err + } + + // If provenance is requested, verify it. + ver := &provenance.Verification{} + if c.Verify > VerifyNever { + found = false + var body *bytes.Buffer + if hash != "" { + if pth, err := c.Cache.Get(digest32, CacheProv); err == nil { + fdata, err := os.ReadFile(pth) + if err == nil { + found = true + body = bytes.NewBuffer(fdata) + slog.Debug("found provenance in cache", "id", hash) + } + } + } + if !found { + body, err = g.Get(u.String() + ".prov") + if err != nil { + if c.Verify == VerifyAlways { + return destfile, ver, fmt.Errorf("failed to fetch provenance %q", u.String()+".prov") + } + fmt.Fprintf(c.Out, "WARNING: Verification not found for %s: %s\n", ref, err) + return destfile, ver, nil + } + } + provfile := destfile + ".prov" + + // Use PlatformAtomicWriteFile for the provenance file as well + if err := fileutil.PlatformAtomicWriteFile(provfile, body, 0644); err != nil { + return destfile, nil, err + } + + if c.Verify != VerifyLater { + ver, err = VerifyChart(destfile, destfile+".prov", c.Keyring) + if err != nil { + // Fail always in this case, since it means the verification step + // failed. + return destfile, ver, err + } + } + } + return destfile, ver, nil +} + +// DownloadToCache retrieves resources while using a content based cache. +func (c *ChartDownloader) DownloadToCache(ref, version string) (string, *provenance.Verification, error) { + if c.Cache == nil { + if c.ContentCache == "" { + return "", nil, errors.New("content cache must be set") + } + c.Cache = &DiskCache{Root: c.ContentCache} + slog.Debug("set up default downloader cache") + } + + digestString, u, err := c.ResolveChartVersion(ref, version) + if err != nil { + return "", nil, err + } + + g, err := c.Getters.ByScheme(u.Scheme) + if err != nil { + return "", nil, err + } + + c.Options = append(c.Options, getter.WithAcceptHeader("application/gzip,application/octet-stream")) + + // Check the cache for the file + // Strip the algorithm prefix (e.g., "sha256:") if present + digest, err := hex.DecodeString(stripDigestAlgorithm(digestString)) + if err != nil { + return "", nil, fmt.Errorf("unable to decode digest: %w", err) + } + if digestString != "" && len(digest) != 32 { + return "", nil, fmt.Errorf("invalid digest length: %d", len(digest)) + } + var digest32 [32]byte + copy(digest32[:], digest) + + var pth string + // only fetch from the cache if we have a digest + if len(digest) > 0 { + pth, err = c.Cache.Get(digest32, CacheChart) + if err == nil { + slog.Debug("found chart in cache", "id", digestString) + } + } + if len(digest) == 0 || err != nil { + slog.Debug("attempting to download chart", "ref", ref, "version", version) + if err != nil && !os.IsNotExist(err) { + return "", nil, err + } + + // Get file not in the cache + data, gerr := g.Get(u.String(), c.Options...) + if gerr != nil { + return "", nil, gerr + } + + // Generate the digest + if len(digest) == 0 { + digest32 = sha256.Sum256(data.Bytes()) + } + + pth, err = c.Cache.Put(digest32, data, CacheChart) + if err != nil { + return "", nil, err + } + slog.Debug("put downloaded chart in cache", "id", hex.EncodeToString(digest32[:])) + } + + // If provenance is requested, verify it. + ver := &provenance.Verification{} + if c.Verify > VerifyNever { + + ppth, err := c.Cache.Get(digest32, CacheProv) + if err == nil { + slog.Debug("found provenance in cache", "id", digestString) + } else { + if !os.IsNotExist(err) { + return pth, ver, err + } + + body, err := g.Get(u.String() + ".prov") + if err != nil { + if c.Verify == VerifyAlways { + return pth, ver, fmt.Errorf("failed to fetch provenance %q", u.String()+".prov") + } + fmt.Fprintf(c.Out, "WARNING: Verification not found for %s: %s\n", ref, err) + return pth, ver, nil + } + + ppth, err = c.Cache.Put(digest32, body, CacheProv) + if err != nil { + return "", nil, err + } + slog.Debug("put downloaded provenance file in cache", "id", hex.EncodeToString(digest32[:])) + } + + if c.Verify != VerifyLater { + + // provenance files pin to a specific name so this needs to be accounted for + // when verifying. + // Note, this does make an assumption that the name/version is unique to a + // hash when a provenance file is used. If this isn't true, this section of code + // will need to be reworked. + name := filepath.Base(u.Path) + if u.Scheme == registry.OCIScheme { + idx := strings.LastIndexByte(name, ':') + name = fmt.Sprintf("%s-%s.tgz", name[:idx], name[idx+1:]) + } + + // Copy chart to a known location with the right name for verification and then + // clean it up. + tmpdir := filepath.Dir(filepath.Join(c.ContentCache, "tmp")) + if err := os.MkdirAll(tmpdir, 0755); err != nil { + return pth, ver, err + } + tmpfile := filepath.Join(tmpdir, name) + err = ifs.CopyFile(pth, tmpfile) + if err != nil { + return pth, ver, err + } + // Not removing the tmp dir itself because a concurrent process may be using it + defer os.RemoveAll(tmpfile) + + ver, err = VerifyChart(tmpfile, ppth, c.Keyring) + if err != nil { + // Fail always in this case, since it means the verification step + // failed. + return pth, ver, err + } + } + } + return pth, ver, nil +} + +// ResolveChartVersion resolves a chart reference to a URL. +// +// It returns: +// - A hash of the content if available +// - The URL and sets the ChartDownloader's Options that can fetch the URL using the appropriate Getter. +// - An error if there is one +// +// A reference may be an HTTP URL, an oci reference URL, a 'reponame/chartname' +// reference, or a local path. +// +// A version is a SemVer string (1.2.3-beta.1+f334a6789). +// +// - For fully qualified URLs, the version will be ignored (since URLs aren't versioned) +// - For a chart reference +// - If version is non-empty, this will return the URL for that version +// - If version is empty, this will return the URL for the latest version +// - If no version can be found, an error is returned +// +// TODO: support OCI hash +func (c *ChartDownloader) ResolveChartVersion(ref, version string) (string, *url.URL, error) { + u, err := url.Parse(ref) + if err != nil { + return "", nil, fmt.Errorf("invalid chart URL format: %s", ref) + } + + if registry.IsOCI(u.String()) { + if c.RegistryClient == nil { + return "", nil, fmt.Errorf("unable to lookup ref %s at version '%s', missing registry client", ref, version) + } + + digest, OCIref, err := c.RegistryClient.ValidateReference(ref, version, u) + return digest, OCIref, err + } + + rf, err := loadRepoConfig(c.RepositoryConfig) + if err != nil { + return "", u, err + } + + if u.IsAbs() && len(u.Host) > 0 && len(u.Path) > 0 { + // In this case, we have to find the parent repo that contains this chart + // URL. And this is an unfortunate problem, as it requires actually going + // through each repo cache file and finding a matching URL. But basically + // we want to find the repo in case we have special SSL cert config + // for that repo. + + rc, err := c.scanReposForURL(ref, rf) + if err != nil { + // If there is no special config, return the default HTTP client and + // swallow the error. + if err == ErrNoOwnerRepo { + // Make sure to add the ref URL as the URL for the getter + c.Options = append(c.Options, getter.WithURL(ref)) + return "", u, nil + } + return "", u, err + } + + // If we get here, we don't need to go through the next phase of looking + // up the URL. We have it already. So we just set the parameters and return. + c.Options = append( + c.Options, + getter.WithURL(rc.URL), + ) + if rc.CertFile != "" || rc.KeyFile != "" || rc.CAFile != "" { + c.Options = append(c.Options, getter.WithTLSClientConfig(rc.CertFile, rc.KeyFile, rc.CAFile)) + } + if rc.Username != "" && rc.Password != "" { + c.Options = append( + c.Options, + getter.WithBasicAuth(rc.Username, rc.Password), + getter.WithPassCredentialsAll(rc.PassCredentialsAll), + ) + } + return "", u, nil + } + + // See if it's of the form: repo/path_to_chart + p := strings.SplitN(u.Path, "/", 2) + if len(p) < 2 { + return "", u, fmt.Errorf("non-absolute URLs should be in form of repo_name/path_to_chart, got: %s", u) + } + + repoName := p[0] + chartName := p[1] + rc, err := pickChartRepositoryConfigByName(repoName, rf.Repositories) + if err != nil { + return "", u, err + } + + // Now that we have the chart repository information we can use that URL + // to set the URL for the getter. + c.Options = append(c.Options, getter.WithURL(rc.URL)) + + r, err := repo.NewChartRepository(rc, c.Getters) + if err != nil { + return "", u, err + } + + if r != nil && r.Config != nil { + if r.Config.CertFile != "" || r.Config.KeyFile != "" || r.Config.CAFile != "" { + c.Options = append(c.Options, getter.WithTLSClientConfig(r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile)) + } + if r.Config.Username != "" && r.Config.Password != "" { + c.Options = append(c.Options, + getter.WithBasicAuth(r.Config.Username, r.Config.Password), + getter.WithPassCredentialsAll(r.Config.PassCredentialsAll), + ) + } + } + + // Next, we need to load the index, and actually look up the chart. + idxFile := filepath.Join(c.RepositoryCache, helmpath.CacheIndexFile(r.Config.Name)) + i, err := repo.LoadIndexFile(idxFile) + if err != nil { + return "", u, fmt.Errorf("no cached repo found. (try 'helm repo update'): %w", err) + } + + cv, err := i.Get(chartName, version) + if err != nil { + return "", u, fmt.Errorf("chart %q matching %s not found in %s index. (try 'helm repo update'): %w", chartName, version, r.Config.Name, err) + } + + if len(cv.URLs) == 0 { + return "", u, fmt.Errorf("chart %q has no downloadable URLs", ref) + } + + // TODO: Seems that picking first URL is not fully correct + resolvedURL, err := repo.ResolveReferenceURL(rc.URL, cv.URLs[0]) + if err != nil { + return cv.Digest, u, fmt.Errorf("invalid chart URL format: %s", ref) + } + + loc, err := url.Parse(resolvedURL) + return cv.Digest, loc, err +} + +// VerifyChart takes a path to a chart archive and a keyring, and verifies the chart. +// +// It assumes that a chart archive file is accompanied by a provenance file whose +// name is the archive file name plus the ".prov" extension. +func VerifyChart(path, provfile, keyring string) (*provenance.Verification, error) { + // For now, error out if it's not a tar file. + switch fi, err := os.Stat(path); { + case err != nil: + return nil, err + case fi.IsDir(): + return nil, errors.New("unpacked charts cannot be verified") + case !isTar(path): + return nil, errors.New("chart must be a tgz file") + } + + if _, err := os.Stat(provfile); err != nil { + return nil, fmt.Errorf("could not load provenance file %s: %w", provfile, err) + } + + sig, err := provenance.NewFromKeyring(keyring, "") + if err != nil { + return nil, fmt.Errorf("failed to load keyring: %w", err) + } + + // Read archive and provenance files + archiveData, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed to read chart archive: %w", err) + } + provData, err := os.ReadFile(provfile) + if err != nil { + return nil, fmt.Errorf("failed to read provenance file: %w", err) + } + + return sig.Verify(archiveData, provData, filepath.Base(path)) +} + +// isTar tests whether the given file is a tar file. +// +// Currently, this simply checks extension, since a subsequent function will +// untar the file and validate its binary format. +func isTar(filename string) bool { + return strings.EqualFold(filepath.Ext(filename), ".tgz") +} + +func pickChartRepositoryConfigByName(name string, cfgs []*repo.Entry) (*repo.Entry, error) { + for _, rc := range cfgs { + if rc.Name == name { + if rc.URL == "" { + return nil, fmt.Errorf("no URL found for repository %s", name) + } + return rc, nil + } + } + return nil, fmt.Errorf("repo %s not found", name) +} + +// scanReposForURL scans all repos to find which repo contains the given URL. +// +// This will attempt to find the given URL in all of the known repositories files. +// +// If the URL is found, this will return the repo entry that contained that URL. +// +// If all of the repos are checked, but the URL is not found, an ErrNoOwnerRepo +// error is returned. +// +// Other errors may be returned when repositories cannot be loaded or searched. +// +// Technically, the fact that a URL is not found in a repo is not a failure indication. +// Charts are not required to be included in an index before they are valid. So +// be mindful of this case. +// +// The same URL can technically exist in two or more repositories. This algorithm +// will return the first one it finds. Order is determined by the order of repositories +// in the repositories.yaml file. +func (c *ChartDownloader) scanReposForURL(u string, rf *repo.File) (*repo.Entry, error) { + // FIXME: This is far from optimal. Larger installations and index files will + // incur a performance hit for this type of scanning. + for _, rc := range rf.Repositories { + r, err := repo.NewChartRepository(rc, c.Getters) + if err != nil { + return nil, err + } + + idxFile := filepath.Join(c.RepositoryCache, helmpath.CacheIndexFile(r.Config.Name)) + i, err := repo.LoadIndexFile(idxFile) + if err != nil { + return nil, fmt.Errorf("no cached repo found. (try 'helm repo update'): %w", err) + } + + for _, entry := range i.Entries { + for _, ver := range entry { + for _, dl := range ver.URLs { + if urlutil.Equal(u, dl) { + return rc, nil + } + } + } + } + } + // This means that there is no repo file for the given URL. + return nil, ErrNoOwnerRepo +} + +func loadRepoConfig(file string) (*repo.File, error) { + r, err := repo.LoadFile(file) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return nil, err + } + return r, nil +} + +// stripDigestAlgorithm removes the algorithm prefix (e.g., "sha256:") from a digest string. +// If no prefix is present, the original string is returned unchanged. +func stripDigestAlgorithm(digest string) string { + if idx := strings.Index(digest, ":"); idx >= 0 { + return digest[idx+1:] + } + return digest +} diff --git a/vendor/helm.sh/helm/v4/pkg/downloader/doc.go b/vendor/helm.sh/helm/v4/pkg/downloader/doc.go new file mode 100644 index 00000000000..8484680907a --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/downloader/doc.go @@ -0,0 +1,24 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package downloader provides a library for downloading charts. + +This package contains various tools for downloading charts from repository +servers, and then storing them in Helm-specific directory structures. This +library contains many functions that depend on a specific +filesystem layout. +*/ +package downloader diff --git a/vendor/helm.sh/helm/v4/pkg/downloader/manager.go b/vendor/helm.sh/helm/v4/pkg/downloader/manager.go new file mode 100644 index 00000000000..6043fbaaa61 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/downloader/manager.go @@ -0,0 +1,922 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package downloader + +import ( + "crypto" + "encoding/hex" + "errors" + "fmt" + "io" + stdfs "io/fs" + "log" + "net/url" + "os" + "path/filepath" + "regexp" + "strings" + "sync" + + "github.com/Masterminds/semver/v3" + "sigs.k8s.io/yaml" + + "helm.sh/helm/v4/internal/resolver" + "helm.sh/helm/v4/internal/third_party/dep/fs" + "helm.sh/helm/v4/internal/urlutil" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/helmpath" + "helm.sh/helm/v4/pkg/registry" + "helm.sh/helm/v4/pkg/repo/v1" +) + +// ErrRepoNotFound indicates that chart repositories can't be found in local repo cache. +// The value of Repos is missing repos. +type ErrRepoNotFound struct { + Repos []string +} + +// Error implements the error interface. +func (e ErrRepoNotFound) Error() string { + return fmt.Sprintf("no repository definition for %s", strings.Join(e.Repos, ", ")) +} + +// Manager handles the lifecycle of fetching, resolving, and storing dependencies. +type Manager struct { + // Out is used to print warnings and notifications. + Out io.Writer + // ChartPath is the path to the unpacked base chart upon which this operates. + ChartPath string + // Verification indicates whether the chart should be verified. + Verify VerificationStrategy + // Debug is the global "--debug" flag + Debug bool + // Keyring is the key ring file. + Keyring string + // SkipUpdate indicates that the repository should not be updated first. + SkipUpdate bool + // Getter collection for the operation + Getters []getter.Provider + RegistryClient *registry.Client + RepositoryConfig string + RepositoryCache string + + // ContentCache is a location where a cache of charts can be stored + ContentCache string +} + +// Build rebuilds a local charts directory from a lockfile. +// +// If the lockfile is not present, this will run a Manager.Update() +// +// If SkipUpdate is set, this will not update the repository. +func (m *Manager) Build() error { + c, err := m.loadChartDir() + if err != nil { + return err + } + + // If a lock file is found, run a build from that. Otherwise, just do + // an update. + lock := c.Lock + if lock == nil { + return m.Update() + } + + // Check that all of the repos we're dependent on actually exist. + req := c.Metadata.Dependencies + + // If using apiVersion v1, calculate the hash before resolve repo names + // because resolveRepoNames will change req if req uses repo alias + // and Helm 2 calculate the digest from the original req + // Fix for: https://github.com/helm/helm/issues/7619 + var v2Sum string + if c.Metadata.APIVersion == chart.APIVersionV1 { + v2Sum, err = resolver.HashV2Req(req) + if err != nil { + return errors.New("the lock file (requirements.lock) is out of sync with the dependencies file (requirements.yaml). Please update the dependencies") + } + } + + if _, err := m.resolveRepoNames(req); err != nil { + return err + } + + if sum, err := resolver.HashReq(req, lock.Dependencies); err != nil || sum != lock.Digest { + // If lock digest differs and chart is apiVersion v1, it maybe because the lock was built + // with Helm 2 and therefore should be checked with Helm v2 hash + // Fix for: https://github.com/helm/helm/issues/7233 + if c.Metadata.APIVersion == chart.APIVersionV1 { + log.Println("warning: a valid Helm v3 hash was not found. Checking against Helm v2 hash...") + if v2Sum != lock.Digest { + return errors.New("the lock file (requirements.lock) is out of sync with the dependencies file (requirements.yaml). Please update the dependencies") + } + } else { + return errors.New("the lock file (Chart.lock) is out of sync with the dependencies file (Chart.yaml). Please update the dependencies") + } + } + + // Check that all of the repos we're dependent on actually exist. + if err := m.hasAllRepos(lock.Dependencies); err != nil { + return err + } + + if !m.SkipUpdate { + // For each repo in the file, update the cached copy of that repo + if err := m.UpdateRepositories(); err != nil { + return err + } + } + + // Now we need to fetch every package here into charts/ + return m.downloadAll(lock.Dependencies) +} + +// Update updates a local charts directory. +// +// It first reads the Chart.yaml file, and then attempts to +// negotiate versions based on that. It will download the versions +// from remote chart repositories unless SkipUpdate is true. +func (m *Manager) Update() error { + c, err := m.loadChartDir() + if err != nil { + return err + } + + // If no dependencies are found, we consider this a successful + // completion. + req := c.Metadata.Dependencies + if req == nil { + return nil + } + + // Get the names of the repositories the dependencies need that Helm is + // configured to know about. + repoNames, err := m.resolveRepoNames(req) + if err != nil { + return err + } + + // For the repositories Helm is not configured to know about, ensure Helm + // has some information about them and, when possible, the index files + // locally. + // TODO(mattfarina): Repositories should be explicitly added by end users + // rather than automatic. In Helm v4 require users to add repositories. They + // should have to add them in order to make sure they are aware of the + // repositories and opt-in to any locations, for security. + repoNames, err = m.ensureMissingRepos(repoNames, req) + if err != nil { + return err + } + + // For each of the repositories Helm is configured to know about, update + // the index information locally. + if !m.SkipUpdate { + if err := m.UpdateRepositories(); err != nil { + return err + } + } + + // Now we need to find out which version of a chart best satisfies the + // dependencies in the Chart.yaml + lock, err := m.resolve(req, repoNames) + if err != nil { + return err + } + + // Now we need to fetch every package here into charts/ + if err := m.downloadAll(lock.Dependencies); err != nil { + return err + } + + // downloadAll might overwrite dependency version, recalculate lock digest + newDigest, err := resolver.HashReq(req, lock.Dependencies) + if err != nil { + return err + } + lock.Digest = newDigest + + // If the lock file hasn't changed, don't write a new one. + oldLock := c.Lock + if oldLock != nil && oldLock.Digest == lock.Digest { + return nil + } + + // Finally, we need to write the lockfile. + return writeLock(m.ChartPath, lock, c.Metadata.APIVersion == chart.APIVersionV1) +} + +func (m *Manager) loadChartDir() (*chart.Chart, error) { + if fi, err := os.Stat(m.ChartPath); err != nil { + return nil, fmt.Errorf("could not find %s: %w", m.ChartPath, err) + } else if !fi.IsDir() { + return nil, errors.New("only unpacked charts can be updated") + } + return loader.LoadDir(m.ChartPath) +} + +// resolve takes a list of dependencies and translates them into an exact version to download. +// +// This returns a lock file, which has all of the dependencies normalized to a specific version. +func (m *Manager) resolve(req []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) { + res := resolver.New(m.ChartPath, m.RepositoryCache, m.RegistryClient) + return res.Resolve(req, repoNames) +} + +// downloadAll takes a list of dependencies and downloads them into charts/ +// +// It will delete versions of the chart that exist on disk and might cause +// a conflict. +func (m *Manager) downloadAll(deps []*chart.Dependency) error { + repos, err := m.loadChartRepositories() + if err != nil { + return err + } + + destPath := filepath.Join(m.ChartPath, "charts") + tmpPath := filepath.Join(m.ChartPath, fmt.Sprintf("tmpcharts-%d", os.Getpid())) + + // Check if 'charts' directory is not actually a directory. If it does not exist, create it. + if fi, err := os.Stat(destPath); err == nil { + if !fi.IsDir() { + return fmt.Errorf("%q is not a directory", destPath) + } + } else if errors.Is(err, stdfs.ErrNotExist) { + if err := os.MkdirAll(destPath, 0755); err != nil { + return err + } + } else { + return fmt.Errorf("unable to retrieve file info for '%s': %v", destPath, err) + } + + // Prepare tmpPath + if err := os.MkdirAll(tmpPath, 0755); err != nil { + return err + } + defer os.RemoveAll(tmpPath) + + fmt.Fprintf(m.Out, "Saving %d charts\n", len(deps)) + var saveError error + churls := make(map[string]struct{}) + for _, dep := range deps { + // No repository means the chart is in charts directory + if dep.Repository == "" { + fmt.Fprintf(m.Out, "Dependency %s did not declare a repository. Assuming it exists in the charts directory\n", dep.Name) + // NOTE: we are only validating the local dependency conforms to the constraints. No copying to tmpPath is necessary. + chartPath := filepath.Join(destPath, dep.Name) + ch, err := loader.LoadDir(chartPath) + if err != nil { + return fmt.Errorf("unable to load chart '%s': %v", chartPath, err) + } + + constraint, err := semver.NewConstraint(dep.Version) + if err != nil { + return fmt.Errorf("dependency %s has an invalid version/constraint format: %s", dep.Name, err) + } + + v, err := semver.NewVersion(ch.Metadata.Version) + if err != nil { + return fmt.Errorf("invalid version %s for dependency %s: %s", dep.Version, dep.Name, err) + } + + if !constraint.Check(v) { + saveError = fmt.Errorf("dependency %s at version %s does not satisfy the constraint %s", dep.Name, ch.Metadata.Version, dep.Version) + break + } + continue + } + if strings.HasPrefix(dep.Repository, "file://") { + if m.Debug { + fmt.Fprintf(m.Out, "Archiving %s from repo %s\n", dep.Name, dep.Repository) + } + ver, err := tarFromLocalDir(m.ChartPath, dep.Name, dep.Repository, dep.Version, tmpPath) + if err != nil { + saveError = err + break + } + dep.Version = ver + continue + } + + // Any failure to resolve/download a chart should fail: + // https://github.com/helm/helm/issues/1439 + churl, username, password, insecureSkipTLSVerify, passCredentialsAll, caFile, certFile, keyFile, err := m.findChartURL(dep.Name, dep.Version, dep.Repository, repos) + if err != nil { + saveError = fmt.Errorf("could not find %s: %w", churl, err) + break + } + + if _, ok := churls[churl]; ok { + fmt.Fprintf(m.Out, "Already downloaded %s from repo %s\n", dep.Name, dep.Repository) + continue + } + + fmt.Fprintf(m.Out, "Downloading %s from repo %s\n", dep.Name, dep.Repository) + + dl := ChartDownloader{ + Out: m.Out, + Verify: m.Verify, + Keyring: m.Keyring, + RepositoryConfig: m.RepositoryConfig, + RepositoryCache: m.RepositoryCache, + ContentCache: m.ContentCache, + RegistryClient: m.RegistryClient, + Getters: m.Getters, + Options: []getter.Option{ + getter.WithBasicAuth(username, password), + getter.WithPassCredentialsAll(passCredentialsAll), + getter.WithInsecureSkipVerifyTLS(insecureSkipTLSVerify), + getter.WithTLSClientConfig(certFile, keyFile, caFile), + }, + } + + version := "" + if registry.IsOCI(churl) { + churl, version, err = parseOCIRef(churl) + if err != nil { + return fmt.Errorf("could not parse OCI reference: %w", err) + } + dl.Options = append(dl.Options, + getter.WithRegistryClient(m.RegistryClient), + getter.WithTagName(version)) + } + + if _, _, err = dl.DownloadTo(churl, version, tmpPath); err != nil { + saveError = fmt.Errorf("could not download %s: %w", churl, err) + break + } + + churls[churl] = struct{}{} + } + + // TODO: this should probably be refactored to be a []error, so we can capture and provide more information rather than "last error wins". + if saveError == nil { + // now we can move all downloaded charts to destPath and delete outdated dependencies + if err := m.safeMoveDeps(deps, tmpPath, destPath); err != nil { + return err + } + } else { + fmt.Fprintln(m.Out, "Save error occurred: ", saveError) + return saveError + } + return nil +} + +func parseOCIRef(chartRef string) (string, string, error) { + refTagRegexp := regexp.MustCompile(`^(oci://[^:]+(:[0-9]{1,5})?[^:]+):(.*)$`) + caps := refTagRegexp.FindStringSubmatch(chartRef) + if len(caps) != 4 { + return "", "", fmt.Errorf("improperly formatted oci chart reference: %s", chartRef) + } + chartRef = caps[1] + tag := caps[3] + + return chartRef, tag, nil +} + +// safeMoveDeps moves all dependencies in the source and moves them into dest. +// +// It does this by first matching the file name to an expected pattern, then loading +// the file to verify that it is a chart. +// +// Any charts in dest that do not exist in source are removed (barring local dependencies) +// +// Because it requires tar file introspection, it is more intensive than a basic move. +// +// This will only return errors that should stop processing entirely. Other errors +// will emit log messages or be ignored. +func (m *Manager) safeMoveDeps(deps []*chart.Dependency, source, dest string) error { + existsInSourceDirectory := map[string]bool{} + isLocalDependency := map[string]bool{} + sourceFiles, err := os.ReadDir(source) + if err != nil { + return err + } + // attempt to read destFiles; fail fast if we can't + destFiles, err := os.ReadDir(dest) + if err != nil { + return err + } + + for _, dep := range deps { + if dep.Repository == "" { + isLocalDependency[dep.Name] = true + } + } + + for _, file := range sourceFiles { + if file.IsDir() { + continue + } + filename := file.Name() + sourcefile := filepath.Join(source, filename) + destfile := filepath.Join(dest, filename) + existsInSourceDirectory[filename] = true + if _, err := loader.LoadFile(sourcefile); err != nil { + fmt.Fprintf(m.Out, "Could not verify %s for moving: %s (Skipping)", sourcefile, err) + continue + } + // NOTE: no need to delete the dest; os.Rename replaces it. + if err := fs.RenameWithFallback(sourcefile, destfile); err != nil { + fmt.Fprintf(m.Out, "Unable to move %s to charts dir %s (Skipping)", sourcefile, err) + continue + } + } + + fmt.Fprintln(m.Out, "Deleting outdated charts") + // find all files that exist in dest that do not exist in source; delete them (outdated dependencies) + for _, file := range destFiles { + if !file.IsDir() && !existsInSourceDirectory[file.Name()] { + fname := filepath.Join(dest, file.Name()) + ch, err := loader.LoadFile(fname) + if err != nil { + fmt.Fprintf(m.Out, "Could not verify %s for deletion: %s (Skipping)\n", fname, err) + continue + } + // local dependency - skip + if isLocalDependency[ch.Name()] { + continue + } + if err := os.Remove(fname); err != nil { + fmt.Fprintf(m.Out, "Could not delete %s: %s (Skipping)", fname, err) + continue + } + } + } + + return nil +} + +// hasAllRepos ensures that all of the referenced deps are in the local repo cache. +func (m *Manager) hasAllRepos(deps []*chart.Dependency) error { + rf, err := loadRepoConfig(m.RepositoryConfig) + if err != nil { + return err + } + repos := rf.Repositories + + // Verify that all repositories referenced in the deps are actually known + // by Helm. + missing := []string{} +Loop: + for _, dd := range deps { + // If repo is from local path or OCI, continue + if strings.HasPrefix(dd.Repository, "file://") || registry.IsOCI(dd.Repository) { + continue + } + + if dd.Repository == "" { + continue + } + for _, repo := range repos { + if urlutil.Equal(repo.URL, strings.TrimSuffix(dd.Repository, "/")) { + continue Loop + } + } + missing = append(missing, dd.Repository) + } + if len(missing) > 0 { + return ErrRepoNotFound{missing} + } + return nil +} + +// ensureMissingRepos attempts to ensure the repository information for repos +// not managed by Helm is present. This takes in the repoNames Helm is configured +// to work with along with the chart dependencies. It will find the deps not +// in a known repo and attempt to ensure the data is present for steps like +// version resolution. +func (m *Manager) ensureMissingRepos(repoNames map[string]string, deps []*chart.Dependency) (map[string]string, error) { + + var ru []*repo.Entry + + for _, dd := range deps { + + // If the chart is in the local charts directory no repository needs + // to be specified. + if dd.Repository == "" { + continue + } + + // When the repoName for a dependency is known we can skip ensuring + if _, ok := repoNames[dd.Name]; ok { + continue + } + + // The generated repository name, which will result in an index being + // locally cached, has a name pattern of "helm-manager-" followed by a + // sha256 of the repo name. This assumes end users will never create + // repositories with these names pointing to other repositories. Using + // this method of naming allows the existing repository pulling and + // resolution code to do most of the work. + rn, err := key(dd.Repository) + if err != nil { + return repoNames, err + } + rn = managerKeyPrefix + rn + + repoNames[dd.Name] = rn + + // Assuming the repository is generally available. For Helm managed + // access controls the repository needs to be added through the user + // managed system. This path will work for public charts, like those + // supplied by Bitnami, but not for protected charts, like corp ones + // behind a username and pass. + ri := &repo.Entry{ + Name: rn, + URL: dd.Repository, + } + ru = append(ru, ri) + } + + // Calls to UpdateRepositories (a public function) will only update + // repositories configured by the user. Here we update repos found in + // the dependencies that are not known to the user if update skipping + // is not configured. + if !m.SkipUpdate && len(ru) > 0 { + fmt.Fprintln(m.Out, "Getting updates for unmanaged Helm repositories...") + if err := m.parallelRepoUpdate(ru); err != nil { + return repoNames, err + } + } + + return repoNames, nil +} + +// resolveRepoNames returns the repo names of the referenced deps which can be used to fetch the cached index file +// and replaces aliased repository URLs into resolved URLs in dependencies. +func (m *Manager) resolveRepoNames(deps []*chart.Dependency) (map[string]string, error) { + rf, err := loadRepoConfig(m.RepositoryConfig) + if err != nil { + if errors.Is(err, stdfs.ErrNotExist) { + return make(map[string]string), nil + } + return nil, err + } + repos := rf.Repositories + + reposMap := make(map[string]string) + + // Verify that all repositories referenced in the deps are actually known + // by Helm. + missing := []string{} + for _, dd := range deps { + // Don't map the repository, we don't need to download chart from charts directory + if dd.Repository == "" { + continue + } + // if dep chart is from local path, verify the path is valid + if strings.HasPrefix(dd.Repository, "file://") { + if _, err := resolver.GetLocalPath(dd.Repository, m.ChartPath); err != nil { + return nil, err + } + + if m.Debug { + fmt.Fprintf(m.Out, "Repository from local path: %s\n", dd.Repository) + } + reposMap[dd.Name] = dd.Repository + continue + } + + if registry.IsOCI(dd.Repository) { + reposMap[dd.Name] = dd.Repository + continue + } + + found := false + + for _, repo := range repos { + if (strings.HasPrefix(dd.Repository, "@") && strings.TrimPrefix(dd.Repository, "@") == repo.Name) || + (strings.HasPrefix(dd.Repository, "alias:") && strings.TrimPrefix(dd.Repository, "alias:") == repo.Name) { + found = true + dd.Repository = repo.URL + reposMap[dd.Name] = repo.Name + break + } else if urlutil.Equal(repo.URL, dd.Repository) { + found = true + reposMap[dd.Name] = repo.Name + break + } + } + if !found { + repository := dd.Repository + // Add if URL + _, err := url.ParseRequestURI(repository) + if err == nil { + reposMap[repository] = repository + continue + } + missing = append(missing, repository) + } + } + if len(missing) > 0 { + errorMessage := fmt.Sprintf("no repository definition for %s. Please add them via 'helm repo add'", strings.Join(missing, ", ")) + // It is common for people to try to enter "stable" as a repository instead of the actual URL. + // For this case, let's give them a suggestion. + containsNonURL := false + for _, repo := range missing { + if !strings.Contains(repo, "//") && !strings.HasPrefix(repo, "@") && !strings.HasPrefix(repo, "alias:") { + containsNonURL = true + } + } + if containsNonURL { + errorMessage += ` +Note that repositories must be URLs or aliases. For example, to refer to the "example" +repository, use "https://charts.example.com/" or "@example" instead of +"example". Don't forget to add the repo, too ('helm repo add').` + } + return nil, errors.New(errorMessage) + } + return reposMap, nil +} + +// UpdateRepositories updates all of the local repos to the latest. +func (m *Manager) UpdateRepositories() error { + rf, err := loadRepoConfig(m.RepositoryConfig) + if err != nil { + return err + } + repos := rf.Repositories + if len(repos) > 0 { + fmt.Fprintln(m.Out, "Hang tight while we grab the latest from your chart repositories...") + // This prints warnings straight to out. + if err := m.parallelRepoUpdate(repos); err != nil { + return err + } + fmt.Fprintln(m.Out, "Update Complete. ⎈Happy Helming!⎈") + } + return nil +} + +// Filter out duplicate repos by URL, including those with trailing slashes. +func dedupeRepos(repos []*repo.Entry) []*repo.Entry { + seen := make(map[string]*repo.Entry) + for _, r := range repos { + // Normalize URL by removing trailing slashes. + seenURL := strings.TrimSuffix(r.URL, "/") + seen[seenURL] = r + } + var unique []*repo.Entry + for _, r := range seen { + unique = append(unique, r) + } + return unique +} + +func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error { + + var wg sync.WaitGroup + + localRepos := dedupeRepos(repos) + + for _, c := range localRepos { + r, err := repo.NewChartRepository(c, m.Getters) + if err != nil { + return err + } + r.CachePath = m.RepositoryCache + wg.Add(1) + go func(r *repo.ChartRepository) { + if _, err := r.DownloadIndexFile(); err != nil { + // For those dependencies that are not known to helm and using a + // generated key name we display the repo url. + if strings.HasPrefix(r.Config.Name, managerKeyPrefix) { + fmt.Fprintf(m.Out, "...Unable to get an update from the %q chart repository:\n\t%s\n", r.Config.URL, err) + } else { + fmt.Fprintf(m.Out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", r.Config.Name, r.Config.URL, err) + } + } else { + // For those dependencies that are not known to helm and using a + // generated key name we display the repo url. + if strings.HasPrefix(r.Config.Name, managerKeyPrefix) { + fmt.Fprintf(m.Out, "...Successfully got an update from the %q chart repository\n", r.Config.URL) + } else { + fmt.Fprintf(m.Out, "...Successfully got an update from the %q chart repository\n", r.Config.Name) + } + } + wg.Done() + }(r) + } + wg.Wait() + + return nil +} + +// findChartURL searches the cache of repo data for a chart that has the name and the repoURL specified. +// +// 'name' is the name of the chart. Version is an exact semver, or an empty string. If empty, the +// newest version will be returned. +// +// repoURL is the repository to search +// +// If it finds a URL that is "relative", it will prepend the repoURL. +func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*repo.ChartRepository) (url, username, password string, insecureSkipTLSVerify, passCredentialsAll bool, caFile, certFile, keyFile string, err error) { + if registry.IsOCI(repoURL) { + return fmt.Sprintf("%s/%s:%s", repoURL, name, version), "", "", false, false, "", "", "", nil + } + + for _, cr := range repos { + if urlutil.Equal(repoURL, cr.Config.URL) { + var entry repo.ChartVersions + entry, err = findEntryByName(name, cr) + if err != nil { + // TODO: Where linting is skipped in this function we should + // refactor to remove naked returns while ensuring the same + // behavior + //nolint:nakedret + return + } + var ve *repo.ChartVersion + ve, err = findVersionedEntry(version, entry) + if err != nil { + //nolint:nakedret + return + } + url, err = repo.ResolveReferenceURL(repoURL, ve.URLs[0]) + if err != nil { + //nolint:nakedret + return + } + username = cr.Config.Username + password = cr.Config.Password + passCredentialsAll = cr.Config.PassCredentialsAll + insecureSkipTLSVerify = cr.Config.InsecureSkipTLSVerify + caFile = cr.Config.CAFile + certFile = cr.Config.CertFile + keyFile = cr.Config.KeyFile + //nolint:nakedret + return + } + } + url, err = repo.FindChartInRepoURL(repoURL, name, m.Getters, repo.WithChartVersion(version), repo.WithClientTLS(certFile, keyFile, caFile)) + if err == nil { + return url, username, password, false, false, "", "", "", err + } + err = fmt.Errorf("chart %s not found in %s: %w", name, repoURL, err) + return url, username, password, false, false, "", "", "", err +} + +// findEntryByName finds an entry in the chart repository whose name matches the given name. +// +// It returns the ChartVersions for that entry. +func findEntryByName(name string, cr *repo.ChartRepository) (repo.ChartVersions, error) { + for ename, entry := range cr.IndexFile.Entries { + if ename == name { + return entry, nil + } + } + return nil, errors.New("entry not found") +} + +// findVersionedEntry takes a ChartVersions list and returns a single chart version that satisfies the version constraints. +// +// If version is empty, the first chart found is returned. +func findVersionedEntry(version string, vers repo.ChartVersions) (*repo.ChartVersion, error) { + for _, verEntry := range vers { + if len(verEntry.URLs) == 0 { + // Not a legit entry. + continue + } + + if version == "" || versionEquals(version, verEntry.Version) { + return verEntry, nil + } + } + return nil, errors.New("no matching version") +} + +func versionEquals(v1, v2 string) bool { + sv1, err := semver.NewVersion(v1) + if err != nil { + // Fallback to string comparison. + return v1 == v2 + } + sv2, err := semver.NewVersion(v2) + if err != nil { + return false + } + return sv1.Equal(sv2) +} + +// loadChartRepositories reads the repositories.yaml, and then builds a map of +// ChartRepositories. +// +// The key is the local name (which is only present in the repositories.yaml). +func (m *Manager) loadChartRepositories() (map[string]*repo.ChartRepository, error) { + indices := map[string]*repo.ChartRepository{} + + // Load repositories.yaml file + rf, err := loadRepoConfig(m.RepositoryConfig) + if err != nil { + return indices, fmt.Errorf("failed to load %s: %w", m.RepositoryConfig, err) + } + + for _, re := range rf.Repositories { + lname := re.Name + idxFile := filepath.Join(m.RepositoryCache, helmpath.CacheIndexFile(lname)) + index, err := repo.LoadIndexFile(idxFile) + if err != nil { + return indices, err + } + + // TODO: use constructor + cr := &repo.ChartRepository{ + Config: re, + IndexFile: index, + } + indices[lname] = cr + } + return indices, nil +} + +// writeLock writes a lockfile to disk +func writeLock(chartpath string, lock *chart.Lock, legacyLockfile bool) error { + data, err := yaml.Marshal(lock) + if err != nil { + return err + } + lockfileName := "Chart.lock" + if legacyLockfile { + lockfileName = "requirements.lock" + } + dest := filepath.Join(chartpath, lockfileName) + + info, err := os.Lstat(dest) + if err != nil && !os.IsNotExist(err) { + return fmt.Errorf("error getting info for %q: %w", dest, err) + } else if err == nil { + if info.Mode()&os.ModeSymlink != 0 { + link, err := os.Readlink(dest) + if err != nil { + return fmt.Errorf("error reading symlink for %q: %w", dest, err) + } + return fmt.Errorf("the %s file is a symlink to %q", lockfileName, link) + } + } + + return os.WriteFile(dest, data, 0644) +} + +// archive a dep chart from local directory and save it into destPath +func tarFromLocalDir(chartpath, name, repo, version, destPath string) (string, error) { + if !strings.HasPrefix(repo, "file://") { + return "", fmt.Errorf("wrong format: chart %s repository %s", name, repo) + } + + origPath, err := resolver.GetLocalPath(repo, chartpath) + if err != nil { + return "", err + } + + ch, err := loader.LoadDir(origPath) + if err != nil { + return "", err + } + + constraint, err := semver.NewConstraint(version) + if err != nil { + return "", fmt.Errorf("dependency %s has an invalid version/constraint format: %w", name, err) + } + + v, err := semver.NewVersion(ch.Metadata.Version) + if err != nil { + return "", err + } + + if constraint.Check(v) { + _, err = chartutil.Save(ch, destPath) + return ch.Metadata.Version, err + } + + return "", fmt.Errorf("can't get a valid version for dependency %s", name) +} + +// The prefix to use for cache keys created by the manager for repo names +const managerKeyPrefix = "helm-manager-" + +// key is used to turn a name, such as a repository url, into a filesystem +// safe name that is unique for querying. To accomplish this a unique hash of +// the string is used. +func key(name string) (string, error) { + in := strings.NewReader(name) + hash := crypto.SHA256.New() + if _, err := io.Copy(hash, in); err != nil { + return "", nil + } + return hex.EncodeToString(hash.Sum(nil)), nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/engine/doc.go b/vendor/helm.sh/helm/v4/pkg/engine/doc.go new file mode 100644 index 00000000000..e764a829a25 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/engine/doc.go @@ -0,0 +1,24 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package engine implements the Go text template engine as needed for Helm. + +When Helm renders templates it does so with additional functions and different +modes (e.g., strict, lint mode). This package handles the helm specific +implementation. +*/ +package engine // import "helm.sh/helm/v4/pkg/engine" diff --git a/vendor/helm.sh/helm/v4/pkg/engine/engine.go b/vendor/helm.sh/helm/v4/pkg/engine/engine.go new file mode 100644 index 00000000000..f5db7e158f2 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/engine/engine.go @@ -0,0 +1,595 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package engine + +import ( + "errors" + "fmt" + "log/slog" + "maps" + "path" + "path/filepath" + "regexp" + "sort" + "strings" + "text/template" + + "k8s.io/client-go/rest" + + ci "helm.sh/helm/v4/pkg/chart" + "helm.sh/helm/v4/pkg/chart/common" +) + +// Engine is an implementation of the Helm rendering implementation for templates. +type Engine struct { + // If strict is enabled, template rendering will fail if a template references + // a value that was not passed in. + Strict bool + // In LintMode, some 'required' template values may be missing, so don't fail + LintMode bool + // optional provider of clients to talk to the Kubernetes API + clientProvider *ClientProvider + // EnableDNS tells the engine to allow DNS lookups when rendering templates + EnableDNS bool + // CustomTemplateFuncs is defined by users to provide custom template funcs + CustomTemplateFuncs template.FuncMap +} + +// New creates a new instance of Engine using the passed in rest config. +func New(config *rest.Config) Engine { + var clientProvider ClientProvider = clientProviderFromConfig{config} + return Engine{ + clientProvider: &clientProvider, + } +} + +// Render takes a chart, optional values, and value overrides, and attempts to render the Go templates. +// +// Render can be called repeatedly on the same engine. +// +// This will look in the chart's 'templates' data (e.g. the 'templates/' directory) +// and attempt to render the templates there using the values passed in. +// +// Values are scoped to their templates. A dependency template will not have +// access to the values set for its parent. If chart "foo" includes chart "bar", +// "bar" will not have access to the values for "foo". +// +// Values should be prepared with something like `chartutils.ReadValues`. +// +// Values are passed through the templates according to scope. If the top layer +// chart includes the chart foo, which includes the chart bar, the values map +// will be examined for a table called "foo". If "foo" is found in vals, +// that section of the values will be passed into the "foo" chart. And if that +// section contains a value named "bar", that value will be passed on to the +// bar chart during render time. +func (e Engine) Render(chrt ci.Charter, values common.Values) (map[string]string, error) { + tmap := allTemplates(chrt, values) + return e.render(tmap) +} + +// Render takes a chart, optional values, and value overrides, and attempts to +// render the Go templates using the default options. +func Render(chrt ci.Charter, values common.Values) (map[string]string, error) { + return new(Engine).Render(chrt, values) +} + +// RenderWithClient takes a chart, optional values, and value overrides, and attempts to +// render the Go templates using the default options. This engine is client aware and so can have template +// functions that interact with the client. +func RenderWithClient(chrt ci.Charter, values common.Values, config *rest.Config) (map[string]string, error) { + var clientProvider ClientProvider = clientProviderFromConfig{config} + return Engine{ + clientProvider: &clientProvider, + }.Render(chrt, values) +} + +// RenderWithClientProvider takes a chart, optional values, and value overrides, and attempts to +// render the Go templates using the default options. This engine is client aware and so can have template +// functions that interact with the client. +// This function differs from RenderWithClient in that it lets you customize the way a dynamic client is constructed. +func RenderWithClientProvider(chrt ci.Charter, values common.Values, clientProvider ClientProvider) (map[string]string, error) { + return Engine{ + clientProvider: &clientProvider, + }.Render(chrt, values) +} + +// renderable is an object that can be rendered. +type renderable struct { + // tpl is the current template. + tpl string + // vals are the values to be supplied to the template. + vals common.Values + // namespace prefix to the templates of the current chart + basePath string +} + +const warnStartDelim = "HELM_ERR_START" +const warnEndDelim = "HELM_ERR_END" +const recursionMaxNums = 1000 + +var warnRegex = regexp.MustCompile(warnStartDelim + `((?s).*)` + warnEndDelim) + +func warnWrap(warn string) string { + return warnStartDelim + warn + warnEndDelim +} + +// 'include' needs to be defined in the scope of a 'tpl' template as +// well as regular file-loaded templates. +func includeFun(t *template.Template, includedNames map[string]int) func(string, interface{}) (string, error) { + return func(name string, data interface{}) (string, error) { + var buf strings.Builder + if v, ok := includedNames[name]; ok { + if v > recursionMaxNums { + return "", fmt.Errorf( + "rendering template has a nested reference name: %s: %w", + name, errors.New("unable to execute template")) + } + includedNames[name]++ + } else { + includedNames[name] = 1 + } + err := t.ExecuteTemplate(&buf, name, data) + includedNames[name]-- + return buf.String(), err + } +} + +// As does 'tpl', so that nested calls to 'tpl' see the templates +// defined by their enclosing contexts. +func tplFun(parent *template.Template, includedNames map[string]int, strict bool) func(string, interface{}) (string, error) { + return func(tpl string, vals interface{}) (string, error) { + t, err := parent.Clone() + if err != nil { + return "", fmt.Errorf("cannot clone template: %w", err) + } + + // Re-inject the missingkey option, see text/template issue https://github.com/golang/go/issues/43022 + // We have to go by strict from our engine configuration, as the option fields are private in Template. + // TODO: Remove workaround (and the strict parameter) once we build only with golang versions with a fix. + if strict { + t.Option("missingkey=error") + } else { + t.Option("missingkey=zero") + } + + // Re-inject 'include' so that it can close over our clone of t; + // this lets any 'define's inside tpl be 'include'd. + t.Funcs(template.FuncMap{ + "include": includeFun(t, includedNames), + "tpl": tplFun(t, includedNames, strict), + }) + + // We need a .New template, as template text which is just blanks + // or comments after parsing out defines just adds new named + // template definitions without changing the main template. + // https://pkg.go.dev/text/template#Template.Parse + // Use the parent's name for lack of a better way to identify the tpl + // text string. (Maybe we could use a hash appended to the name?) + t, err = t.New(parent.Name()).Parse(tpl) + if err != nil { + return "", fmt.Errorf("cannot parse template %q: %w", tpl, err) + } + + var buf strings.Builder + if err := t.Execute(&buf, vals); err != nil { + return "", fmt.Errorf("error during tpl function execution for %q: %w", tpl, err) + } + + // See comment in renderWithReferences explaining the hack. + return strings.ReplaceAll(buf.String(), "", ""), nil + } +} + +// initFunMap creates the Engine's FuncMap and adds context-specific functions. +func (e Engine) initFunMap(t *template.Template) { + funcMap := funcMap() + includedNames := make(map[string]int) + + // Add the template-rendering functions here so we can close over t. + funcMap["include"] = includeFun(t, includedNames) + funcMap["tpl"] = tplFun(t, includedNames, e.Strict) + + // Add the `required` function here so we can use lintMode + funcMap["required"] = func(warn string, val interface{}) (interface{}, error) { + if val == nil { + if e.LintMode { + // Don't fail on missing required values when linting + slog.Warn("missing required value", "message", warn) + return "", nil + } + return val, errors.New(warnWrap(warn)) + } else if _, ok := val.(string); ok { + if val == "" { + if e.LintMode { + // Don't fail on missing required values when linting + slog.Warn("missing required values", "message", warn) + return "", nil + } + return val, errors.New(warnWrap(warn)) + } + } + return val, nil + } + + // Override sprig fail function for linting and wrapping message + funcMap["fail"] = func(msg string) (string, error) { + if e.LintMode { + // Don't fail when linting + slog.Info("funcMap fail", "message", msg) + return "", nil + } + return "", errors.New(warnWrap(msg)) + } + + // If we are not linting and have a cluster connection, provide a Kubernetes-backed + // implementation. + if !e.LintMode && e.clientProvider != nil { + funcMap["lookup"] = newLookupFunction(*e.clientProvider) + } + + // When DNS lookups are not enabled override the sprig function and return + // an empty string. + if !e.EnableDNS { + funcMap["getHostByName"] = func(_ string) string { + return "" + } + } + + // Set custom template funcs + maps.Copy(funcMap, e.CustomTemplateFuncs) + + t.Funcs(funcMap) +} + +// render takes a map of templates/values and renders them. +func (e Engine) render(tpls map[string]renderable) (rendered map[string]string, err error) { + // Basically, what we do here is start with an empty parent template and then + // build up a list of templates -- one for each file. Once all of the templates + // have been parsed, we loop through again and execute every template. + // + // The idea with this process is to make it possible for more complex templates + // to share common blocks, but to make the entire thing feel like a file-based + // template engine. + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("rendering template failed: %v", r) + } + }() + t := template.New("gotpl") + if e.Strict { + t.Option("missingkey=error") + } else { + // Not that zero will attempt to add default values for types it knows, + // but will still emit for others. We mitigate that later. + t.Option("missingkey=zero") + } + + e.initFunMap(t) + + // We want to parse the templates in a predictable order. The order favors + // higher-level (in file system) templates over deeply nested templates. + keys := sortTemplates(tpls) + + for _, filename := range keys { + r := tpls[filename] + if _, err := t.New(filename).Parse(r.tpl); err != nil { + return map[string]string{}, cleanupParseError(filename, err) + } + } + + rendered = make(map[string]string, len(keys)) + for _, filename := range keys { + // Don't render partials. We don't care out the direct output of partials. + // They are only included from other templates. + if strings.HasPrefix(path.Base(filename), "_") { + continue + } + // At render time, add information about the template that is being rendered. + vals := tpls[filename].vals + vals["Template"] = common.Values{"Name": filename, "BasePath": tpls[filename].basePath} + var buf strings.Builder + if err := t.ExecuteTemplate(&buf, filename, vals); err != nil { + return map[string]string{}, reformatExecErrorMsg(filename, err) + } + + // Work around the issue where Go will emit "" even if Options(missing=zero) + // is set. Since missing=error will never get here, we do not need to handle + // the Strict case. + rendered[filename] = strings.ReplaceAll(buf.String(), "", "") + } + + return rendered, nil +} + +func cleanupParseError(filename string, err error) error { + tokens := strings.Split(err.Error(), ": ") + if len(tokens) == 1 { + // This might happen if a non-templating error occurs + return fmt.Errorf("parse error in (%s): %s", filename, err) + } + // The first token is "template" + // The second token is either "filename:lineno" or "filename:lineNo:columnNo" + location := tokens[1] + // The remaining tokens make up a stacktrace-like chain, ending with the relevant error + errMsg := tokens[len(tokens)-1] + return fmt.Errorf("parse error at (%s): %s", location, errMsg) +} + +type TraceableError struct { + location string + message string + executedFunction string +} + +func (t TraceableError) String() string { + var errorString strings.Builder + if t.location != "" { + _, _ = fmt.Fprintf(&errorString, "%s\n ", t.location) + } + if t.executedFunction != "" { + _, _ = fmt.Fprintf(&errorString, "%s\n ", t.executedFunction) + } + if t.message != "" { + _, _ = fmt.Fprintf(&errorString, "%s\n", t.message) + } + return errorString.String() +} + +// parseTemplateExecErrorString parses a template execution error string from text/template +// without using regular expressions. It returns a TraceableError and true if parsing succeeded. +func parseTemplateExecErrorString(s string) (TraceableError, bool) { + const prefix = "template: " + if !strings.HasPrefix(s, prefix) { + return TraceableError{}, false + } + remainder := s[len(prefix):] + + // Special case: "template: no template %q associated with template %q" + // Matches https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/text/template/exec.go;l=191 + traceableError, done := parseTemplateNoTemplateError(s, remainder) + if done { + return traceableError, true + } + + // Executing form: ": executing \"\" at <>: [ template:...]" + // Matches https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/text/template/exec.go;l=141 + traceableError, done = parseTemplateExecutingAtErrorType(remainder) + if done { + return traceableError, true + } + + // Simple form: ": " + // Use LastIndex to avoid splitting colons within line:col info. + // Matches https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/text/template/exec.go;l=138 + traceableError, done = parseTemplateSimpleErrorString(remainder) + if done { + return traceableError, true + } + + return TraceableError{}, false +} + +// Special case: "template: no template %q associated with template %q" +// Matches https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/text/template/exec.go;l=191 +func parseTemplateNoTemplateError(s string, remainder string) (TraceableError, bool) { + if strings.HasPrefix(remainder, "no template ") { + return TraceableError{message: s}, true + } + return TraceableError{}, false +} + +// Simple form: ": " +// Use LastIndex to avoid splitting colons within line:col info. +// Matches https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/text/template/exec.go;l=138 +func parseTemplateSimpleErrorString(remainder string) (TraceableError, bool) { + if sep := strings.LastIndex(remainder, ": "); sep != -1 { + templateName := remainder[:sep] + errMsg := remainder[sep+2:] + if cut := strings.Index(errMsg, " template:"); cut != -1 { + errMsg = errMsg[:cut] + } + return TraceableError{location: templateName, message: errMsg}, true + } + return TraceableError{}, false +} + +// Executing form: ": executing \"\" at <>: [ template:...]" +// Matches https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/text/template/exec.go;l=141 +func parseTemplateExecutingAtErrorType(remainder string) (TraceableError, bool) { + if idx := strings.Index(remainder, ": executing "); idx != -1 { + templateName := remainder[:idx] + after := remainder[idx+len(": executing "):] + if len(after) == 0 || after[0] != '"' { + return TraceableError{}, false + } + // find closing quote for function name + endQuote := strings.IndexByte(after[1:], '"') + if endQuote == -1 { + return TraceableError{}, false + } + endQuote++ // account for offset we started at 1 + functionName := after[1:endQuote] + afterFunc := after[endQuote+1:] + + // expect: " at <" then location then ">: " then message + const atPrefix = " at <" + if !strings.HasPrefix(afterFunc, atPrefix) { + return TraceableError{}, false + } + afterAt := afterFunc[len(atPrefix):] + endLoc := strings.Index(afterAt, ">: ") + if endLoc == -1 { + return TraceableError{}, false + } + locationName := afterAt[:endLoc] + errMsg := afterAt[endLoc+len(">: "):] + + // trim chained next error starting with space + "template:" if present + if cut := strings.Index(errMsg, " template:"); cut != -1 { + errMsg = errMsg[:cut] + } + return TraceableError{ + location: templateName, + message: errMsg, + executedFunction: "executing \"" + functionName + "\" at <" + locationName + ">:", + }, true + } + return TraceableError{}, false +} + +// reformatExecErrorMsg takes an error message for template rendering and formats it into a formatted +// multi-line error string +func reformatExecErrorMsg(filename string, err error) error { + // This function parses the error message produced by text/template package. + // If it can parse out details from that error message such as the line number, template it failed on, + // and error description, then it will construct a new error that displays these details in a structured way. + // If there are issues with parsing the error message, the err passed into the function should return instead. + var execError template.ExecError + if !errors.As(err, &execError) { + return err + } + + tokens := strings.SplitN(err.Error(), ": ", 3) + if len(tokens) != 3 { + // This might happen if a non-templating error occurs + return fmt.Errorf("execution error in (%s): %s", filename, err) + } + + // The first token is "template" + // The second token is either "filename:lineno" or "filename:lineNo:columnNo" + location := tokens[1] + + parts := warnRegex.FindStringSubmatch(tokens[2]) + if len(parts) >= 2 { + return fmt.Errorf("execution error at (%s): %s", location, parts[1]) + } + current := err + var fileLocations []TraceableError + for current != nil { + if tr, ok := parseTemplateExecErrorString(current.Error()); ok { + if len(fileLocations) == 0 || fileLocations[len(fileLocations)-1] != tr { + fileLocations = append(fileLocations, tr) + } + } else { + return err + } + current = errors.Unwrap(current) + } + + var finalErrorString strings.Builder + for _, fileLocation := range fileLocations { + _, _ = fmt.Fprintf(&finalErrorString, "%s", fileLocation.String()) + } + + return errors.New(strings.TrimSpace(finalErrorString.String())) +} + +func sortTemplates(tpls map[string]renderable) []string { + keys := make([]string, len(tpls)) + i := 0 + for key := range tpls { + keys[i] = key + i++ + } + sort.Sort(sort.Reverse(byPathLen(keys))) + return keys +} + +type byPathLen []string + +func (p byPathLen) Len() int { return len(p) } +func (p byPathLen) Swap(i, j int) { p[j], p[i] = p[i], p[j] } +func (p byPathLen) Less(i, j int) bool { + a, b := p[i], p[j] + ca, cb := strings.Count(a, "/"), strings.Count(b, "/") + if ca == cb { + return strings.Compare(a, b) == -1 + } + return ca < cb +} + +// allTemplates returns all templates for a chart and its dependencies. +// +// As it goes, it also prepares the values in a scope-sensitive manner. +func allTemplates(c ci.Charter, vals common.Values) map[string]renderable { + templates := make(map[string]renderable) + recAllTpls(c, templates, vals) + return templates +} + +// recAllTpls recurses through the templates in a chart. +// +// As it recurses, it also sets the values to be appropriate for the template +// scope. +func recAllTpls(c ci.Charter, templates map[string]renderable, values common.Values) map[string]interface{} { + vals := values.AsMap() + subCharts := make(map[string]interface{}) + accessor, err := ci.NewAccessor(c) + if err != nil { + slog.Error("error accessing chart", "error", err) + } + chartMetaData := accessor.MetadataAsMap() + chartMetaData["IsRoot"] = accessor.IsRoot() + + next := map[string]interface{}{ + "Chart": chartMetaData, + "Files": newFiles(accessor.Files()), + "Release": vals["Release"], + "Capabilities": vals["Capabilities"], + "Values": make(common.Values), + "Subcharts": subCharts, + } + + // If there is a {{.Values.ThisChart}} in the parent metadata, + // copy that into the {{.Values}} for this template. + if accessor.IsRoot() { + next["Values"] = vals["Values"] + } else if vs, err := values.Table("Values." + accessor.Name()); err == nil { + next["Values"] = vs + } + + for _, child := range accessor.Dependencies() { + // TODO: Handle error + sub, _ := ci.NewAccessor(child) + subCharts[sub.Name()] = recAllTpls(child, templates, next) + } + + newParentID := accessor.ChartFullPath() + for _, t := range accessor.Templates() { + if t == nil { + continue + } + if !isTemplateValid(accessor, t.Name) { + continue + } + templates[path.Join(newParentID, t.Name)] = renderable{ + tpl: string(t.Data), + vals: next, + basePath: path.Join(newParentID, "templates"), + } + } + + return next +} + +// isTemplateValid returns true if the template is valid for the chart type +func isTemplateValid(accessor ci.Accessor, templateName string) bool { + if accessor.IsLibraryChart() { + return strings.HasPrefix(filepath.Base(templateName), "_") + } + return true +} diff --git a/vendor/helm.sh/helm/v4/pkg/engine/files.go b/vendor/helm.sh/helm/v4/pkg/engine/files.go new file mode 100644 index 00000000000..7834cac2c19 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/engine/files.go @@ -0,0 +1,165 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package engine + +import ( + "encoding/base64" + "path" + "strings" + + "github.com/gobwas/glob" + + "helm.sh/helm/v4/pkg/chart/common" +) + +// files is a map of files in a chart that can be accessed from a template. +type files map[string][]byte + +// NewFiles creates a new files from chart files. +// Given an []*chart.File (the format for files in a chart.Chart), extract a map of files. +func newFiles(from []*common.File) files { + files := make(map[string][]byte) + for _, f := range from { + files[f.Name] = f.Data + } + return files +} + +// GetBytes gets a file by path. +// +// The returned data is raw. In a template context, this is identical to calling +// {{index .Files $path}}. +// +// This is intended to be accessed from within a template, so a missed key returns +// an empty []byte. +func (f files) GetBytes(name string) []byte { + if v, ok := f[name]; ok { + return v + } + return []byte{} +} + +// Get returns a string representation of the given file. +// +// Fetch the contents of a file as a string. It is designed to be called in a +// template. +// +// {{.Files.Get "foo"}} +func (f files) Get(name string) string { + return string(f.GetBytes(name)) +} + +// Glob takes a glob pattern and returns another files object only containing +// matched files. +// +// This is designed to be called from a template. +// +// {{ range $name, $content := .Files.Glob("foo/**") }} +// {{ $name }}: | +// {{ .Files.Get($name) | indent 4 }}{{ end }} +func (f files) Glob(pattern string) files { + g, err := glob.Compile(pattern, '/') + if err != nil { + g, _ = glob.Compile("**") + } + + nf := newFiles(nil) + for name, contents := range f { + if g.Match(name) { + nf[name] = contents + } + } + + return nf +} + +// AsConfig turns a Files group and flattens it to a YAML map suitable for +// including in the 'data' section of a Kubernetes ConfigMap definition. +// Duplicate keys will be overwritten, so be aware that your file names +// (regardless of path) should be unique. +// +// This is designed to be called from a template, and will return empty string +// (via toYAML function) if it cannot be serialized to YAML, or if the Files +// object is nil. +// +// The output will not be indented, so you will want to pipe this to the +// 'indent' template function. +// +// data: +// +// {{ .Files.Glob("config/**").AsConfig() | indent 4 }} +func (f files) AsConfig() string { + if f == nil { + return "" + } + + m := make(map[string]string) + + // Explicitly convert to strings, and file names + for k, v := range f { + m[path.Base(k)] = string(v) + } + + return toYAML(m) +} + +// AsSecrets returns the base64-encoded value of a Files object suitable for +// including in the 'data' section of a Kubernetes Secret definition. +// Duplicate keys will be overwritten, so be aware that your file names +// (regardless of path) should be unique. +// +// This is designed to be called from a template, and will return empty string +// (via toYAML function) if it cannot be serialized to YAML, or if the Files +// object is nil. +// +// The output will not be indented, so you will want to pipe this to the +// 'indent' template function. +// +// data: +// +// {{ .Files.Glob("secrets/*").AsSecrets() | indent 4 }} +func (f files) AsSecrets() string { + if f == nil { + return "" + } + + m := make(map[string]string) + + for k, v := range f { + m[path.Base(k)] = base64.StdEncoding.EncodeToString(v) + } + + return toYAML(m) +} + +// Lines returns each line of a named file (split by "\n") as a slice, so it can +// be ranged over in your templates. +// +// This is designed to be called from a template. +// +// {{ range .Files.Lines "foo/bar.html" }} +// {{ . }}{{ end }} +func (f files) Lines(path string) []string { + if f == nil || f[path] == nil { + return []string{} + } + s := string(f[path]) + if s[len(s)-1] == '\n' { + s = s[:len(s)-1] + } + return strings.Split(s, "\n") +} diff --git a/vendor/helm.sh/helm/v4/pkg/engine/funcs.go b/vendor/helm.sh/helm/v4/pkg/engine/funcs.go new file mode 100644 index 00000000000..a97f8f1044e --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/engine/funcs.go @@ -0,0 +1,234 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package engine + +import ( + "bytes" + "encoding/json" + "maps" + "strings" + "text/template" + + "github.com/BurntSushi/toml" + "github.com/Masterminds/sprig/v3" + "sigs.k8s.io/yaml" + goYaml "sigs.k8s.io/yaml/goyaml.v3" +) + +// funcMap returns a mapping of all of the functions that Engine has. +// +// Because some functions are late-bound (e.g. contain context-sensitive +// data), the functions may not all perform identically outside of an Engine +// as they will inside of an Engine. +// +// Known late-bound functions: +// +// - "include" +// - "tpl" +// +// These are late-bound in Engine.Render(). The +// version included in the FuncMap is a placeholder. +func funcMap() template.FuncMap { + f := sprig.TxtFuncMap() + delete(f, "env") + delete(f, "expandenv") + + // Add some extra functionality + extra := template.FuncMap{ + "toToml": toTOML, + "fromToml": fromTOML, + "toYaml": toYAML, + "mustToYaml": mustToYAML, + "toYamlPretty": toYAMLPretty, + "fromYaml": fromYAML, + "fromYamlArray": fromYAMLArray, + "toJson": toJSON, + "mustToJson": mustToJSON, + "fromJson": fromJSON, + "fromJsonArray": fromJSONArray, + + // This is a placeholder for the "include" function, which is + // late-bound to a template. By declaring it here, we preserve the + // integrity of the linter. + "include": func(string, interface{}) string { return "not implemented" }, + "tpl": func(string, interface{}) interface{} { return "not implemented" }, + "required": func(string, interface{}) (interface{}, error) { return "not implemented", nil }, + // Provide a placeholder for the "lookup" function, which requires a kubernetes + // connection. + "lookup": func(string, string, string, string) (map[string]interface{}, error) { + return map[string]interface{}{}, nil + }, + } + + maps.Copy(f, extra) + + return f +} + +// toYAML takes an interface, marshals it to yaml, and returns a string. It will +// always return a string, even on marshal error (empty string). +// +// This is designed to be called from a template. +func toYAML(v interface{}) string { + data, err := yaml.Marshal(v) + if err != nil { + // Swallow errors inside of a template. + return "" + } + return strings.TrimSuffix(string(data), "\n") +} + +// mustToYAML takes an interface, marshals it to yaml, and returns a string. +// It will panic if there is an error. +// +// This is designed to be called from a template when need to ensure that the +// output YAML is valid. +func mustToYAML(v interface{}) string { + data, err := yaml.Marshal(v) + if err != nil { + panic(err) + } + return strings.TrimSuffix(string(data), "\n") +} + +func toYAMLPretty(v interface{}) string { + var data bytes.Buffer + encoder := goYaml.NewEncoder(&data) + encoder.SetIndent(2) + err := encoder.Encode(v) + + if err != nil { + // Swallow errors inside of a template. + return "" + } + return strings.TrimSuffix(data.String(), "\n") +} + +// fromYAML converts a YAML document into a map[string]interface{}. +// +// This is not a general-purpose YAML parser, and will not parse all valid +// YAML documents. Additionally, because its intended use is within templates +// it tolerates errors. It will insert the returned error message string into +// m["Error"] in the returned map. +func fromYAML(str string) map[string]interface{} { + m := map[string]interface{}{} + + if err := yaml.Unmarshal([]byte(str), &m); err != nil { + m["Error"] = err.Error() + } + return m +} + +// fromYAMLArray converts a YAML array into a []interface{}. +// +// This is not a general-purpose YAML parser, and will not parse all valid +// YAML documents. Additionally, because its intended use is within templates +// it tolerates errors. It will insert the returned error message string as +// the first and only item in the returned array. +func fromYAMLArray(str string) []interface{} { + a := []interface{}{} + + if err := yaml.Unmarshal([]byte(str), &a); err != nil { + a = []interface{}{err.Error()} + } + return a +} + +// toTOML takes an interface, marshals it to toml, and returns a string. It will +// always return a string, even on marshal error (empty string). +// +// This is designed to be called from a template. +func toTOML(v interface{}) string { + b := bytes.NewBuffer(nil) + e := toml.NewEncoder(b) + err := e.Encode(v) + if err != nil { + return err.Error() + } + return b.String() +} + +// fromTOML converts a TOML document into a map[string]interface{}. +// +// This is not a general-purpose TOML parser, and will not parse all valid +// TOML documents. Additionally, because its intended use is within templates +// it tolerates errors. It will insert the returned error message string into +// m["Error"] in the returned map. +func fromTOML(str string) map[string]interface{} { + m := make(map[string]interface{}) + + if err := toml.Unmarshal([]byte(str), &m); err != nil { + m["Error"] = err.Error() + } + return m +} + +// toJSON takes an interface, marshals it to json, and returns a string. It will +// always return a string, even on marshal error (empty string). +// +// This is designed to be called from a template. +func toJSON(v interface{}) string { + data, err := json.Marshal(v) + if err != nil { + // Swallow errors inside of a template. + return "" + } + return string(data) +} + +// mustToJSON takes an interface, marshals it to json, and returns a string. +// It will panic if there is an error. +// +// This is designed to be called from a template when need to ensure that the +// output JSON is valid. +func mustToJSON(v interface{}) string { + data, err := json.Marshal(v) + if err != nil { + panic(err) + } + return string(data) +} + +// fromJSON converts a JSON document into a map[string]interface{}. +// +// This is not a general-purpose JSON parser, and will not parse all valid +// JSON documents. Additionally, because its intended use is within templates +// it tolerates errors. It will insert the returned error message string into +// m["Error"] in the returned map. +func fromJSON(str string) map[string]interface{} { + m := make(map[string]interface{}) + + if err := json.Unmarshal([]byte(str), &m); err != nil { + m["Error"] = err.Error() + } + return m +} + +// fromJSONArray converts a JSON array into a []interface{}. +// +// This is not a general-purpose JSON parser, and will not parse all valid +// JSON documents. Additionally, because its intended use is within templates +// it tolerates errors. It will insert the returned error message string as +// the first and only item in the returned array. +func fromJSONArray(str string) []interface{} { + a := []interface{}{} + + if err := json.Unmarshal([]byte(str), &a); err != nil { + a = []interface{}{err.Error()} + } + return a +} diff --git a/vendor/helm.sh/helm/v4/pkg/engine/lookup_func.go b/vendor/helm.sh/helm/v4/pkg/engine/lookup_func.go new file mode 100644 index 00000000000..c6ad8d252c8 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/engine/lookup_func.go @@ -0,0 +1,148 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package engine + +import ( + "context" + "fmt" + "log/slog" + "strings" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" +) + +type lookupFunc = func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error) + +// NewLookupFunction returns a function for looking up objects in the cluster. +// +// If the resource does not exist, no error is raised. +func NewLookupFunction(config *rest.Config) lookupFunc { //nolint:revive + return newLookupFunction(clientProviderFromConfig{config: config}) +} + +type ClientProvider interface { + // GetClientFor returns a dynamic.NamespaceableResourceInterface suitable for interacting with resources + // corresponding to the provided apiVersion and kind, as well as a boolean indicating whether the resources + // are namespaced. + GetClientFor(apiVersion, kind string) (dynamic.NamespaceableResourceInterface, bool, error) +} + +type clientProviderFromConfig struct { + config *rest.Config +} + +func (c clientProviderFromConfig) GetClientFor(apiVersion, kind string) (dynamic.NamespaceableResourceInterface, bool, error) { + return getDynamicClientOnKind(apiVersion, kind, c.config) +} + +func newLookupFunction(clientProvider ClientProvider) lookupFunc { + return func(apiversion string, kind string, namespace string, name string) (map[string]interface{}, error) { + var client dynamic.ResourceInterface + c, namespaced, err := clientProvider.GetClientFor(apiversion, kind) + if err != nil { + return map[string]interface{}{}, err + } + if namespaced && namespace != "" { + client = c.Namespace(namespace) + } else { + client = c + } + if name != "" { + // this will return a single object + obj, err := client.Get(context.Background(), name, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + // Just return an empty interface when the object was not found. + // That way, users can use `if not (lookup ...)` in their templates. + return map[string]interface{}{}, nil + } + return map[string]interface{}{}, err + } + return obj.UnstructuredContent(), nil + } + // this will return a list + obj, err := client.List(context.Background(), metav1.ListOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + // Just return an empty interface when the object was not found. + // That way, users can use `if not (lookup ...)` in their templates. + return map[string]interface{}{}, nil + } + return map[string]interface{}{}, err + } + return obj.UnstructuredContent(), nil + } +} + +// getDynamicClientOnKind returns a dynamic client on an Unstructured type. This client can be further namespaced. +func getDynamicClientOnKind(apiversion string, kind string, config *rest.Config) (dynamic.NamespaceableResourceInterface, bool, error) { + gvk := schema.FromAPIVersionAndKind(apiversion, kind) + apiRes, err := getAPIResourceForGVK(gvk, config) + if err != nil { + slog.Error( + "unable to get apiresource", + slog.String("groupVersionKind", gvk.String()), + slog.Any("error", err), + ) + return nil, false, fmt.Errorf("unable to get apiresource from unstructured: %s: %w", gvk.String(), err) + } + gvr := schema.GroupVersionResource{ + Group: apiRes.Group, + Version: apiRes.Version, + Resource: apiRes.Name, + } + intf, err := dynamic.NewForConfig(config) + if err != nil { + slog.Error("unable to get dynamic client", slog.Any("error", err)) + return nil, false, err + } + res := intf.Resource(gvr) + return res, apiRes.Namespaced, nil +} + +func getAPIResourceForGVK(gvk schema.GroupVersionKind, config *rest.Config) (metav1.APIResource, error) { + res := metav1.APIResource{} + discoveryClient, err := discovery.NewDiscoveryClientForConfig(config) + if err != nil { + slog.Error("unable to create discovery client", slog.Any("error", err)) + return res, err + } + resList, err := discoveryClient.ServerResourcesForGroupVersion(gvk.GroupVersion().String()) + if err != nil { + slog.Error( + "unable to retrieve resource list", + slog.String("GroupVersion", gvk.GroupVersion().String()), + slog.Any("error", err), + ) + return res, err + } + for _, resource := range resList.APIResources { + // if a resource contains a "/" it's referencing a subresource. we don't support subresource for now. + if resource.Kind == gvk.Kind && !strings.Contains(resource.Name, "/") { + res = resource + res.Group = gvk.Group + res.Version = gvk.Version + break + } + } + return res, nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/getter/doc.go b/vendor/helm.sh/helm/v4/pkg/getter/doc.go new file mode 100644 index 00000000000..11cf6153eb2 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/getter/doc.go @@ -0,0 +1,22 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package getter provides a generalize tool for fetching data by scheme. + +This provides a method by which the plugin system can load arbitrary protocol +handlers based upon a URL scheme. +*/ +package getter diff --git a/vendor/helm.sh/helm/v4/pkg/getter/getter.go b/vendor/helm.sh/helm/v4/pkg/getter/getter.go new file mode 100644 index 00000000000..a2d0f0ee254 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/getter/getter.go @@ -0,0 +1,232 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package getter + +import ( + "bytes" + "fmt" + "net/http" + "slices" + "time" + + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/registry" +) + +// getterOptions are generic parameters to be provided to the getter during instantiation. +// +// Getters may or may not ignore these parameters as they are passed in. +// TODO what is the difference between this and schema.GetterOptionsV1? +type getterOptions struct { + url string + certFile string + keyFile string + caFile string + unTar bool + insecureSkipVerifyTLS bool + plainHTTP bool + acceptHeader string + username string + password string + passCredentialsAll bool + userAgent string + version string + registryClient *registry.Client + timeout time.Duration + transport *http.Transport + artifactType string +} + +// Option allows specifying various settings configurable by the user for overriding the defaults +// used when performing Get operations with the Getter. +type Option func(*getterOptions) + +// WithURL informs the getter the server name that will be used when fetching objects. Used in conjunction with +// WithTLSClientConfig to set the TLSClientConfig's server name. +func WithURL(url string) Option { + return func(opts *getterOptions) { + opts.url = url + } +} + +// WithAcceptHeader sets the request's Accept header as some REST APIs serve multiple content types +func WithAcceptHeader(header string) Option { + return func(opts *getterOptions) { + opts.acceptHeader = header + } +} + +// WithBasicAuth sets the request's Authorization header to use the provided credentials +func WithBasicAuth(username, password string) Option { + return func(opts *getterOptions) { + opts.username = username + opts.password = password + } +} + +func WithPassCredentialsAll(pass bool) Option { + return func(opts *getterOptions) { + opts.passCredentialsAll = pass + } +} + +// WithUserAgent sets the request's User-Agent header to use the provided agent name. +func WithUserAgent(userAgent string) Option { + return func(opts *getterOptions) { + opts.userAgent = userAgent + } +} + +// WithInsecureSkipVerifyTLS determines if a TLS Certificate will be checked +func WithInsecureSkipVerifyTLS(insecureSkipVerifyTLS bool) Option { + return func(opts *getterOptions) { + opts.insecureSkipVerifyTLS = insecureSkipVerifyTLS + } +} + +// WithTLSClientConfig sets the client auth with the provided credentials. +func WithTLSClientConfig(certFile, keyFile, caFile string) Option { + return func(opts *getterOptions) { + opts.certFile = certFile + opts.keyFile = keyFile + opts.caFile = caFile + } +} + +func WithPlainHTTP(plainHTTP bool) Option { + return func(opts *getterOptions) { + opts.plainHTTP = plainHTTP + } +} + +// WithTimeout sets the timeout for requests +func WithTimeout(timeout time.Duration) Option { + return func(opts *getterOptions) { + opts.timeout = timeout + } +} + +func WithTagName(tagname string) Option { + return func(opts *getterOptions) { + opts.version = tagname + } +} + +func WithRegistryClient(client *registry.Client) Option { + return func(opts *getterOptions) { + opts.registryClient = client + } +} + +func WithUntar() Option { + return func(opts *getterOptions) { + opts.unTar = true + } +} + +// WithTransport sets the http.Transport to allow overwriting the HTTPGetter default. +func WithTransport(transport *http.Transport) Option { + return func(opts *getterOptions) { + opts.transport = transport + } +} + +// WithArtifactType sets the type of OCI artifact ("chart" or "plugin") +func WithArtifactType(artifactType string) Option { + return func(opts *getterOptions) { + opts.artifactType = artifactType + } +} + +// Getter is an interface to support GET to the specified URL. +type Getter interface { + // Get file content by url string + Get(url string, options ...Option) (*bytes.Buffer, error) +} + +// Constructor is the function for every getter which creates a specific instance +// according to the configuration +type Constructor func(options ...Option) (Getter, error) + +// Provider represents any getter and the schemes that it supports. +// +// For example, an HTTP provider may provide one getter that handles both +// 'http' and 'https' schemes. +type Provider struct { + Schemes []string + New Constructor +} + +// Provides returns true if the given scheme is supported by this Provider. +func (p Provider) Provides(scheme string) bool { + return slices.Contains(p.Schemes, scheme) +} + +// Providers is a collection of Provider objects. +type Providers []Provider + +// ByScheme returns a Provider that handles the given scheme. +// +// If no provider handles this scheme, this will return an error. +func (p Providers) ByScheme(scheme string) (Getter, error) { + for _, pp := range p { + if pp.Provides(scheme) { + return pp.New() + } + } + return nil, fmt.Errorf("scheme %q not supported", scheme) +} + +const ( + // The cost timeout references curl's default connection timeout. + // https://github.com/curl/curl/blob/master/lib/connect.h#L40C21-L40C21 + // The helm commands are usually executed manually. Considering the acceptable waiting time, we reduced the entire request time to 120s. + DefaultHTTPTimeout = 120 +) + +var defaultOptions = []Option{WithTimeout(time.Second * DefaultHTTPTimeout)} + +func Getters(extraOpts ...Option) Providers { + return Providers{ + Provider{ + Schemes: []string{"http", "https"}, + New: func(options ...Option) (Getter, error) { + options = append(options, defaultOptions...) + options = append(options, extraOpts...) + return NewHTTPGetter(options...) + }, + }, + Provider{ + Schemes: []string{registry.OCIScheme}, + New: func(options ...Option) (Getter, error) { + options = append(options, defaultOptions...) + options = append(options, extraOpts...) + return NewOCIGetter(options...) + }, + }, + } +} + +// All finds all of the registered getters as a list of Provider instances. +// Currently, the built-in getters and the discovered plugins with downloader +// notations are collected. +func All(settings *cli.EnvSettings, opts ...Option) Providers { + result := Getters(opts...) + pluginDownloaders, _ := collectGetterPlugins(settings) + result = append(result, pluginDownloaders...) + return result +} diff --git a/vendor/helm.sh/helm/v4/pkg/getter/httpgetter.go b/vendor/helm.sh/helm/v4/pkg/getter/httpgetter.go new file mode 100644 index 00000000000..110f45c54eb --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/getter/httpgetter.go @@ -0,0 +1,160 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package getter + +import ( + "bytes" + "crypto/tls" + "fmt" + "io" + "net/http" + "net/url" + "sync" + + "helm.sh/helm/v4/internal/tlsutil" + "helm.sh/helm/v4/internal/version" +) + +// HTTPGetter is the default HTTP(/S) backend handler +type HTTPGetter struct { + opts getterOptions + transport *http.Transport + once sync.Once +} + +// Get performs a Get from repo.Getter and returns the body. +func (g *HTTPGetter) Get(href string, options ...Option) (*bytes.Buffer, error) { + for _, opt := range options { + opt(&g.opts) + } + return g.get(href) +} + +func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) { + // Set a helm specific user agent so that a repo server and metrics can + // separate helm calls from other tools interacting with repos. + req, err := http.NewRequest(http.MethodGet, href, nil) + if err != nil { + return nil, err + } + + if g.opts.acceptHeader != "" { + req.Header.Set("Accept", g.opts.acceptHeader) + } + + req.Header.Set("User-Agent", version.GetUserAgent()) + if g.opts.userAgent != "" { + req.Header.Set("User-Agent", g.opts.userAgent) + } + + // Before setting the basic auth credentials, make sure the URL associated + // with the basic auth is the one being fetched. + u1, err := url.Parse(g.opts.url) + if err != nil { + return nil, fmt.Errorf("unable to parse getter URL: %w", err) + } + u2, err := url.Parse(href) + if err != nil { + return nil, fmt.Errorf("unable to parse URL getting from: %w", err) + } + + // Host on URL (returned from url.Parse) contains the port if present. + // This check ensures credentials are not passed between different + // services on different ports. + if g.opts.passCredentialsAll || (u1.Scheme == u2.Scheme && u1.Host == u2.Host) { + if g.opts.username != "" && g.opts.password != "" { + req.SetBasicAuth(g.opts.username, g.opts.password) + } + } + + client, err := g.httpClient() + if err != nil { + return nil, err + } + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to fetch %s : %s", href, resp.Status) + } + + buf := bytes.NewBuffer(nil) + _, err = io.Copy(buf, resp.Body) + return buf, err +} + +// NewHTTPGetter constructs a valid http/https client as a Getter +func NewHTTPGetter(options ...Option) (Getter, error) { + var client HTTPGetter + + for _, opt := range options { + opt(&client.opts) + } + + return &client, nil +} + +func (g *HTTPGetter) httpClient() (*http.Client, error) { + if g.opts.transport != nil { + return &http.Client{ + Transport: g.opts.transport, + Timeout: g.opts.timeout, + }, nil + } + + g.once.Do(func() { + g.transport = &http.Transport{ + DisableCompression: true, + Proxy: http.ProxyFromEnvironment, + // Being nil would cause the tls.Config default to be used + // "NewTLSConfig" modifies an empty TLS config, not the default one + TLSClientConfig: &tls.Config{}, + } + }) + + if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" || g.opts.insecureSkipVerifyTLS { + tlsConf, err := tlsutil.NewTLSConfig( + tlsutil.WithInsecureSkipVerify(g.opts.insecureSkipVerifyTLS), + tlsutil.WithCertKeyPairFiles(g.opts.certFile, g.opts.keyFile), + tlsutil.WithCAFile(g.opts.caFile), + ) + if err != nil { + return nil, fmt.Errorf("can't create TLS config for client: %w", err) + } + + g.transport.TLSClientConfig = tlsConf + } + + if g.opts.insecureSkipVerifyTLS { + if g.transport.TLSClientConfig == nil { + g.transport.TLSClientConfig = &tls.Config{ + InsecureSkipVerify: true, + } + } else { + g.transport.TLSClientConfig.InsecureSkipVerify = true + } + } + + client := &http.Client{ + Transport: g.transport, + Timeout: g.opts.timeout, + } + + return client, nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/getter/ocigetter.go b/vendor/helm.sh/helm/v4/pkg/getter/ocigetter.go new file mode 100644 index 00000000000..24fc60c5652 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/getter/ocigetter.go @@ -0,0 +1,213 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package getter + +import ( + "bytes" + "crypto/tls" + "fmt" + "net" + "net/http" + "path" + "strings" + "sync" + "time" + + "helm.sh/helm/v4/internal/tlsutil" + "helm.sh/helm/v4/internal/urlutil" + "helm.sh/helm/v4/pkg/registry" +) + +// OCIGetter is the default HTTP(/S) backend handler +type OCIGetter struct { + opts getterOptions + transport *http.Transport + once sync.Once +} + +// Get performs a Get from repo.Getter and returns the body. +func (g *OCIGetter) Get(href string, options ...Option) (*bytes.Buffer, error) { + for _, opt := range options { + opt(&g.opts) + } + return g.get(href) +} + +func (g *OCIGetter) get(href string) (*bytes.Buffer, error) { + client := g.opts.registryClient + // if the user has already provided a configured registry client, use it, + // this is particularly true when user has his own way of handling the client credentials. + if client == nil { + c, err := g.newRegistryClient() + if err != nil { + return nil, err + } + client = c + } + + ref := strings.TrimPrefix(href, fmt.Sprintf("%s://", registry.OCIScheme)) + + if version := g.opts.version; version != "" && !strings.Contains(path.Base(ref), ":") { + ref = fmt.Sprintf("%s:%s", ref, version) + } + // Check if this is a plugin request + if g.opts.artifactType == "plugin" { + return g.getPlugin(client, ref) + } + + // Default to chart behavior for backward compatibility + var pullOpts []registry.PullOption + requestingProv := strings.HasSuffix(ref, ".prov") + if requestingProv { + ref = strings.TrimSuffix(ref, ".prov") + pullOpts = append(pullOpts, + registry.PullOptWithChart(false), + registry.PullOptWithProv(true)) + } + + result, err := client.Pull(ref, pullOpts...) + if err != nil { + return nil, err + } + + if requestingProv { + return bytes.NewBuffer(result.Prov.Data), nil + } + return bytes.NewBuffer(result.Chart.Data), nil +} + +// NewOCIGetter constructs a valid http/https client as a Getter +func NewOCIGetter(ops ...Option) (Getter, error) { + var client OCIGetter + + for _, opt := range ops { + opt(&client.opts) + } + + return &client, nil +} + +func (g *OCIGetter) newRegistryClient() (*registry.Client, error) { + if g.opts.transport != nil { + client, err := registry.NewClient( + registry.ClientOptHTTPClient(&http.Client{ + Transport: g.opts.transport, + Timeout: g.opts.timeout, + }), + ) + if err != nil { + return nil, err + } + return client, nil + } + + g.once.Do(func() { + g.transport = &http.Transport{ + // From https://github.com/google/go-containerregistry/blob/31786c6cbb82d6ec4fb8eb79cd9387905130534e/pkg/v1/remote/options.go#L87 + DisableCompression: true, + DialContext: (&net.Dialer{ + // By default we wrap the transport in retries, so reduce the + // default dial timeout to 5s to avoid 5x 30s of connection + // timeouts when doing the "ping" on certain http registries. + Timeout: 5 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + Proxy: http.ProxyFromEnvironment, + // Being nil would cause the tls.Config default to be used + // "NewTLSConfig" modifies an empty TLS config, not the default one + TLSClientConfig: &tls.Config{}, + } + }) + + if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" || g.opts.insecureSkipVerifyTLS { + tlsConf, err := tlsutil.NewTLSConfig( + tlsutil.WithInsecureSkipVerify(g.opts.insecureSkipVerifyTLS), + tlsutil.WithCertKeyPairFiles(g.opts.certFile, g.opts.keyFile), + tlsutil.WithCAFile(g.opts.caFile), + ) + if err != nil { + return nil, fmt.Errorf("can't create TLS config for client: %w", err) + } + + sni, err := urlutil.ExtractHostname(g.opts.url) + if err != nil { + return nil, err + } + tlsConf.ServerName = sni + + g.transport.TLSClientConfig = tlsConf + } + + opts := []registry.ClientOption{registry.ClientOptHTTPClient(&http.Client{ + Transport: g.transport, + Timeout: g.opts.timeout, + })} + if g.opts.plainHTTP { + opts = append(opts, registry.ClientOptPlainHTTP()) + } + + client, err := registry.NewClient(opts...) + + if err != nil { + return nil, err + } + + return client, nil +} + +// getPlugin handles plugin-specific OCI pulls +func (g *OCIGetter) getPlugin(client *registry.Client, ref string) (*bytes.Buffer, error) { + // Check if this is a provenance file request + requestingProv := strings.HasSuffix(ref, ".prov") + if requestingProv { + ref = strings.TrimSuffix(ref, ".prov") + } + + // Extract plugin name from the reference + // e.g., "ghcr.io/user/plugin-name:v1.0.0" -> "plugin-name" + parts := strings.Split(ref, "/") + if len(parts) < 2 { + return nil, fmt.Errorf("invalid OCI reference: %s", ref) + } + lastPart := parts[len(parts)-1] + pluginName := lastPart + if idx := strings.LastIndex(lastPart, ":"); idx > 0 { + pluginName = lastPart[:idx] + } + if idx := strings.LastIndex(lastPart, "@"); idx > 0 { + pluginName = lastPart[:idx] + } + + var pullOpts []registry.PluginPullOption + if requestingProv { + pullOpts = append(pullOpts, registry.PullPluginOptWithProv(true)) + } + + result, err := client.PullPlugin(ref, pluginName, pullOpts...) + if err != nil { + return nil, err + } + + if requestingProv { + return bytes.NewBuffer(result.Prov.Data), nil + } + return bytes.NewBuffer(result.PluginData), nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/getter/plugingetter.go b/vendor/helm.sh/helm/v4/pkg/getter/plugingetter.go new file mode 100644 index 00000000000..ef8b87503cd --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/getter/plugingetter.go @@ -0,0 +1,129 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package getter + +import ( + "bytes" + "context" + "fmt" + + "net/url" + + "helm.sh/helm/v4/internal/plugin" + + "helm.sh/helm/v4/internal/plugin/schema" + "helm.sh/helm/v4/pkg/cli" +) + +// collectGetterPlugins scans for getter plugins. +// This will load plugins according to the cli. +func collectGetterPlugins(settings *cli.EnvSettings) (Providers, error) { + d := plugin.Descriptor{ + Type: "getter/v1", + } + plgs, err := plugin.FindPlugins([]string{settings.PluginsDirectory}, d) + if err != nil { + return nil, err + } + env := plugin.FormatEnv(settings.EnvVars()) + pluginConstructorBuilder := func(plg plugin.Plugin) Constructor { + return func(option ...Option) (Getter, error) { + + return &getterPlugin{ + options: append([]Option{}, option...), + plg: plg, + env: env, + }, nil + } + } + results := make([]Provider, 0, len(plgs)) + for _, plg := range plgs { + if c, ok := plg.Metadata().Config.(*schema.ConfigGetterV1); ok { + results = append(results, Provider{ + Schemes: c.Protocols, + New: pluginConstructorBuilder(plg), + }) + } + } + return results, nil +} + +func convertOptions(globalOptions, options []Option) schema.GetterOptionsV1 { + opts := getterOptions{} + for _, opt := range globalOptions { + opt(&opts) + } + for _, opt := range options { + opt(&opts) + } + + result := schema.GetterOptionsV1{ + URL: opts.url, + CertFile: opts.certFile, + KeyFile: opts.keyFile, + CAFile: opts.caFile, + UNTar: opts.unTar, + InsecureSkipVerifyTLS: opts.insecureSkipVerifyTLS, + PlainHTTP: opts.plainHTTP, + AcceptHeader: opts.acceptHeader, + Username: opts.username, + Password: opts.password, + PassCredentialsAll: opts.passCredentialsAll, + UserAgent: opts.userAgent, + Version: opts.version, + Timeout: opts.timeout, + } + + return result +} + +type getterPlugin struct { + options []Option + plg plugin.Plugin + env []string +} + +func (g *getterPlugin) Get(href string, options ...Option) (*bytes.Buffer, error) { + opts := convertOptions(g.options, options) + + // TODO optimization: pass this along to Get() instead of re-parsing here + u, err := url.Parse(href) + if err != nil { + return nil, err + } + + input := &plugin.Input{ + Message: schema.InputMessageGetterV1{ + Href: href, + Options: opts, + Protocol: u.Scheme, + }, + Env: g.env, + // TODO should we pass Stdin, Stdout, and Stderr through Input here to getter plugins? + // Stdout: os.Stdout, + } + output, err := g.plg.Invoke(context.Background(), input) + if err != nil { + return nil, fmt.Errorf("plugin %q failed to invoke: %w", g.plg, err) + } + + outputMessage, ok := output.Message.(schema.OutputMessageGetterV1) + if !ok { + return nil, fmt.Errorf("invalid output message type from plugin %q", g.plg.Metadata().Name) + } + + return bytes.NewBuffer(outputMessage.Data), nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/helmpath/home.go b/vendor/helm.sh/helm/v4/pkg/helmpath/home.go new file mode 100644 index 00000000000..bd43e8890c8 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/helmpath/home.go @@ -0,0 +1,44 @@ +// Copyright The Helm Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package helmpath calculates filesystem paths to Helm's configuration, cache and data. +package helmpath + +// This helper builds paths to Helm's configuration, cache and data paths. +const lp = lazypath("helm") + +// ConfigPath returns the path where Helm stores configuration. +func ConfigPath(elem ...string) string { return lp.configPath(elem...) } + +// CachePath returns the path where Helm stores cached objects. +func CachePath(elem ...string) string { return lp.cachePath(elem...) } + +// DataPath returns the path where Helm stores data. +func DataPath(elem ...string) string { return lp.dataPath(elem...) } + +// CacheIndexFile returns the path to an index for the given named repository. +func CacheIndexFile(name string) string { + if name != "" { + name += "-" + } + return name + "index.yaml" +} + +// CacheChartsFile returns the path to a text file listing all the charts +// within the given named repository. +func CacheChartsFile(name string) string { + if name != "" { + name += "-" + } + return name + "charts.txt" +} diff --git a/vendor/helm.sh/helm/v4/pkg/helmpath/lazypath.go b/vendor/helm.sh/helm/v4/pkg/helmpath/lazypath.go new file mode 100644 index 00000000000..c1f86875472 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/helmpath/lazypath.go @@ -0,0 +1,72 @@ +// Copyright The Helm Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package helmpath + +import ( + "os" + "path/filepath" + + "helm.sh/helm/v4/pkg/helmpath/xdg" +) + +const ( + // CacheHomeEnvVar is the environment variable used by Helm + // for the cache directory. When no value is set a default is used. + CacheHomeEnvVar = "HELM_CACHE_HOME" + + // ConfigHomeEnvVar is the environment variable used by Helm + // for the config directory. When no value is set a default is used. + ConfigHomeEnvVar = "HELM_CONFIG_HOME" + + // DataHomeEnvVar is the environment variable used by Helm + // for the data directory. When no value is set a default is used. + DataHomeEnvVar = "HELM_DATA_HOME" +) + +// lazypath is a lazy-loaded path buffer for the XDG base directory specification. +type lazypath string + +func (l lazypath) path(helmEnvVar, xdgEnvVar string, defaultFn func() string, elem ...string) string { + + // There is an order to checking for a path. + // 1. See if a Helm specific environment variable has been set. + // 2. Check if an XDG environment variable is set + // 3. Fall back to a default + base := os.Getenv(helmEnvVar) + if base != "" { + return filepath.Join(base, filepath.Join(elem...)) + } + base = os.Getenv(xdgEnvVar) + if base == "" { + base = defaultFn() + } + return filepath.Join(base, string(l), filepath.Join(elem...)) +} + +// cachePath defines the base directory relative to which user specific non-essential data files +// should be stored. +func (l lazypath) cachePath(elem ...string) string { + return l.path(CacheHomeEnvVar, xdg.CacheHomeEnvVar, cacheHome, filepath.Join(elem...)) +} + +// configPath defines the base directory relative to which user specific configuration files should +// be stored. +func (l lazypath) configPath(elem ...string) string { + return l.path(ConfigHomeEnvVar, xdg.ConfigHomeEnvVar, configHome, filepath.Join(elem...)) +} + +// dataPath defines the base directory relative to which user specific data files should be stored. +func (l lazypath) dataPath(elem ...string) string { + return l.path(DataHomeEnvVar, xdg.DataHomeEnvVar, dataHome, filepath.Join(elem...)) +} diff --git a/vendor/helm.sh/helm/v4/pkg/helmpath/lazypath_darwin.go b/vendor/helm.sh/helm/v4/pkg/helmpath/lazypath_darwin.go new file mode 100644 index 00000000000..eba6dde151d --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/helmpath/lazypath_darwin.go @@ -0,0 +1,34 @@ +// Copyright The Helm Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build darwin + +package helmpath + +import ( + "path/filepath" + + "k8s.io/client-go/util/homedir" +) + +func dataHome() string { + return filepath.Join(homedir.HomeDir(), "Library") +} + +func configHome() string { + return filepath.Join(homedir.HomeDir(), "Library", "Preferences") +} + +func cacheHome() string { + return filepath.Join(homedir.HomeDir(), "Library", "Caches") +} diff --git a/vendor/helm.sh/helm/v4/pkg/helmpath/lazypath_unix.go b/vendor/helm.sh/helm/v4/pkg/helmpath/lazypath_unix.go new file mode 100644 index 00000000000..82fb4b6f1fe --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/helmpath/lazypath_unix.go @@ -0,0 +1,45 @@ +// Copyright The Helm Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !windows && !darwin + +package helmpath + +import ( + "path/filepath" + + "k8s.io/client-go/util/homedir" +) + +// dataHome defines the base directory relative to which user specific data files should be stored. +// +// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share is used. +func dataHome() string { + return filepath.Join(homedir.HomeDir(), ".local", "share") +} + +// configHome defines the base directory relative to which user specific configuration files should +// be stored. +// +// If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config is used. +func configHome() string { + return filepath.Join(homedir.HomeDir(), ".config") +} + +// cacheHome defines the base directory relative to which user specific non-essential data files +// should be stored. +// +// If $XDG_CACHE_HOME is either not set or empty, a default equal to $HOME/.cache is used. +func cacheHome() string { + return filepath.Join(homedir.HomeDir(), ".cache") +} diff --git a/vendor/helm.sh/helm/v4/pkg/helmpath/lazypath_windows.go b/vendor/helm.sh/helm/v4/pkg/helmpath/lazypath_windows.go new file mode 100644 index 00000000000..230aee2a933 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/helmpath/lazypath_windows.go @@ -0,0 +1,24 @@ +// Copyright The Helm Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build windows + +package helmpath + +import "os" + +func dataHome() string { return configHome() } + +func configHome() string { return os.Getenv("APPDATA") } + +func cacheHome() string { return os.Getenv("TEMP") } diff --git a/vendor/helm.sh/helm/v4/pkg/helmpath/xdg/xdg.go b/vendor/helm.sh/helm/v4/pkg/helmpath/xdg/xdg.go new file mode 100644 index 00000000000..eaa3e686434 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/helmpath/xdg/xdg.go @@ -0,0 +1,34 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package xdg holds constants pertaining to XDG Base Directory Specification. +// +// The XDG Base Directory Specification https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html +// specifies the environment variables that define user-specific base directories for various categories of files. +package xdg + +const ( + // CacheHomeEnvVar is the environment variable used by the + // XDG base directory specification for the cache directory. + CacheHomeEnvVar = "XDG_CACHE_HOME" + + // ConfigHomeEnvVar is the environment variable used by the + // XDG base directory specification for the config directory. + ConfigHomeEnvVar = "XDG_CONFIG_HOME" + + // DataHomeEnvVar is the environment variable used by the + // XDG base directory specification for the data directory. + DataHomeEnvVar = "XDG_DATA_HOME" +) diff --git a/vendor/helm.sh/helm/v4/pkg/ignore/doc.go b/vendor/helm.sh/helm/v4/pkg/ignore/doc.go new file mode 100644 index 00000000000..a66066eb255 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/ignore/doc.go @@ -0,0 +1,68 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package ignore provides tools for writing ignore files (a la .gitignore). + +This provides both an ignore parser and a file-aware processor. + +The format of ignore files closely follows, but does not exactly match, the +format for .gitignore files (https://git-scm.com/docs/gitignore). + +The formatting rules are as follows: + + - Parsing is line-by-line + - Empty lines are ignored + - Lines that begin with # (comments) will be ignored + - Leading and trailing spaces are always ignored + - Inline comments are NOT supported ('foo* # Any foo' does not contain a comment) + - There is no support for multi-line patterns + - Shell glob patterns are supported. See Go's "path/filepath".Match + - If a pattern begins with a leading !, the match will be negated. + - If a pattern begins with a leading /, only paths relatively rooted will match. + - If the pattern ends with a trailing /, only directories will match + - If a pattern contains no slashes, file basenames are tested (not paths) + - The pattern sequence "**", while legal in a glob, will cause an error here + (to indicate incompatibility with .gitignore). + +Example: + + # Match any file named foo.txt + foo.txt + + # Match any text file + *.txt + + # Match only directories named mydir + mydir/ + + # Match only text files in the top-level directory + /*.txt + + # Match only the file foo.txt in the top-level directory + /foo.txt + + # Match any file named ab.txt, ac.txt, or ad.txt + a[b-d].txt + +Notable differences from .gitignore: + - The '**' syntax is not supported. + - The globbing library is Go's 'filepath.Match', not fnmatch(3) + - Trailing spaces are always ignored (there is no supported escape sequence) + - The evaluation of escape sequences has not been tested for compatibility + - There is no support for '\!' as a special leading sequence. +*/ +package ignore // import "helm.sh/helm/v4/pkg/ignore" diff --git a/vendor/helm.sh/helm/v4/pkg/ignore/rules.go b/vendor/helm.sh/helm/v4/pkg/ignore/rules.go new file mode 100644 index 00000000000..a8160da2a28 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/ignore/rules.go @@ -0,0 +1,231 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ignore + +import ( + "bufio" + "bytes" + "errors" + "io" + "log/slog" + "os" + "path/filepath" + "strings" +) + +// HelmIgnore default name of an ignorefile. +const HelmIgnore = ".helmignore" + +// Rules is a collection of path matching rules. +// +// Parse() and ParseFile() will construct and populate new Rules. +// Empty() will create an immutable empty ruleset. +type Rules struct { + patterns []*pattern +} + +// Empty builds an empty ruleset. +func Empty() *Rules { + return &Rules{patterns: []*pattern{}} +} + +// AddDefaults adds default ignore patterns. +// +// Ignore all dotfiles in "templates/" +func (r *Rules) AddDefaults() { + r.parseRule(`templates/.?*`) +} + +// ParseFile parses a helmignore file and returns the *Rules. +func ParseFile(file string) (*Rules, error) { + f, err := os.Open(file) + if err != nil { + return nil, err + } + defer f.Close() + return Parse(f) +} + +// Parse parses a rules file +func Parse(file io.Reader) (*Rules, error) { + r := &Rules{patterns: []*pattern{}} + + s := bufio.NewScanner(file) + currentLine := 0 + utf8bom := []byte{0xEF, 0xBB, 0xBF} + for s.Scan() { + scannedBytes := s.Bytes() + // We trim UTF8 BOM + if currentLine == 0 { + scannedBytes = bytes.TrimPrefix(scannedBytes, utf8bom) + } + line := string(scannedBytes) + currentLine++ + + if err := r.parseRule(line); err != nil { + return r, err + } + } + return r, s.Err() +} + +// Ignore evaluates the file at the given path, and returns true if it should be ignored. +// +// Ignore evaluates path against the rules in order. Evaluation stops when a match +// is found. Matching a negative rule will stop evaluation. +func (r *Rules) Ignore(path string, fi os.FileInfo) bool { + // Don't match on empty dirs. + if path == "" { + return false + } + + // Disallow ignoring the current working directory. + // See issue: + // 1776 (New York City) Hamilton: "Pardon me, are you Aaron Burr, sir?" + if path == "." || path == "./" { + return false + } + for _, p := range r.patterns { + if p.match == nil { + slog.Info("this will be ignored no matcher supplied", "patterns", p.raw) + return false + } + + // For negative rules, we need to capture and return non-matches, + // and continue for matches. + if p.negate { + if p.mustDir && !fi.IsDir() { + return true + } + if !p.match(path, fi) { + return true + } + continue + } + + // If the rule is looking for directories, and this is not a directory, + // skip it. + if p.mustDir && !fi.IsDir() { + continue + } + if p.match(path, fi) { + return true + } + } + return false +} + +// parseRule parses a rule string and creates a pattern, which is then stored in the Rules object. +func (r *Rules) parseRule(rule string) error { + rule = strings.TrimSpace(rule) + + // Ignore blank lines + if rule == "" { + return nil + } + // Comment + if strings.HasPrefix(rule, "#") { + return nil + } + + // Fail any rules that contain ** + if strings.Contains(rule, "**") { + return errors.New("double-star (**) syntax is not supported") + } + + // Fail any patterns that can't compile. A non-empty string must be + // given to Match() to avoid optimization that skips rule evaluation. + if _, err := filepath.Match(rule, "abc"); err != nil { + return err + } + + p := &pattern{raw: rule} + + // Negation is handled at a higher level, so strip the leading ! from the + // string. + if strings.HasPrefix(rule, "!") { + p.negate = true + rule = rule[1:] + } + + // Directory verification is handled by a higher level, so the trailing / + // is removed from the rule. That way, a directory named "foo" matches, + // even if the supplied string does not contain a literal slash character. + if strings.HasSuffix(rule, "/") { + p.mustDir = true + rule = strings.TrimSuffix(rule, "/") + } + + if after, ok := strings.CutPrefix(rule, "/"); ok { + // Require path matches the root path. + p.match = func(n string, _ os.FileInfo) bool { + rule = after + ok, err := filepath.Match(rule, n) + if err != nil { + slog.Error("failed to compile", slog.String("rule", rule), slog.Any("error", err)) + return false + } + return ok + } + } else if strings.Contains(rule, "/") { + // require structural match. + p.match = func(n string, _ os.FileInfo) bool { + ok, err := filepath.Match(rule, n) + if err != nil { + slog.Error( + "failed to compile", + slog.String("rule", rule), + slog.Any("error", err), + ) + return false + } + return ok + } + } else { + p.match = func(n string, _ os.FileInfo) bool { + // When there is no slash in the pattern, we evaluate ONLY the + // filename. + n = filepath.Base(n) + ok, err := filepath.Match(rule, n) + if err != nil { + slog.Error("failed to compile", slog.String("rule", rule), slog.Any("error", err)) + return false + } + return ok + } + } + + r.patterns = append(r.patterns, p) + return nil +} + +// matcher is a function capable of computing a match. +// +// It returns true if the rule matches. +type matcher func(name string, fi os.FileInfo) bool + +// pattern describes a pattern to be matched in a rule set. +type pattern struct { + // raw is the unparsed string, with nothing stripped. + raw string + // match is the matcher function. + match matcher + // negate indicates that the rule's outcome should be negated. + negate bool + // mustDir indicates that the matched file must be a directory. + mustDir bool +} diff --git a/vendor/helm.sh/helm/v4/pkg/kube/client.go b/vendor/helm.sh/helm/v4/pkg/kube/client.go new file mode 100644 index 00000000000..b78dc8d6d20 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/kube/client.go @@ -0,0 +1,1297 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kube // import "helm.sh/helm/v4/pkg/kube" + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "log/slog" + "net/http" + "os" + "path/filepath" + "reflect" + "strings" + "sync" + + jsonpatch "github.com/evanphx/json-patch/v5" + v1 "k8s.io/api/core/v1" + apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" + + "helm.sh/helm/v4/internal/logging" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/jsonmergepatch" + "k8s.io/apimachinery/pkg/util/mergepatch" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/resource" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/util/csaupgrade" + "k8s.io/client-go/util/retry" + cmdutil "k8s.io/kubectl/pkg/cmd/util" +) + +// ErrNoObjectsVisited indicates that during a visit operation, no matching objects were found. +var ErrNoObjectsVisited = errors.New("no objects visited") + +var metadataAccessor = meta.NewAccessor() + +// ManagedFieldsManager is the name of the manager of Kubernetes managedFields +// first introduced in Kubernetes 1.18 +var ManagedFieldsManager string + +// Client represents a client capable of communicating with the Kubernetes API. +type Client struct { + // Factory provides a minimal version of the kubectl Factory interface. If + // you need the full Factory you can type switch to the full interface. + // Since Kubernetes Go API does not provide backwards compatibility across + // minor versions, this API does not follow Helm backwards compatibility. + // Helm is exposing Kubernetes in this property and cannot guarantee this + // will not change. The minimal interface only has the functions that Helm + // needs. The smaller surface area of the interface means there is a lower + // chance of it changing. + Factory Factory + // Namespace allows to bypass the kubeconfig file for the choice of the namespace + Namespace string + + // WaitContext is an optional context to use for wait operations. + // If not set, a context will be created internally using the + // timeout provided to the wait functions. + // + // Deprecated: Use WithWaitContext wait option when getting a Waiter instead. + WaitContext context.Context + + Waiter + kubeClient kubernetes.Interface + + // Embed a LogHolder to provide logger functionality + logging.LogHolder +} + +var _ Interface = (*Client)(nil) + +// WaitStrategy represents the algorithm used to wait for Kubernetes +// resources to reach their desired state. +type WaitStrategy string + +const ( + // StatusWatcherStrategy: event-driven waits using kstatus (watches + aggregated readers). + // Default for --wait. More accurate and responsive; waits CRs and full reconciliation. + // Requires: reachable API server, list+watch RBAC on deployed resources, and a non-zero timeout. + StatusWatcherStrategy WaitStrategy = "watcher" + + // LegacyStrategy: Helm 3-style periodic polling until ready or timeout. + // Use when watches aren’t available/reliable, or for compatibility/simple CI. + // Requires only list RBAC for polled resources. + LegacyStrategy WaitStrategy = "legacy" + + // HookOnlyStrategy: wait only for hook Pods/Jobs to complete; does not wait for general chart resources. + HookOnlyStrategy WaitStrategy = "hookOnly" +) + +type FieldValidationDirective string + +const ( + FieldValidationDirectiveIgnore FieldValidationDirective = "Ignore" + FieldValidationDirectiveWarn FieldValidationDirective = "Warn" + FieldValidationDirectiveStrict FieldValidationDirective = "Strict" +) + +type CreateApplyFunc func(target *resource.Info) error +type UpdateApplyFunc func(original, target *resource.Info) error + +func init() { + // Add CRDs to the scheme. They are missing by default. + if err := apiextv1.AddToScheme(scheme.Scheme); err != nil { + // This should never happen. + panic(err) + } + if err := apiextv1beta1.AddToScheme(scheme.Scheme); err != nil { + panic(err) + } +} + +func (c *Client) newStatusWatcher(opts ...WaitOption) (*statusWaiter, error) { + var o waitOptions + for _, opt := range opts { + opt(&o) + } + cfg, err := c.Factory.ToRESTConfig() + if err != nil { + return nil, err + } + dynamicClient, err := c.Factory.DynamicClient() + if err != nil { + return nil, err + } + httpClient, err := rest.HTTPClientFor(cfg) + if err != nil { + return nil, err + } + restMapper, err := apiutil.NewDynamicRESTMapper(cfg, httpClient) + if err != nil { + return nil, err + } + waitContext := o.ctx + if waitContext == nil { + waitContext = c.WaitContext + } + sw := &statusWaiter{ + restMapper: restMapper, + client: dynamicClient, + ctx: waitContext, + watchUntilReadyCtx: o.watchUntilReadyCtx, + waitCtx: o.waitCtx, + waitWithJobsCtx: o.waitWithJobsCtx, + waitForDeleteCtx: o.waitForDeleteCtx, + readers: o.statusReaders, + } + sw.SetLogger(c.Logger().Handler()) + return sw, nil +} + +func (c *Client) GetWaiter(ws WaitStrategy) (Waiter, error) { + return c.GetWaiterWithOptions(ws) +} + +func (c *Client) GetWaiterWithOptions(strategy WaitStrategy, opts ...WaitOption) (Waiter, error) { + switch strategy { + case LegacyStrategy: + kc, err := c.Factory.KubernetesClientSet() + if err != nil { + return nil, err + } + return &legacyWaiter{kubeClient: kc, ctx: c.WaitContext}, nil + case StatusWatcherStrategy: + return c.newStatusWatcher(opts...) + case HookOnlyStrategy: + sw, err := c.newStatusWatcher(opts...) + if err != nil { + return nil, err + } + return &hookOnlyWaiter{sw: sw}, nil + case "": + return nil, errors.New("wait strategy not set. Choose one of: " + string(StatusWatcherStrategy) + ", " + string(HookOnlyStrategy) + ", " + string(LegacyStrategy)) + default: + return nil, errors.New("unknown wait strategy (s" + string(strategy) + "). Valid values are: " + string(StatusWatcherStrategy) + ", " + string(HookOnlyStrategy) + ", " + string(LegacyStrategy)) + } +} + +func (c *Client) SetWaiter(ws WaitStrategy) error { + return c.SetWaiterWithOptions(ws) +} + +func (c *Client) SetWaiterWithOptions(ws WaitStrategy, opts ...WaitOption) error { + var err error + c.Waiter, err = c.GetWaiterWithOptions(ws, opts...) + if err != nil { + return err + } + return nil +} + +// New creates a new Client. +func New(getter genericclioptions.RESTClientGetter) *Client { + if getter == nil { + getter = genericclioptions.NewConfigFlags(true) + } + factory := cmdutil.NewFactory(getter) + c := &Client{ + Factory: factory, + } + c.SetLogger(slog.Default().Handler()) + return c +} + +// getKubeClient get or create a new KubernetesClientSet +func (c *Client) getKubeClient() (kubernetes.Interface, error) { + var err error + if c.kubeClient == nil { + c.kubeClient, err = c.Factory.KubernetesClientSet() + } + + return c.kubeClient, err +} + +// IsReachable tests connectivity to the cluster. +func (c *Client) IsReachable() error { + client, err := c.getKubeClient() + if err == genericclioptions.ErrEmptyConfig { + // re-replace kubernetes ErrEmptyConfig error with a friendly error + // moar workarounds for Kubernetes API breaking. + return errors.New("kubernetes cluster unreachable") + } + if err != nil { + return fmt.Errorf("kubernetes cluster unreachable: %w", err) + } + if _, err := client.Discovery().ServerVersion(); err != nil { + return fmt.Errorf("kubernetes cluster unreachable: %w", err) + } + return nil +} + +type clientCreateOptions struct { + serverSideApply bool + forceConflicts bool + dryRun bool + fieldValidationDirective FieldValidationDirective +} + +type ClientCreateOption func(*clientCreateOptions) error + +// ClientCreateOptionServerSideApply enables performing object apply server-side +// see: https://kubernetes.io/docs/reference/using-api/server-side-apply/ +// +// `forceConflicts` forces conflicts to be resolved (may be when serverSideApply enabled only) +// see: https://kubernetes.io/docs/reference/using-api/server-side-apply/#conflicts +func ClientCreateOptionServerSideApply(serverSideApply, forceConflicts bool) ClientCreateOption { + return func(o *clientCreateOptions) error { + if !serverSideApply && forceConflicts { + return fmt.Errorf("forceConflicts enabled when serverSideApply disabled") + } + + o.serverSideApply = serverSideApply + o.forceConflicts = forceConflicts + + return nil + } +} + +// ClientCreateOptionDryRun requests the server to perform non-mutating operations only +func ClientCreateOptionDryRun(dryRun bool) ClientCreateOption { + return func(o *clientCreateOptions) error { + o.dryRun = dryRun + + return nil + } +} + +// ClientCreateOptionFieldValidationDirective specifies how API operations validate object's schema +// - For client-side apply: this is ignored +// - For server-side apply: the directive is sent to the server to perform the validation +// +// Defaults to `FieldValidationDirectiveStrict` +func ClientCreateOptionFieldValidationDirective(fieldValidationDirective FieldValidationDirective) ClientCreateOption { + return func(o *clientCreateOptions) error { + o.fieldValidationDirective = fieldValidationDirective + + return nil + } +} + +func (c *Client) makeCreateApplyFunc(serverSideApply, forceConflicts, dryRun bool, fieldValidationDirective FieldValidationDirective) CreateApplyFunc { + if serverSideApply { + c.Logger().Debug( + "using server-side apply for resource creation", + slog.Bool("forceConflicts", forceConflicts), + slog.Bool("dryRun", dryRun), + slog.String("fieldValidationDirective", string(fieldValidationDirective))) + + return func(target *resource.Info) error { + err := patchResourceServerSide(target, dryRun, forceConflicts, fieldValidationDirective) + + logger := c.Logger().With( + slog.String("namespace", target.Namespace), + slog.String("name", target.Name), + slog.String("gvk", target.Mapping.GroupVersionKind.String())) + if err != nil { + logger.Debug("Error creating resource via patch", slog.Any("error", err)) + return err + } + + logger.Debug("Created resource via patch") + + return nil + } + } + + c.Logger().Debug("using client-side apply for resource creation") + return createResource +} + +// Create creates Kubernetes resources specified in the resource list. +func (c *Client) Create(resources ResourceList, options ...ClientCreateOption) (*Result, error) { + c.Logger().Debug("creating resource(s)", "resources", len(resources)) + + createOptions := clientCreateOptions{ + serverSideApply: true, // Default to server-side apply + fieldValidationDirective: FieldValidationDirectiveStrict, + } + + errs := make([]error, 0, len(options)) + for _, o := range options { + errs = append(errs, o(&createOptions)) + } + if err := errors.Join(errs...); err != nil { + return nil, fmt.Errorf("invalid client create option(s): %w", err) + } + + createApplyFunc := c.makeCreateApplyFunc( + createOptions.serverSideApply, + createOptions.forceConflicts, + createOptions.dryRun, + createOptions.fieldValidationDirective) + if err := perform(resources, createApplyFunc); err != nil { + return nil, err + } + return &Result{Created: resources}, nil +} + +func transformRequests(req *rest.Request) { + tableParam := strings.Join([]string{ + fmt.Sprintf("application/json;as=Table;v=%s;g=%s", metav1.SchemeGroupVersion.Version, metav1.GroupName), + fmt.Sprintf("application/json;as=Table;v=%s;g=%s", metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName), + "application/json", + }, ",") + req.SetHeader("Accept", tableParam) + + // if sorting, ensure we receive the full object in order to introspect its fields via jsonpath + req.Param("includeObject", "Object") +} + +// Get retrieves the resource objects supplied. If related is set to true the +// related pods are fetched as well. If the passed in resources are a table kind +// the related resources will also be fetched as kind=table. +func (c *Client) Get(resources ResourceList, related bool) (map[string][]runtime.Object, error) { + buf := new(bytes.Buffer) + objs := make(map[string][]runtime.Object) + + podSelectors := []map[string]string{} + err := resources.Visit(func(info *resource.Info, err error) error { + if err != nil { + return err + } + + gvk := info.ResourceMapping().GroupVersionKind + vk := gvk.Version + "/" + gvk.Kind + obj, err := getResource(info) + if err != nil { + fmt.Fprintf(buf, "Get resource %s failed, err:%v\n", info.Name, err) + } else { + objs[vk] = append(objs[vk], obj) + + // Only fetch related pods if they are requested + if related { + // Discover if the existing object is a table. If it is, request + // the pods as Tables. Otherwise request them normally. + objGVK := obj.GetObjectKind().GroupVersionKind() + var isTable bool + if objGVK.Kind == "Table" { + isTable = true + } + + objs, err = c.getSelectRelationPod(info, objs, isTable, &podSelectors) + if err != nil { + c.Logger().Warn("get the relation pod is failed", slog.Any("error", err)) + } + } + } + + return nil + }) + if err != nil { + return nil, err + } + + return objs, nil +} + +func (c *Client) getSelectRelationPod(info *resource.Info, objs map[string][]runtime.Object, table bool, podSelectors *[]map[string]string) (map[string][]runtime.Object, error) { + if info == nil { + return objs, nil + } + c.Logger().Debug("get relation pod of object", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind) + selector, ok, _ := getSelectorFromObject(info.Object) + if !ok { + return objs, nil + } + + for index := range *podSelectors { + if reflect.DeepEqual((*podSelectors)[index], selector) { + // check if pods for selectors are already added. This avoids duplicate printing of pods + return objs, nil + } + } + + *podSelectors = append(*podSelectors, selector) + + var infos []*resource.Info + var err error + if table { + infos, err = c.Factory.NewBuilder(). + Unstructured(). + ContinueOnError(). + NamespaceParam(info.Namespace). + DefaultNamespace(). + ResourceTypes("pods"). + LabelSelector(labels.Set(selector).AsSelector().String()). + TransformRequests(transformRequests). + Do().Infos() + if err != nil { + return objs, err + } + } else { + infos, err = c.Factory.NewBuilder(). + Unstructured(). + ContinueOnError(). + NamespaceParam(info.Namespace). + DefaultNamespace(). + ResourceTypes("pods"). + LabelSelector(labels.Set(selector).AsSelector().String()). + Do().Infos() + if err != nil { + return objs, err + } + } + vk := "v1/Pod(related)" + + for _, info := range infos { + objs[vk] = append(objs[vk], info.Object) + } + return objs, nil +} + +func getSelectorFromObject(obj runtime.Object) (map[string]string, bool, error) { + typed := obj.(*unstructured.Unstructured) + kind := typed.Object["kind"] + switch kind { + case "ReplicaSet", "Deployment", "StatefulSet", "DaemonSet", "Job": + return unstructured.NestedStringMap(typed.Object, "spec", "selector", "matchLabels") + case "ReplicationController": + return unstructured.NestedStringMap(typed.Object, "spec", "selector") + default: + return nil, false, nil + } +} + +func getResource(info *resource.Info) (runtime.Object, error) { + obj, err := resource.NewHelper(info.Client, info.Mapping).Get(info.Namespace, info.Name) + if err != nil { + return nil, err + } + return obj, nil +} + +func (c *Client) namespace() string { + if c.Namespace != "" { + return c.Namespace + } + if ns, _, err := c.Factory.ToRawKubeConfigLoader().Namespace(); err == nil { + return ns + } + return v1.NamespaceDefault +} + +func determineFieldValidationDirective(validate bool) FieldValidationDirective { + if validate { + return FieldValidationDirectiveStrict + } + + return FieldValidationDirectiveIgnore +} + +func buildResourceList(f Factory, namespace string, validationDirective FieldValidationDirective, reader io.Reader, transformRequest resource.RequestTransform) (ResourceList, error) { + + schema, err := f.Validator(string(validationDirective)) + if err != nil { + return nil, err + } + + builder := f.NewBuilder(). + ContinueOnError(). + NamespaceParam(namespace). + DefaultNamespace(). + Flatten(). + Unstructured(). + Schema(schema). + Stream(reader, "") + if transformRequest != nil { + builder.TransformRequests(transformRequest) + } + result, err := builder.Do().Infos() + return result, scrubValidationError(err) +} + +// Build validates for Kubernetes objects and returns unstructured infos. +func (c *Client) Build(reader io.Reader, validate bool) (ResourceList, error) { + return buildResourceList( + c.Factory, + c.namespace(), + determineFieldValidationDirective(validate), + reader, + nil) +} + +// BuildTable validates for Kubernetes objects and returns unstructured infos. +// The returned kind is a Table. +func (c *Client) BuildTable(reader io.Reader, validate bool) (ResourceList, error) { + return buildResourceList( + c.Factory, + c.namespace(), + determineFieldValidationDirective(validate), + reader, + transformRequests) +} + +func (c *Client) update(originals, targets ResourceList, createApplyFunc CreateApplyFunc, updateApplyFunc UpdateApplyFunc) (*Result, error) { + updateErrors := []error{} + res := &Result{} + + c.Logger().Debug("checking resources for changes", "resources", len(targets)) + err := targets.Visit(func(target *resource.Info, err error) error { + if err != nil { + return err + } + + helper := resource.NewHelper(target.Client, target.Mapping).WithFieldManager(getManagedFieldsManager()) + if _, err := helper.Get(target.Namespace, target.Name); err != nil { + if !apierrors.IsNotFound(err) { + return fmt.Errorf("could not get information about the resource: %w", err) + } + + // Append the created resource to the results, even if something fails + res.Created = append(res.Created, target) + + // Since the resource does not exist, create it. + if err := createApplyFunc(target); err != nil { + return fmt.Errorf("failed to create resource: %w", err) + } + + kind := target.Mapping.GroupVersionKind.Kind + c.Logger().Debug( + "created a new resource", + slog.String("namespace", target.Namespace), + slog.String("name", target.Name), + slog.String("kind", kind), + ) + return nil + } + + original := originals.Get(target) + if original == nil { + kind := target.Mapping.GroupVersionKind.Kind + return fmt.Errorf("original object %s with the name %q not found", kind, target.Name) + } + + if err := updateApplyFunc(original, target); err != nil { + updateErrors = append(updateErrors, err) + } + + // Because we check for errors later, append the info regardless + res.Updated = append(res.Updated, target) + + return nil + }) + + switch { + case err != nil: + return res, err + case len(updateErrors) != 0: + return res, joinErrors(updateErrors, " && ") + } + + for _, info := range originals.Difference(targets) { + c.Logger().Debug("deleting resource", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind) + + if err := info.Get(); err != nil { + c.Logger().Debug( + "unable to get object", + slog.String("namespace", info.Namespace), + slog.String("name", info.Name), + slog.String("kind", info.Mapping.GroupVersionKind.Kind), + slog.Any("error", err), + ) + continue + } + annotations, err := metadataAccessor.Annotations(info.Object) + if err != nil { + c.Logger().Debug( + "unable to get annotations", + slog.String("namespace", info.Namespace), + slog.String("name", info.Name), + slog.String("kind", info.Mapping.GroupVersionKind.Kind), + slog.Any("error", err), + ) + } + if annotations != nil && annotations[ResourcePolicyAnno] == KeepPolicy { + c.Logger().Debug("skipping delete due to annotation", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, "annotation", ResourcePolicyAnno, "value", KeepPolicy) + continue + } + if err := deleteResource(info, metav1.DeletePropagationBackground); err != nil { + c.Logger().Debug( + "failed to delete resource", + slog.String("namespace", info.Namespace), + slog.String("name", info.Name), + slog.String("kind", info.Mapping.GroupVersionKind.Kind), + slog.Any("error", err), + ) + if !apierrors.IsNotFound(err) { + updateErrors = append(updateErrors, fmt.Errorf("failed to delete resource %s: %w", info.Name, err)) + } + continue + } + res.Deleted = append(res.Deleted, info) + } + + if len(updateErrors) != 0 { + return res, joinErrors(updateErrors, " && ") + } + return res, nil +} + +type clientUpdateOptions struct { + threeWayMergeForUnstructured bool + serverSideApply bool + forceReplace bool + forceConflicts bool + dryRun bool + fieldValidationDirective FieldValidationDirective + upgradeClientSideFieldManager bool +} + +type ClientUpdateOption func(*clientUpdateOptions) error + +// ClientUpdateOptionThreeWayMergeForUnstructured enables performing three-way merge for unstructured objects +// Must not be enabled when ClientUpdateOptionServerSideApply is enabled +func ClientUpdateOptionThreeWayMergeForUnstructured(threeWayMergeForUnstructured bool) ClientUpdateOption { + return func(o *clientUpdateOptions) error { + o.threeWayMergeForUnstructured = threeWayMergeForUnstructured + + return nil + } +} + +// ClientUpdateOptionServerSideApply enables performing object apply server-side (default) +// see: https://kubernetes.io/docs/reference/using-api/server-side-apply/ +// Must not be enabled when ClientUpdateOptionThreeWayMerge is enabled +// +// `forceConflicts` forces conflicts to be resolved (may be enabled when serverSideApply enabled only) +// see: https://kubernetes.io/docs/reference/using-api/server-side-apply/#conflicts +func ClientUpdateOptionServerSideApply(serverSideApply, forceConflicts bool) ClientUpdateOption { + return func(o *clientUpdateOptions) error { + if !serverSideApply && forceConflicts { + return fmt.Errorf("forceConflicts enabled when serverSideApply disabled") + } + + o.serverSideApply = serverSideApply + o.forceConflicts = forceConflicts + + return nil + } +} + +// ClientUpdateOptionForceReplace forces objects to be replaced rather than updated via patch +// Must not be enabled when ClientUpdateOptionForceConflicts is enabled +func ClientUpdateOptionForceReplace(forceReplace bool) ClientUpdateOption { + return func(o *clientUpdateOptions) error { + o.forceReplace = forceReplace + + return nil + } +} + +// ClientUpdateOptionDryRun requests the server to perform non-mutating operations only +func ClientUpdateOptionDryRun(dryRun bool) ClientUpdateOption { + return func(o *clientUpdateOptions) error { + o.dryRun = dryRun + + return nil + } +} + +// ClientUpdateOptionFieldValidationDirective specifies how API operations validate object's schema +// - For client-side apply: this is ignored +// - For server-side apply: the directive is sent to the server to perform the validation +// +// Defaults to `FieldValidationDirectiveStrict` +func ClientUpdateOptionFieldValidationDirective(fieldValidationDirective FieldValidationDirective) ClientUpdateOption { + return func(o *clientUpdateOptions) error { + o.fieldValidationDirective = fieldValidationDirective + + return nil + } +} + +// ClientUpdateOptionUpgradeClientSideFieldManager specifies that resources client-side field manager should be upgraded to server-side apply +// (before applying the object server-side) +// This is required when upgrading a chart from client-side to server-side apply, otherwise the client-side field management remains. Conflicting with server-side applied updates. +// +// Note: +// if this option is specified, but the object is not managed by client-side field manager, it will be a no-op. However, the cost of fetching the objects will be incurred. +// +// see: +// - https://github.com/kubernetes/kubernetes/pull/112905 +// - `UpgradeManagedFields` / https://github.com/kubernetes/kubernetes/blob/f47e9696d7237f1011d23c9b55f6947e60526179/staging/src/k8s.io/client-go/util/csaupgrade/upgrade.go#L81 +func ClientUpdateOptionUpgradeClientSideFieldManager(upgradeClientSideFieldManager bool) ClientUpdateOption { + return func(o *clientUpdateOptions) error { + o.upgradeClientSideFieldManager = upgradeClientSideFieldManager + + return nil + } +} + +// Update takes the current list of objects and target list of objects and +// creates resources that don't already exist, updates resources that have been +// modified in the target configuration, and deletes resources from the current +// configuration that are not present in the target configuration. If an error +// occurs, a Result will still be returned with the error, containing all +// resource updates, creations, and deletions that were attempted. These can be +// used for cleanup or other logging purposes. +// +// The default is to use server-side apply, equivalent to: `ClientUpdateOptionServerSideApply(true)` +func (c *Client) Update(originals, targets ResourceList, options ...ClientUpdateOption) (*Result, error) { + updateOptions := clientUpdateOptions{ + serverSideApply: true, // Default to server-side apply + fieldValidationDirective: FieldValidationDirectiveStrict, + } + + errs := make([]error, 0, len(options)) + for _, o := range options { + errs = append(errs, o(&updateOptions)) + } + if err := errors.Join(errs...); err != nil { + return &Result{}, fmt.Errorf("invalid client update option(s): %w", err) + } + + if updateOptions.threeWayMergeForUnstructured && updateOptions.serverSideApply { + return &Result{}, fmt.Errorf("invalid operation: cannot use three-way merge for unstructured and server-side apply together") + } + + if updateOptions.forceConflicts && updateOptions.forceReplace { + return &Result{}, fmt.Errorf("invalid operation: cannot use force conflicts and force replace together") + } + + if updateOptions.serverSideApply && updateOptions.forceReplace { + return &Result{}, fmt.Errorf("invalid operation: cannot use server-side apply and force replace together") + } + + createApplyFunc := c.makeCreateApplyFunc( + updateOptions.serverSideApply, + updateOptions.forceConflicts, + updateOptions.dryRun, + updateOptions.fieldValidationDirective) + + makeUpdateApplyFunc := func() UpdateApplyFunc { + if updateOptions.forceReplace { + c.Logger().Debug( + "using resource replace update strategy", + slog.String("fieldValidationDirective", string(updateOptions.fieldValidationDirective))) + return func(original, target *resource.Info) error { + if err := replaceResource(target, updateOptions.fieldValidationDirective); err != nil { + c.Logger().With( + slog.String("namespace", target.Namespace), + slog.String("name", target.Name), + slog.String("gvk", target.Mapping.GroupVersionKind.String()), + ).Debug( + "error replacing the resource", slog.Any("error", err), + ) + return err + } + + originalObject := original.Object + kind := target.Mapping.GroupVersionKind.Kind + c.Logger().Debug("replace succeeded", "name", original.Name, "initialKind", originalObject.GetObjectKind().GroupVersionKind().Kind, "kind", kind) + + return nil + } + } else if updateOptions.serverSideApply { + c.Logger().Debug( + "using server-side apply for resource update", + slog.Bool("forceConflicts", updateOptions.forceConflicts), + slog.Bool("dryRun", updateOptions.dryRun), + slog.String("fieldValidationDirective", string(updateOptions.fieldValidationDirective)), + slog.Bool("upgradeClientSideFieldManager", updateOptions.upgradeClientSideFieldManager)) + return func(original, target *resource.Info) error { + + logger := c.Logger().With( + slog.String("namespace", target.Namespace), + slog.String("name", target.Name), + slog.String("gvk", target.Mapping.GroupVersionKind.String())) + + if updateOptions.upgradeClientSideFieldManager { + patched, err := upgradeClientSideFieldManager(original, updateOptions.dryRun, updateOptions.fieldValidationDirective) + if err != nil { + c.Logger().Debug("Error patching resource to replace CSA field management", slog.Any("error", err)) + return err + } + + if patched { + logger.Debug("Upgraded object client-side field management with server-side apply field management") + } + } + + if err := patchResourceServerSide(target, updateOptions.dryRun, updateOptions.forceConflicts, updateOptions.fieldValidationDirective); err != nil { + logger.Debug("Error patching resource", slog.Any("error", err)) + return err + } + + logger.Debug("Patched resource") + + return nil + } + } + + c.Logger().Debug("using client-side apply for resource update", slog.Bool("threeWayMergeForUnstructured", updateOptions.threeWayMergeForUnstructured)) + return func(original, target *resource.Info) error { + return patchResourceClientSide(original.Object, target, updateOptions.threeWayMergeForUnstructured) + } + } + + return c.update(originals, targets, createApplyFunc, makeUpdateApplyFunc()) +} + +// Delete deletes Kubernetes resources specified in the resources list with +// given deletion propagation policy. It will attempt to delete all resources even +// if one or more fail and collect any errors. All successfully deleted items +// will be returned in the `Deleted` ResourceList that is part of the result. +func (c *Client) Delete(resources ResourceList, policy metav1.DeletionPropagation) (*Result, []error) { + var errs []error + res := &Result{} + mtx := sync.Mutex{} + err := perform(resources, func(target *resource.Info) error { + c.Logger().Debug("starting delete resource", "namespace", target.Namespace, "name", target.Name, "kind", target.Mapping.GroupVersionKind.Kind) + err := deleteResource(target, policy) + if err == nil || apierrors.IsNotFound(err) { + if err != nil { + c.Logger().Debug( + "ignoring delete failure", + slog.String("namespace", target.Namespace), + slog.String("name", target.Name), + slog.String("kind", target.Mapping.GroupVersionKind.Kind), + slog.Any("error", err)) + } + mtx.Lock() + defer mtx.Unlock() + res.Deleted = append(res.Deleted, target) + return nil + } + mtx.Lock() + defer mtx.Unlock() + // Collect the error and continue on + errs = append(errs, err) + return nil + }) + if err != nil { + if errors.Is(err, ErrNoObjectsVisited) { + err = fmt.Errorf("object not found, skipping delete: %w", err) + } + errs = append(errs, err) + } + if errs != nil { + return nil, errs + } + return res, nil +} + +// https://github.com/kubernetes/kubectl/blob/197123726db24c61aa0f78d1f0ba6e91a2ec2f35/pkg/cmd/apply/apply.go#L439 +func isIncompatibleServerError(err error) bool { + // 415: Unsupported media type means we're talking to a server which doesn't + // support server-side apply. + if _, ok := err.(*apierrors.StatusError); !ok { + // Non-StatusError means the error isn't because the server is incompatible. + return false + } + return err.(*apierrors.StatusError).Status().Code == http.StatusUnsupportedMediaType +} + +// getManagedFieldsManager returns the manager string. If one was set it will be returned. +// Otherwise, one is calculated based on the name of the binary. +func getManagedFieldsManager() string { + + // When a manager is explicitly set use it + if ManagedFieldsManager != "" { + return ManagedFieldsManager + } + + // When no manager is set and no calling application can be found it is unknown + if len(os.Args[0]) == 0 { + return "unknown" + } + + // When there is an application that can be determined and no set manager + // use the base name. This is one of the ways Kubernetes libs handle figuring + // names out. + return filepath.Base(os.Args[0]) +} + +func perform(infos ResourceList, fn func(*resource.Info) error) error { + var result error + + if len(infos) == 0 { + return ErrNoObjectsVisited + } + + errs := make(chan error) + go batchPerform(infos, fn, errs) + + for range infos { + err := <-errs + if err != nil { + result = errors.Join(result, err) + } + } + + return result +} + +func batchPerform(infos ResourceList, fn func(*resource.Info) error, errs chan<- error) { + var kind string + var wg sync.WaitGroup + defer wg.Wait() + + for _, info := range infos { + currentKind := info.Object.GetObjectKind().GroupVersionKind().Kind + if kind != currentKind { + wg.Wait() + kind = currentKind + } + + wg.Add(1) + go func(info *resource.Info) { + errs <- fn(info) + wg.Done() + }(info) + } +} + +var createMutex sync.Mutex + +func createResource(info *resource.Info) error { + return retry.RetryOnConflict( + retry.DefaultRetry, + func() error { + createMutex.Lock() + defer createMutex.Unlock() + obj, err := resource.NewHelper(info.Client, info.Mapping).WithFieldManager(getManagedFieldsManager()).Create(info.Namespace, true, info.Object) + if err != nil { + return err + } + + return info.Refresh(obj, true) + }) +} + +func deleteResource(info *resource.Info, policy metav1.DeletionPropagation) error { + return retry.RetryOnConflict( + retry.DefaultRetry, + func() error { + opts := &metav1.DeleteOptions{PropagationPolicy: &policy} + _, err := resource.NewHelper(info.Client, info.Mapping).WithFieldManager(getManagedFieldsManager()).DeleteWithOptions(info.Namespace, info.Name, opts) + return err + }) +} + +func createPatch(original runtime.Object, target *resource.Info, threeWayMergeForUnstructured bool) ([]byte, types.PatchType, error) { + oldData, err := json.Marshal(original) + if err != nil { + return nil, types.StrategicMergePatchType, fmt.Errorf("serializing current configuration: %w", err) + } + newData, err := json.Marshal(target.Object) + if err != nil { + return nil, types.StrategicMergePatchType, fmt.Errorf("serializing target configuration: %w", err) + } + + // Fetch the current object for the three way merge + helper := resource.NewHelper(target.Client, target.Mapping).WithFieldManager(getManagedFieldsManager()) + currentObj, err := helper.Get(target.Namespace, target.Name) + if err != nil && !apierrors.IsNotFound(err) { + return nil, types.StrategicMergePatchType, fmt.Errorf("unable to get data for current object %s/%s: %w", target.Namespace, target.Name, err) + } + + // Even if currentObj is nil (because it was not found), it will marshal just fine + currentData, err := json.Marshal(currentObj) + if err != nil { + return nil, types.StrategicMergePatchType, fmt.Errorf("serializing live configuration: %w", err) + } + + // Get a versioned object + versionedObject := AsVersioned(target) + + // Unstructured objects, such as CRDs, may not have a not registered error + // returned from ConvertToVersion. Anything that's unstructured should + // use generic JSON merge patch. Strategic Merge Patch is not supported + // on objects like CRDs. + _, isUnstructured := versionedObject.(runtime.Unstructured) + + // On newer K8s versions, CRDs aren't unstructured but has this dedicated type + _, isCRD := versionedObject.(*apiextv1beta1.CustomResourceDefinition) + + if isUnstructured || isCRD { + if threeWayMergeForUnstructured { + // from https://github.com/kubernetes/kubectl/blob/b83b2ec7d15f286720bccf7872b5c72372cb8e80/pkg/cmd/apply/patcher.go#L129 + preconditions := []mergepatch.PreconditionFunc{ + mergepatch.RequireKeyUnchanged("apiVersion"), + mergepatch.RequireKeyUnchanged("kind"), + mergepatch.RequireMetadataKeyUnchanged("name"), + } + patch, err := jsonmergepatch.CreateThreeWayJSONMergePatch(oldData, newData, currentData, preconditions...) + if err != nil && mergepatch.IsPreconditionFailed(err) { + err = fmt.Errorf("%w: at least one field was changed: apiVersion, kind or name", err) + } + return patch, types.MergePatchType, err + } + // fall back to generic JSON merge patch + patch, err := jsonpatch.CreateMergePatch(oldData, newData) + return patch, types.MergePatchType, err + } + + patchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObject) + if err != nil { + return nil, types.StrategicMergePatchType, fmt.Errorf("unable to create patch metadata from object: %w", err) + } + + patch, err := strategicpatch.CreateThreeWayMergePatch(oldData, newData, currentData, patchMeta, true) + return patch, types.StrategicMergePatchType, err +} + +func replaceResource(target *resource.Info, fieldValidationDirective FieldValidationDirective) error { + + helper := resource.NewHelper(target.Client, target.Mapping). + WithFieldValidation(string(fieldValidationDirective)). + WithFieldManager(getManagedFieldsManager()) + + obj, err := helper.Replace(target.Namespace, target.Name, true, target.Object) + if err != nil { + return fmt.Errorf("failed to replace object: %w", err) + } + + if err := target.Refresh(obj, true); err != nil { + return fmt.Errorf("failed to refresh object after replace: %w", err) + } + + return nil + +} + +func patchResourceClientSide(original runtime.Object, target *resource.Info, threeWayMergeForUnstructured bool) error { + + patch, patchType, err := createPatch(original, target, threeWayMergeForUnstructured) + if err != nil { + return fmt.Errorf("failed to create patch: %w", err) + } + + kind := target.Mapping.GroupVersionKind.Kind + if patch == nil || string(patch) == "{}" { + slog.Debug("no changes detected", "kind", kind, "name", target.Name) + // This needs to happen to make sure that Helm has the latest info from the API + // Otherwise there will be no labels and other functions that use labels will panic + if err := target.Get(); err != nil { + return fmt.Errorf("failed to refresh resource information: %w", err) + } + return nil + } + + // send patch to server + slog.Debug("patching resource", "kind", kind, "name", target.Name, "namespace", target.Namespace) + helper := resource.NewHelper(target.Client, target.Mapping).WithFieldManager(getManagedFieldsManager()) + obj, err := helper.Patch(target.Namespace, target.Name, patchType, patch, nil) + if err != nil { + return fmt.Errorf("cannot patch %q with kind %s: %w", target.Name, kind, err) + } + + target.Refresh(obj, true) + + return nil +} + +// upgradeClientSideFieldManager is simply a wrapper around csaupgrade.UpgradeManagedFields +// that upgrade CSA managed fields to SSA apply +// see: https://github.com/kubernetes/kubernetes/pull/112905 +func upgradeClientSideFieldManager(info *resource.Info, dryRun bool, fieldValidationDirective FieldValidationDirective) (bool, error) { + + fieldManagerName := getManagedFieldsManager() + + patched := false + err := retry.RetryOnConflict( + retry.DefaultRetry, + func() error { + + if err := info.Get(); err != nil { + return fmt.Errorf("failed to get object %s/%s %s: %w", info.Namespace, info.Name, info.Mapping.GroupVersionKind.String(), err) + } + + helper := resource.NewHelper( + info.Client, + info.Mapping). + DryRun(dryRun). + WithFieldManager(fieldManagerName). + WithFieldValidation(string(fieldValidationDirective)) + + patchData, err := csaupgrade.UpgradeManagedFieldsPatch( + info.Object, + sets.New(fieldManagerName), + fieldManagerName) + if err != nil { + return fmt.Errorf("failed to upgrade managed fields for object %s/%s %s: %w", info.Namespace, info.Name, info.Mapping.GroupVersionKind.String(), err) + } + + if len(patchData) == 0 { + return nil + } + + obj, err := helper.Patch( + info.Namespace, + info.Name, + types.JSONPatchType, + patchData, + nil) + + if err == nil { + patched = true + return info.Refresh(obj, true) + } + + if !apierrors.IsConflict(err) { + return fmt.Errorf("failed to patch object to upgrade CSA field manager %s/%s %s: %w", info.Namespace, info.Name, info.Mapping.GroupVersionKind.String(), err) + } + + return err + }) + + return patched, err +} + +// Patch reource using server-side apply +func patchResourceServerSide(target *resource.Info, dryRun bool, forceConflicts bool, fieldValidationDirective FieldValidationDirective) error { + helper := resource.NewHelper( + target.Client, + target.Mapping). + DryRun(dryRun). + WithFieldManager(getManagedFieldsManager()). + WithFieldValidation(string(fieldValidationDirective)) + + // Send the full object to be applied on the server side. + data, err := runtime.Encode(unstructured.UnstructuredJSONScheme, target.Object) + if err != nil { + return fmt.Errorf("failed to encode object %s/%s %s: %w", target.Namespace, target.Name, target.Mapping.GroupVersionKind.String(), err) + } + options := metav1.PatchOptions{ + Force: &forceConflicts, + } + obj, err := helper.Patch( + target.Namespace, + target.Name, + types.ApplyPatchType, + data, + &options, + ) + if err != nil { + if isIncompatibleServerError(err) { + return fmt.Errorf("server-side apply not available on the server: %v", err) + } + + if apierrors.IsConflict(err) { + return fmt.Errorf("conflict occurred while applying object %s/%s %s: %w", target.Namespace, target.Name, target.Mapping.GroupVersionKind.String(), err) + } + + return err + } + + return target.Refresh(obj, true) +} + +// GetPodList uses the kubernetes interface to get the list of pods filtered by listOptions +func (c *Client) GetPodList(namespace string, listOptions metav1.ListOptions) (*v1.PodList, error) { + podList, err := c.kubeClient.CoreV1().Pods(namespace).List(context.Background(), listOptions) + if err != nil { + return nil, fmt.Errorf("failed to get pod list with options: %+v with error: %v", listOptions, err) + } + return podList, nil +} + +// OutputContainerLogsForPodList is a helper that outputs logs for a list of pods +func (c *Client) OutputContainerLogsForPodList(podList *v1.PodList, namespace string, writerFunc func(namespace, pod, container string) io.Writer) error { + for _, pod := range podList.Items { + for _, container := range pod.Spec.Containers { + options := &v1.PodLogOptions{ + Container: container.Name, + } + request := c.kubeClient.CoreV1().Pods(namespace).GetLogs(pod.Name, options) + err2 := copyRequestStreamToWriter(request, pod.Name, container.Name, writerFunc(namespace, pod.Name, container.Name)) + if err2 != nil { + return err2 + } + } + } + return nil +} + +func copyRequestStreamToWriter(request *rest.Request, podName, containerName string, writer io.Writer) error { + readCloser, err := request.Stream(context.Background()) + if err != nil { + return fmt.Errorf("failed to stream pod logs for pod: %s, container: %s", podName, containerName) + } + defer readCloser.Close() + _, err = io.Copy(writer, readCloser) + if err != nil { + return fmt.Errorf("failed to copy IO from logs for pod: %s, container: %s", podName, containerName) + } + return nil +} + +// scrubValidationError removes kubectl info from the message. +func scrubValidationError(err error) error { + if err == nil { + return nil + } + const stopValidateMessage = "if you choose to ignore these errors, turn validation off with --validate=false" + + if strings.Contains(err.Error(), stopValidateMessage) { + return errors.New(strings.ReplaceAll(err.Error(), "; "+stopValidateMessage, "")) + } + return err +} + +type joinedErrors struct { + errs []error + sep string +} + +func joinErrors(errs []error, sep string) error { + return &joinedErrors{ + errs: errs, + sep: sep, + } +} + +func (e *joinedErrors) Error() string { + errs := make([]string, 0, len(e.errs)) + for _, err := range e.errs { + errs = append(errs, err.Error()) + } + return strings.Join(errs, e.sep) +} + +func (e *joinedErrors) Unwrap() []error { + return e.errs +} diff --git a/vendor/helm.sh/helm/v4/pkg/kube/converter.go b/vendor/helm.sh/helm/v4/pkg/kube/converter.go new file mode 100644 index 00000000000..ac6d95fb448 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/kube/converter.go @@ -0,0 +1,69 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kube // import "helm.sh/helm/v4/pkg/kube" + +import ( + "sync" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/cli-runtime/pkg/resource" + "k8s.io/client-go/kubernetes/scheme" +) + +var k8sNativeScheme *runtime.Scheme +var k8sNativeSchemeOnce sync.Once + +// AsVersioned converts the given info into a runtime.Object with the correct +// group and version set +func AsVersioned(info *resource.Info) runtime.Object { + return convertWithMapper(info.Object, info.Mapping) +} + +// convertWithMapper converts the given object with the optional provided +// RESTMapping. If no mapping is provided, the default schema versioner is used +func convertWithMapper(obj runtime.Object, mapping *meta.RESTMapping) runtime.Object { + s := kubernetesNativeScheme() + var gv = runtime.GroupVersioner(schema.GroupVersions(s.PrioritizedVersionsAllGroups())) + if mapping != nil { + gv = mapping.GroupVersionKind.GroupVersion() + } + if obj, err := runtime.ObjectConvertor(s).ConvertToVersion(obj, gv); err == nil { + return obj + } + return obj +} + +// kubernetesNativeScheme returns a clean *runtime.Scheme with _only_ Kubernetes +// native resources added to it. This is required to break free of custom resources +// that may have been added to scheme.Scheme due to Helm being used as a package in +// combination with e.g. a versioned kube client. If we would not do this, the client +// may attempt to perform e.g. a 3-way-merge strategy patch for custom resources. +func kubernetesNativeScheme() *runtime.Scheme { + k8sNativeSchemeOnce.Do(func() { + k8sNativeScheme = runtime.NewScheme() + scheme.AddToScheme(k8sNativeScheme) + // API extensions are not in the above scheme set, + // and must thus be added separately. + apiextensionsv1beta1.AddToScheme(k8sNativeScheme) + apiextensionsv1.AddToScheme(k8sNativeScheme) + }) + return k8sNativeScheme +} diff --git a/vendor/helm.sh/helm/v4/pkg/kube/factory.go b/vendor/helm.sh/helm/v4/pkg/kube/factory.go new file mode 100644 index 00000000000..1d237c307e1 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/kube/factory.go @@ -0,0 +1,55 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kube // import "helm.sh/helm/v4/pkg/kube" + +import ( + "k8s.io/cli-runtime/pkg/resource" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/kubectl/pkg/validation" +) + +// Factory provides abstractions that allow the Kubectl command to be extended across multiple types +// of resources and different API sets. +// This interface is a minimal copy of the kubectl Factory interface containing only the functions +// needed by Helm. Since Kubernetes Go APIs, including interfaces, can change in any minor release +// this interface is not covered by the Helm backwards compatibility guarantee. The reasons for the +// minimal copy is that it does not include the full interface. Changes or additions to functions +// Helm does not need are not impacted or exposed. This minimizes the impact of Kubernetes changes +// being exposed. +type Factory interface { + // ToRESTConfig returns restconfig + ToRESTConfig() (*rest.Config, error) + + // ToRawKubeConfigLoader return kubeconfig loader as-is + ToRawKubeConfigLoader() clientcmd.ClientConfig + + // DynamicClient returns a dynamic client ready for use + DynamicClient() (dynamic.Interface, error) + + // KubernetesClientSet gives you back an external clientset + KubernetesClientSet() (*kubernetes.Clientset, error) + + // NewBuilder returns an object that assists in loading objects from both disk and the server + // and which implements the common patterns for CLI interactions with generic resources. + NewBuilder() *resource.Builder + + // Returns a schema that can validate objects stored on disk. + Validator(validationDirective string) (validation.Schema, error) +} diff --git a/vendor/helm.sh/helm/v4/pkg/kube/fake/failing_kube_client.go b/vendor/helm.sh/helm/v4/pkg/kube/fake/failing_kube_client.go new file mode 100644 index 00000000000..0f7787f7944 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/kube/fake/failing_kube_client.go @@ -0,0 +1,189 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package fake implements various fake KubeClients for use in testing +package fake + +import ( + "io" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/resource" + + "helm.sh/helm/v4/pkg/kube" +) + +// FailingKubeClient implements KubeClient for testing purposes. It also has +// additional errors you can set to fail different functions, otherwise it +// delegates all its calls to `PrintingKubeClient` +type FailingKubeClient struct { + PrintingKubeClient + CreateError error + GetError error + DeleteError error + UpdateError error + BuildError error + BuildTableError error + ConnectionError error + BuildDummy bool + DummyResources kube.ResourceList + BuildUnstructuredError error + WaitError error + WaitForDeleteError error + WatchUntilReadyError error + WaitDuration time.Duration + // RecordedWaitOptions stores the WaitOptions passed to GetWaiter for testing + RecordedWaitOptions []kube.WaitOption +} + +var _ kube.Interface = &FailingKubeClient{} + +// FailingKubeWaiter implements kube.Waiter for testing purposes. +// It also has additional errors you can set to fail different functions, otherwise it delegates all its calls to `PrintingKubeWaiter` +type FailingKubeWaiter struct { + *PrintingKubeWaiter + waitError error + waitForDeleteError error + watchUntilReadyError error + waitDuration time.Duration +} + +// Create returns the configured error if set or prints +func (f *FailingKubeClient) Create(resources kube.ResourceList, options ...kube.ClientCreateOption) (*kube.Result, error) { + if f.CreateError != nil { + return nil, f.CreateError + } + return f.PrintingKubeClient.Create(resources, options...) +} + +// Get returns the configured error if set or prints +func (f *FailingKubeClient) Get(resources kube.ResourceList, related bool) (map[string][]runtime.Object, error) { + if f.GetError != nil { + return nil, f.GetError + } + return f.PrintingKubeClient.Get(resources, related) +} + +// Waits the amount of time defined on f.WaitDuration, then returns the configured error if set or prints. +func (f *FailingKubeWaiter) Wait(resources kube.ResourceList, d time.Duration) error { + time.Sleep(f.waitDuration) + if f.waitError != nil { + return f.waitError + } + return f.PrintingKubeWaiter.Wait(resources, d) +} + +// WaitWithJobs returns the configured error if set or prints +func (f *FailingKubeWaiter) WaitWithJobs(resources kube.ResourceList, d time.Duration) error { + if f.waitError != nil { + return f.waitError + } + return f.PrintingKubeWaiter.WaitWithJobs(resources, d) +} + +// WaitForDelete returns the configured error if set or prints +func (f *FailingKubeWaiter) WaitForDelete(resources kube.ResourceList, d time.Duration) error { + if f.waitForDeleteError != nil { + return f.waitForDeleteError + } + return f.PrintingKubeWaiter.WaitForDelete(resources, d) +} + +// Delete returns the configured error if set or prints +func (f *FailingKubeClient) Delete(resources kube.ResourceList, deletionPropagation metav1.DeletionPropagation) (*kube.Result, []error) { + if f.DeleteError != nil { + return nil, []error{f.DeleteError} + } + + return f.PrintingKubeClient.Delete(resources, deletionPropagation) +} + +// WatchUntilReady returns the configured error if set or prints +func (f *FailingKubeWaiter) WatchUntilReady(resources kube.ResourceList, d time.Duration) error { + if f.watchUntilReadyError != nil { + return f.watchUntilReadyError + } + return f.PrintingKubeWaiter.WatchUntilReady(resources, d) +} + +// Update returns the configured error if set or prints +func (f *FailingKubeClient) Update(r, modified kube.ResourceList, options ...kube.ClientUpdateOption) (*kube.Result, error) { + if f.UpdateError != nil { + return &kube.Result{}, f.UpdateError + } + return f.PrintingKubeClient.Update(r, modified, options...) +} + +// Build returns the configured error if set or prints +func (f *FailingKubeClient) Build(r io.Reader, _ bool) (kube.ResourceList, error) { + if f.BuildError != nil { + return []*resource.Info{}, f.BuildError + } + if f.DummyResources != nil { + return f.DummyResources, nil + } + if f.BuildDummy { + return createDummyResourceList(), nil + } + return f.PrintingKubeClient.Build(r, false) +} + +// BuildTable returns the configured error if set or prints +func (f *FailingKubeClient) BuildTable(r io.Reader, _ bool) (kube.ResourceList, error) { + if f.BuildTableError != nil { + return []*resource.Info{}, f.BuildTableError + } + if f.BuildDummy { + return createDummyResourceList(), nil + } + return f.PrintingKubeClient.BuildTable(r, false) +} + +func (f *FailingKubeClient) GetWaiter(ws kube.WaitStrategy) (kube.Waiter, error) { + return f.GetWaiterWithOptions(ws) +} + +func (f *FailingKubeClient) GetWaiterWithOptions(ws kube.WaitStrategy, opts ...kube.WaitOption) (kube.Waiter, error) { + // Record the WaitOptions for testing + f.RecordedWaitOptions = append(f.RecordedWaitOptions, opts...) + waiter, _ := f.PrintingKubeClient.GetWaiterWithOptions(ws, opts...) + printingKubeWaiter, _ := waiter.(*PrintingKubeWaiter) + return &FailingKubeWaiter{ + PrintingKubeWaiter: printingKubeWaiter, + waitError: f.WaitError, + waitForDeleteError: f.WaitForDeleteError, + watchUntilReadyError: f.WatchUntilReadyError, + waitDuration: f.WaitDuration, + }, nil +} + +func (f *FailingKubeClient) IsReachable() error { + if f.ConnectionError != nil { + return f.ConnectionError + } + return f.PrintingKubeClient.IsReachable() +} + +func createDummyResourceList() kube.ResourceList { + var resInfo resource.Info + resInfo.Name = "dummyName" + resInfo.Namespace = "dummyNamespace" + var resourceList kube.ResourceList + resourceList.Append(&resInfo) + return resourceList +} diff --git a/vendor/helm.sh/helm/v4/pkg/kube/fake/printer.go b/vendor/helm.sh/helm/v4/pkg/kube/fake/printer.go new file mode 100644 index 00000000000..e3fa11576f1 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/kube/fake/printer.go @@ -0,0 +1,165 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fake + +import ( + "fmt" + "io" + "strings" + "time" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/resource" + + "helm.sh/helm/v4/pkg/kube" +) + +// PrintingKubeClient implements KubeClient, but simply prints the reader to +// the given output. +type PrintingKubeClient struct { + Out io.Writer + LogOutput io.Writer +} + +// PrintingKubeWaiter implements kube.Waiter, but simply prints the reader to the given output +type PrintingKubeWaiter struct { + Out io.Writer + LogOutput io.Writer +} + +var _ kube.Interface = &PrintingKubeClient{} + +// IsReachable checks if the cluster is reachable +func (p *PrintingKubeClient) IsReachable() error { + return nil +} + +// Create prints the values of what would be created with a real KubeClient. +func (p *PrintingKubeClient) Create(resources kube.ResourceList, _ ...kube.ClientCreateOption) (*kube.Result, error) { + _, err := io.Copy(p.Out, bufferize(resources)) + if err != nil { + return nil, err + } + return &kube.Result{Created: resources}, nil +} + +func (p *PrintingKubeClient) Get(resources kube.ResourceList, _ bool) (map[string][]runtime.Object, error) { + _, err := io.Copy(p.Out, bufferize(resources)) + if err != nil { + return nil, err + } + return make(map[string][]runtime.Object), nil +} + +func (p *PrintingKubeWaiter) Wait(resources kube.ResourceList, _ time.Duration) error { + _, err := io.Copy(p.Out, bufferize(resources)) + return err +} + +func (p *PrintingKubeWaiter) WaitWithJobs(resources kube.ResourceList, _ time.Duration) error { + _, err := io.Copy(p.Out, bufferize(resources)) + return err +} + +func (p *PrintingKubeWaiter) WaitForDelete(resources kube.ResourceList, _ time.Duration) error { + _, err := io.Copy(p.Out, bufferize(resources)) + return err +} + +// WatchUntilReady implements KubeClient WatchUntilReady. +func (p *PrintingKubeWaiter) WatchUntilReady(resources kube.ResourceList, _ time.Duration) error { + _, err := io.Copy(p.Out, bufferize(resources)) + return err +} + +// Delete implements KubeClient delete. +// +// It only prints out the content to be deleted. +func (p *PrintingKubeClient) Delete(resources kube.ResourceList, _ metav1.DeletionPropagation) (*kube.Result, []error) { + _, err := io.Copy(p.Out, bufferize(resources)) + if err != nil { + return nil, []error{err} + } + return &kube.Result{Deleted: resources}, nil +} + +// Update implements KubeClient Update. +func (p *PrintingKubeClient) Update(_, modified kube.ResourceList, _ ...kube.ClientUpdateOption) (*kube.Result, error) { + _, err := io.Copy(p.Out, bufferize(modified)) + if err != nil { + return nil, err + } + // TODO: This doesn't completely mock out have some that get created, + // updated, and deleted. I don't think these are used in any unit tests, but + // we may want to refactor a way to handle future tests + return &kube.Result{Updated: modified}, nil +} + +// Build implements KubeClient Build. +func (p *PrintingKubeClient) Build(_ io.Reader, _ bool) (kube.ResourceList, error) { + return []*resource.Info{}, nil +} + +// BuildTable implements KubeClient BuildTable. +func (p *PrintingKubeClient) BuildTable(_ io.Reader, _ bool) (kube.ResourceList, error) { + return []*resource.Info{}, nil +} + +// WaitAndGetCompletedPodPhase implements KubeClient WaitAndGetCompletedPodPhase. +func (p *PrintingKubeClient) WaitAndGetCompletedPodPhase(_ string, _ time.Duration) (v1.PodPhase, error) { + return v1.PodSucceeded, nil +} + +// GetPodList implements KubeClient GetPodList. +func (p *PrintingKubeClient) GetPodList(_ string, _ metav1.ListOptions) (*v1.PodList, error) { + return &v1.PodList{}, nil +} + +// OutputContainerLogsForPodList implements KubeClient OutputContainerLogsForPodList. +func (p *PrintingKubeClient) OutputContainerLogsForPodList(_ *v1.PodList, someNamespace string, _ func(namespace, pod, container string) io.Writer) error { + _, err := io.Copy(p.LogOutput, strings.NewReader(fmt.Sprintf("attempted to output logs for namespace: %s", someNamespace))) + return err +} + +// DeleteWithPropagationPolicy implements KubeClient delete. +// +// It only prints out the content to be deleted. +func (p *PrintingKubeClient) DeleteWithPropagationPolicy(resources kube.ResourceList, _ metav1.DeletionPropagation) (*kube.Result, []error) { + _, err := io.Copy(p.Out, bufferize(resources)) + if err != nil { + return nil, []error{err} + } + return &kube.Result{Deleted: resources}, nil +} + +func (p *PrintingKubeClient) GetWaiter(ws kube.WaitStrategy) (kube.Waiter, error) { + return p.GetWaiterWithOptions(ws) +} + +func (p *PrintingKubeClient) GetWaiterWithOptions(_ kube.WaitStrategy, _ ...kube.WaitOption) (kube.Waiter, error) { + return &PrintingKubeWaiter{Out: p.Out, LogOutput: p.LogOutput}, nil +} + +func bufferize(resources kube.ResourceList) io.Reader { + var builder strings.Builder + for _, info := range resources { + builder.WriteString(info.String() + "\n") + } + return strings.NewReader(builder.String()) +} diff --git a/vendor/helm.sh/helm/v4/pkg/kube/interface.go b/vendor/helm.sh/helm/v4/pkg/kube/interface.go new file mode 100644 index 00000000000..63c78475120 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/kube/interface.go @@ -0,0 +1,112 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kube + +import ( + "io" + "time" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// Interface represents a client capable of communicating with the Kubernetes API. +// +// A KubernetesClient must be concurrency safe. +type Interface interface { + // Get details of deployed resources. + // The first argument is a list of resources to get. The second argument + // specifies if related pods should be fetched. For example, the pods being + // managed by a deployment. + Get(resources ResourceList, related bool) (map[string][]runtime.Object, error) + + // Create creates one or more resources. + Create(resources ResourceList, options ...ClientCreateOption) (*Result, error) + + // Delete destroys one or more resources using the specified deletion propagation policy. + // The 'policy' parameter determines how child resources are handled during deletion. + Delete(resources ResourceList, policy metav1.DeletionPropagation) (*Result, []error) + + // Update updates one or more resources or creates the resource + // if it doesn't exist. + Update(original, target ResourceList, options ...ClientUpdateOption) (*Result, error) + + // Build creates a resource list from a Reader. + // + // Reader must contain a YAML stream (one or more YAML documents separated + // by "\n---\n") + // + // Validates against OpenAPI schema if validate is true. + Build(reader io.Reader, validate bool) (ResourceList, error) + // IsReachable checks whether the client is able to connect to the cluster. + IsReachable() error + + // GetWaiter gets the Kube.Waiter. + GetWaiter(ws WaitStrategy) (Waiter, error) + + // GetPodList lists all pods that match the specified listOptions + GetPodList(namespace string, listOptions metav1.ListOptions) (*v1.PodList, error) + + // OutputContainerLogsForPodList outputs the logs for a pod list + OutputContainerLogsForPodList(podList *v1.PodList, namespace string, writerFunc func(namespace, pod, container string) io.Writer) error + + // BuildTable creates a resource list from a Reader. This differs from + // Interface.Build() in that a table kind is returned. A table is useful + // if you want to use a printer to display the information. + // + // Reader must contain a YAML stream (one or more YAML documents separated + // by "\n---\n") + // + // Validates against OpenAPI schema if validate is true. + // TODO Helm 4: Integrate into Build with an argument + BuildTable(reader io.Reader, validate bool) (ResourceList, error) +} + +// Waiter defines methods related to waiting for resource states. +type Waiter interface { + // Wait waits up to the given timeout for the specified resources to be ready. + Wait(resources ResourceList, timeout time.Duration) error + + // WaitWithJobs wait up to the given timeout for the specified resources to be ready, including jobs. + WaitWithJobs(resources ResourceList, timeout time.Duration) error + + // WaitForDelete wait up to the given timeout for the specified resources to be deleted. + WaitForDelete(resources ResourceList, timeout time.Duration) error + + // WatchUntilReady watches the resources given and waits until it is ready. + // + // This method is mainly for hook implementations. It watches for a resource to + // hit a particular milestone. The milestone depends on the Kind. + // + // For Jobs, "ready" means the Job ran to completion (exited without error). + // For Pods, "ready" means the Pod phase is marked "succeeded". + // For all other kinds, it means the kind was created or modified without + // error. + WatchUntilReady(resources ResourceList, timeout time.Duration) error +} + +// InterfaceWaitOptions defines an interface that extends Interface with +// methods that accept wait options. +// +// TODO Helm 5: Remove InterfaceWaitOptions and integrate its method(s) into the Interface. +type InterfaceWaitOptions interface { + // GetWaiter gets the Kube.Waiter with options. + GetWaiterWithOptions(ws WaitStrategy, opts ...WaitOption) (Waiter, error) +} + +var _ InterfaceWaitOptions = (*Client)(nil) diff --git a/vendor/helm.sh/helm/v4/pkg/kube/options.go b/vendor/helm.sh/helm/v4/pkg/kube/options.go new file mode 100644 index 00000000000..3326c284b76 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/kube/options.go @@ -0,0 +1,82 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kube + +import ( + "context" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" +) + +// WaitOption is a function that configures an option for waiting on resources. +type WaitOption func(*waitOptions) + +// WithWaitContext sets the context for waiting on resources. +// If unset, context.Background() will be used. +func WithWaitContext(ctx context.Context) WaitOption { + return func(wo *waitOptions) { + wo.ctx = ctx + } +} + +// WithWatchUntilReadyMethodContext sets the context specifically for the WatchUntilReady method. +// If unset, the context set by `WithWaitContext` will be used (falling back to `context.Background()`). +func WithWatchUntilReadyMethodContext(ctx context.Context) WaitOption { + return func(wo *waitOptions) { + wo.watchUntilReadyCtx = ctx + } +} + +// WithWaitMethodContext sets the context specifically for the Wait method. +// If unset, the context set by `WithWaitContext` will be used (falling back to `context.Background()`). +func WithWaitMethodContext(ctx context.Context) WaitOption { + return func(wo *waitOptions) { + wo.waitCtx = ctx + } +} + +// WithWaitWithJobsMethodContext sets the context specifically for the WaitWithJobs method. +// If unset, the context set by `WithWaitContext` will be used (falling back to `context.Background()`). +func WithWaitWithJobsMethodContext(ctx context.Context) WaitOption { + return func(wo *waitOptions) { + wo.waitWithJobsCtx = ctx + } +} + +// WithWaitForDeleteMethodContext sets the context specifically for the WaitForDelete method. +// If unset, the context set by `WithWaitContext` will be used (falling back to `context.Background()`). +func WithWaitForDeleteMethodContext(ctx context.Context) WaitOption { + return func(wo *waitOptions) { + wo.waitForDeleteCtx = ctx + } +} + +// WithKStatusReaders sets the status readers to be used while waiting on resources. +func WithKStatusReaders(readers ...engine.StatusReader) WaitOption { + return func(wo *waitOptions) { + wo.statusReaders = readers + } +} + +type waitOptions struct { + ctx context.Context + watchUntilReadyCtx context.Context + waitCtx context.Context + waitWithJobsCtx context.Context + waitForDeleteCtx context.Context + statusReaders []engine.StatusReader +} diff --git a/vendor/helm.sh/helm/v4/pkg/kube/ready.go b/vendor/helm.sh/helm/v4/pkg/kube/ready.go new file mode 100644 index 00000000000..bfa98504c49 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/kube/ready.go @@ -0,0 +1,466 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kube // import "helm.sh/helm/v4/pkg/kube" + +import ( + "context" + "fmt" + "log/slog" + + appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/cli-runtime/pkg/resource" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + + deploymentutil "helm.sh/helm/v4/internal/third_party/k8s.io/kubernetes/deployment/util" +) + +// ReadyCheckerOption is a function that configures a ReadyChecker. +type ReadyCheckerOption func(*ReadyChecker) + +// PausedAsReady returns a ReadyCheckerOption that configures a ReadyChecker +// to consider paused resources to be ready. For example a Deployment +// with spec.paused equal to true would be considered ready. +func PausedAsReady(pausedAsReady bool) ReadyCheckerOption { + return func(c *ReadyChecker) { + c.pausedAsReady = pausedAsReady + } +} + +// CheckJobs returns a ReadyCheckerOption that configures a ReadyChecker +// to consider readiness of Job resources. +func CheckJobs(checkJobs bool) ReadyCheckerOption { + return func(c *ReadyChecker) { + c.checkJobs = checkJobs + } +} + +// NewReadyChecker creates a new checker. Passed ReadyCheckerOptions can +// be used to override defaults. +func NewReadyChecker(cl kubernetes.Interface, opts ...ReadyCheckerOption) ReadyChecker { + c := ReadyChecker{ + client: cl, + } + for _, opt := range opts { + opt(&c) + } + return c +} + +// ReadyChecker is a type that can check core Kubernetes types for readiness. +type ReadyChecker struct { + client kubernetes.Interface + checkJobs bool + pausedAsReady bool +} + +// IsReady checks if v is ready. It supports checking readiness for pods, +// deployments, persistent volume claims, services, daemon sets, custom +// resource definitions, stateful sets, replication controllers, jobs (optional), +// and replica sets. All other resource kinds are always considered ready. +// +// IsReady will fetch the latest state of the object from the server prior to +// performing readiness checks, and it will return any error encountered. +func (c *ReadyChecker) IsReady(ctx context.Context, v *resource.Info) (bool, error) { + switch value := AsVersioned(v).(type) { + case *corev1.Pod: + pod, err := c.client.CoreV1().Pods(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) + if err != nil || !c.isPodReady(pod) { + return false, err + } + case *batchv1.Job: + if c.checkJobs { + job, err := c.client.BatchV1().Jobs(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + ready, err := c.jobReady(job) + return ready, err + } + case *appsv1.Deployment: + currentDeployment, err := c.client.AppsV1().Deployments(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + // If paused deployment will never be ready + if currentDeployment.Spec.Paused { + return c.pausedAsReady, nil + } + // Find RS associated with deployment + newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, c.client.AppsV1()) + if err != nil || newReplicaSet == nil { + return false, err + } + if !c.deploymentReady(newReplicaSet, currentDeployment) { + return false, nil + } + case *corev1.PersistentVolumeClaim: + claim, err := c.client.CoreV1().PersistentVolumeClaims(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + if !c.volumeReady(claim) { + return false, nil + } + case *corev1.Service: + svc, err := c.client.CoreV1().Services(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + if !c.serviceReady(svc) { + return false, nil + } + case *appsv1.DaemonSet: + ds, err := c.client.AppsV1().DaemonSets(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + if !c.daemonSetReady(ds) { + return false, nil + } + case *apiextv1beta1.CustomResourceDefinition: + if err := v.Get(); err != nil { + return false, err + } + crd := &apiextv1beta1.CustomResourceDefinition{} + if err := scheme.Scheme.Convert(v.Object, crd, nil); err != nil { + return false, err + } + if !c.crdBetaReady(*crd) { + return false, nil + } + case *apiextv1.CustomResourceDefinition: + if err := v.Get(); err != nil { + return false, err + } + crd := &apiextv1.CustomResourceDefinition{} + if err := scheme.Scheme.Convert(v.Object, crd, nil); err != nil { + return false, err + } + if !c.crdReady(*crd) { + return false, nil + } + case *appsv1.StatefulSet: + sts, err := c.client.AppsV1().StatefulSets(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + if !c.statefulSetReady(sts) { + return false, nil + } + case *corev1.ReplicationController: + rc, err := c.client.CoreV1().ReplicationControllers(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + if !c.replicationControllerReady(rc) { + return false, nil + } + ready, err := c.podsReadyForObject(ctx, v.Namespace, value) + if !ready || err != nil { + return false, err + } + case *appsv1.ReplicaSet: + rs, err := c.client.AppsV1().ReplicaSets(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + if !c.replicaSetReady(rs) { + return false, nil + } + ready, err := c.podsReadyForObject(ctx, v.Namespace, value) + if !ready || err != nil { + return false, err + } + } + return true, nil +} + +func (c *ReadyChecker) podsReadyForObject(ctx context.Context, namespace string, obj runtime.Object) (bool, error) { + pods, err := c.podsforObject(ctx, namespace, obj) + if err != nil { + return false, err + } + for _, pod := range pods { + if !c.isPodReady(&pod) { + return false, nil + } + } + return true, nil +} + +func (c *ReadyChecker) podsforObject(ctx context.Context, namespace string, obj runtime.Object) ([]corev1.Pod, error) { + selector, err := SelectorsForObject(obj) + if err != nil { + return nil, err + } + list, err := getPods(ctx, c.client, namespace, selector.String()) + return list, err +} + +// isPodReady returns true if a pod is ready; false otherwise. +func (c *ReadyChecker) isPodReady(pod *corev1.Pod) bool { + for _, c := range pod.Status.Conditions { + if c.Type == corev1.PodReady && c.Status == corev1.ConditionTrue { + return true + } + } + slog.Debug("Pod is not ready", "namespace", pod.GetNamespace(), "name", pod.GetName()) + return false +} + +func (c *ReadyChecker) jobReady(job *batchv1.Job) (bool, error) { + if job.Status.Failed > *job.Spec.BackoffLimit { + slog.Debug("Job is failed", "namespace", job.GetNamespace(), "name", job.GetName()) + // If a job is failed, it can't recover, so throw an error + return false, fmt.Errorf("job is failed: %s/%s", job.GetNamespace(), job.GetName()) + } + if job.Spec.Completions != nil && job.Status.Succeeded < *job.Spec.Completions { + slog.Debug("Job is not completed", "namespace", job.GetNamespace(), "name", job.GetName()) + return false, nil + } + slog.Debug("Job is completed", "namespace", job.GetNamespace(), "name", job.GetName()) + return true, nil +} + +func (c *ReadyChecker) serviceReady(s *corev1.Service) bool { + // ExternalName Services are external to cluster so helm shouldn't be checking to see if they're 'ready' (i.e. have an IP Set) + if s.Spec.Type == corev1.ServiceTypeExternalName { + return true + } + + // Ensure that the service cluster IP is not empty + if s.Spec.ClusterIP == "" { + slog.Debug("Service does not have cluster IP address", "namespace", s.GetNamespace(), "name", s.GetName()) + return false + } + + // This checks if the service has a LoadBalancer and that balancer has an Ingress defined + if s.Spec.Type == corev1.ServiceTypeLoadBalancer { + // do not wait when at least 1 external IP is set + if len(s.Spec.ExternalIPs) > 0 { + slog.Debug("Service has external IP addresses", "namespace", s.GetNamespace(), "name", s.GetName(), "externalIPs", s.Spec.ExternalIPs) + return true + } + + if s.Status.LoadBalancer.Ingress == nil { + slog.Debug("Service does not have load balancer ingress IP address", "namespace", s.GetNamespace(), "name", s.GetName()) + return false + } + } + slog.Debug("Service is ready", "namespace", s.GetNamespace(), "name", s.GetName(), "clusterIP", s.Spec.ClusterIP, "externalIPs", s.Spec.ExternalIPs) + return true +} + +func (c *ReadyChecker) volumeReady(v *corev1.PersistentVolumeClaim) bool { + if v.Status.Phase != corev1.ClaimBound { + slog.Debug("PersistentVolumeClaim is not bound", "namespace", v.GetNamespace(), "name", v.GetName()) + return false + } + slog.Debug("PersistentVolumeClaim is bound", "namespace", v.GetNamespace(), "name", v.GetName(), "phase", v.Status.Phase) + return true +} + +func (c *ReadyChecker) deploymentReady(rs *appsv1.ReplicaSet, dep *appsv1.Deployment) bool { + // Verify the replicaset readiness + if !c.replicaSetReady(rs) { + return false + } + // Verify the generation observed by the deployment controller matches the spec generation + if dep.Status.ObservedGeneration != dep.Generation { + slog.Debug("Deployment is not ready, observedGeneration does not match spec generation", "namespace", dep.GetNamespace(), "name", dep.GetName(), "actualGeneration", dep.Status.ObservedGeneration, "expectedGeneration", dep.Generation) + return false + } + + expectedReady := *dep.Spec.Replicas - deploymentutil.MaxUnavailable(*dep) + if rs.Status.ReadyReplicas < expectedReady { + slog.Debug("Deployment does not have enough pods ready", "namespace", dep.GetNamespace(), "name", dep.GetName(), "readyPods", rs.Status.ReadyReplicas, "totalPods", expectedReady) + return false + } + slog.Debug("Deployment is ready", "namespace", dep.GetNamespace(), "name", dep.GetName(), "readyPods", rs.Status.ReadyReplicas, "totalPods", expectedReady) + return true +} + +func (c *ReadyChecker) daemonSetReady(ds *appsv1.DaemonSet) bool { + // Verify the generation observed by the daemonSet controller matches the spec generation + if ds.Status.ObservedGeneration != ds.Generation { + slog.Debug("DaemonSet is not ready, observedGeneration does not match spec generation", "namespace", ds.GetNamespace(), "name", ds.GetName(), "observedGeneration", ds.Status.ObservedGeneration, "expectedGeneration", ds.Generation) + return false + } + + // If the update strategy is not a rolling update, there will be nothing to wait for + if ds.Spec.UpdateStrategy.Type != appsv1.RollingUpdateDaemonSetStrategyType { + return true + } + + // Make sure all the updated pods have been scheduled + if ds.Status.UpdatedNumberScheduled != ds.Status.DesiredNumberScheduled { + slog.Debug("DaemonSet does not have enough Pods scheduled", "namespace", ds.GetNamespace(), "name", ds.GetName(), "scheduledPods", ds.Status.UpdatedNumberScheduled, "totalPods", ds.Status.DesiredNumberScheduled) + return false + } + maxUnavailable, err := intstr.GetScaledValueFromIntOrPercent(ds.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable, int(ds.Status.DesiredNumberScheduled), true) + if err != nil { + // If for some reason the value is invalid, set max unavailable to the + // number of desired replicas. This is the same behavior as the + // `MaxUnavailable` function in deploymentutil + maxUnavailable = int(ds.Status.DesiredNumberScheduled) + } + + expectedReady := int(ds.Status.DesiredNumberScheduled) - maxUnavailable + if int(ds.Status.NumberReady) < expectedReady { + slog.Debug("DaemonSet does not have enough Pods ready", "namespace", ds.GetNamespace(), "name", ds.GetName(), "readyPods", ds.Status.NumberReady, "totalPods", expectedReady) + return false + } + slog.Debug("DaemonSet is ready", "namespace", ds.GetNamespace(), "name", ds.GetName(), "readyPods", ds.Status.NumberReady, "totalPods", expectedReady) + return true +} + +// Because the v1 extensions API is not available on all supported k8s versions +// yet and because Go doesn't support generics, we need to have a duplicate +// function to support the v1beta1 types +func (c *ReadyChecker) crdBetaReady(crd apiextv1beta1.CustomResourceDefinition) bool { + for _, cond := range crd.Status.Conditions { + switch cond.Type { + case apiextv1beta1.Established: + if cond.Status == apiextv1beta1.ConditionTrue { + return true + } + case apiextv1beta1.NamesAccepted: + if cond.Status == apiextv1beta1.ConditionFalse { + // This indicates a naming conflict, but it's probably not the + // job of this function to fail because of that. Instead, + // we treat it as a success, since the process should be able to + // continue. + return true + } + default: + // intentionally left empty + } + } + return false +} + +func (c *ReadyChecker) crdReady(crd apiextv1.CustomResourceDefinition) bool { + for _, cond := range crd.Status.Conditions { + switch cond.Type { + case apiextv1.Established: + if cond.Status == apiextv1.ConditionTrue { + return true + } + case apiextv1.NamesAccepted: + if cond.Status == apiextv1.ConditionFalse { + // This indicates a naming conflict, but it's probably not the + // job of this function to fail because of that. Instead, + // we treat it as a success, since the process should be able to + // continue. + return true + } + default: + // intentionally left empty + } + } + return false +} + +func (c *ReadyChecker) statefulSetReady(sts *appsv1.StatefulSet) bool { + // Verify the generation observed by the statefulSet controller matches the spec generation + if sts.Status.ObservedGeneration != sts.Generation { + slog.Debug("StatefulSet is not ready, observedGeneration doest not match spec generation", "namespace", sts.GetNamespace(), "name", sts.GetName(), "actualGeneration", sts.Status.ObservedGeneration, "expectedGeneration", sts.Generation) + return false + } + + // If the update strategy is not a rolling update, there will be nothing to wait for + if sts.Spec.UpdateStrategy.Type != appsv1.RollingUpdateStatefulSetStrategyType { + slog.Debug("StatefulSet skipped ready check", "namespace", sts.GetNamespace(), "name", sts.GetName(), "updateStrategy", sts.Spec.UpdateStrategy.Type) + return true + } + + // Dereference all the pointers because StatefulSets like them + var partition int + // 1 is the default for replicas if not set + replicas := 1 + // For some reason, even if the update strategy is a rolling update, the + // actual rollingUpdate field can be nil. If it is, we can safely assume + // there is no partition value + if sts.Spec.UpdateStrategy.RollingUpdate != nil && sts.Spec.UpdateStrategy.RollingUpdate.Partition != nil { + partition = int(*sts.Spec.UpdateStrategy.RollingUpdate.Partition) + } + if sts.Spec.Replicas != nil { + replicas = int(*sts.Spec.Replicas) + } + + // Because an update strategy can use partitioning, we need to calculate the + // number of updated replicas we should have. For example, if the replicas + // is set to 3 and the partition is 2, we'd expect only one pod to be + // updated + expectedReplicas := replicas - partition + + // Make sure all the updated pods have been scheduled + if int(sts.Status.UpdatedReplicas) < expectedReplicas { + slog.Debug("StatefulSet does not have enough Pods scheduled", "namespace", sts.GetNamespace(), "name", sts.GetName(), "readyPods", sts.Status.UpdatedReplicas, "totalPods", expectedReplicas) + return false + } + + if int(sts.Status.ReadyReplicas) != replicas { + slog.Debug("StatefulSet does not have enough Pods ready", "namespace", sts.GetNamespace(), "name", sts.GetName(), "readyPods", sts.Status.ReadyReplicas, "totalPods", replicas) + return false + } + // This check only makes sense when all partitions are being upgraded otherwise during a + // partitioned rolling upgrade, this condition will never evaluate to true, leading to + // error. + if partition == 0 && sts.Status.CurrentRevision != sts.Status.UpdateRevision { + slog.Debug("StatefulSet is not ready, currentRevision does not match updateRevision", "namespace", sts.GetNamespace(), "name", sts.GetName(), "currentRevision", sts.Status.CurrentRevision, "updateRevision", sts.Status.UpdateRevision) + return false + } + slog.Debug("StatefulSet is ready", "namespace", sts.GetNamespace(), "name", sts.GetName(), "readyPods", sts.Status.ReadyReplicas, "totalPods", replicas) + return true +} + +func (c *ReadyChecker) replicationControllerReady(rc *corev1.ReplicationController) bool { + // Verify the generation observed by the replicationController controller matches the spec generation + if rc.Status.ObservedGeneration != rc.Generation { + slog.Debug("ReplicationController is not ready, observedGeneration doest not match spec generation", "namespace", rc.GetNamespace(), "name", rc.GetName(), "actualGeneration", rc.Status.ObservedGeneration, "expectedGeneration", rc.Generation) + return false + } + return true +} + +func (c *ReadyChecker) replicaSetReady(rs *appsv1.ReplicaSet) bool { + // Verify the generation observed by the replicaSet controller matches the spec generation + if rs.Status.ObservedGeneration != rs.Generation { + slog.Debug("ReplicaSet is not ready, observedGeneration doest not match spec generation", "namespace", rs.GetNamespace(), "name", rs.GetName(), "actualGeneration", rs.Status.ObservedGeneration, "expectedGeneration", rs.Generation) + return false + } + return true +} + +func getPods(ctx context.Context, client kubernetes.Interface, namespace, selector string) ([]corev1.Pod, error) { + list, err := client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ + LabelSelector: selector, + }) + if err != nil { + return nil, fmt.Errorf("failed to list pods: %w", err) + } + return list.Items, nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/kube/resource.go b/vendor/helm.sh/helm/v4/pkg/kube/resource.go new file mode 100644 index 00000000000..d6d08d589d5 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/kube/resource.go @@ -0,0 +1,92 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kube // import "helm.sh/helm/v4/pkg/kube" + +import "k8s.io/cli-runtime/pkg/resource" + +// ResourceList provides convenience methods for comparing collections of Infos. +type ResourceList []*resource.Info + +// Append adds an Info to the Result. +func (r *ResourceList) Append(val *resource.Info) { + *r = append(*r, val) +} + +// Visit implements resource.Visitor. The visitor stops if fn returns an error. +func (r ResourceList) Visit(fn resource.VisitorFunc) error { + for _, i := range r { + if err := fn(i, nil); err != nil { + return err + } + } + return nil +} + +// Filter returns a new Result with Infos that satisfy the predicate fn. +func (r ResourceList) Filter(fn func(*resource.Info) bool) ResourceList { + var result ResourceList + for _, i := range r { + if fn(i) { + result.Append(i) + } + } + return result +} + +// Get returns the Info from the result that matches the name and kind. +func (r ResourceList) Get(info *resource.Info) *resource.Info { + for _, i := range r { + if isMatchingInfo(i, info) { + return i + } + } + return nil +} + +// Contains checks to see if an object exists. +func (r ResourceList) Contains(info *resource.Info) bool { + for _, i := range r { + if isMatchingInfo(i, info) { + return true + } + } + return false +} + +// Difference will return a new Result with objects not contained in rs. +func (r ResourceList) Difference(rs ResourceList) ResourceList { + return r.Filter(func(info *resource.Info) bool { + return !rs.Contains(info) + }) +} + +// Intersect will return a new Result with objects contained in both Results. +func (r ResourceList) Intersect(rs ResourceList) ResourceList { + return r.Filter(rs.Contains) +} + +// isMatchingInfo returns true if infos match on Name, Namespace, Group and Kind. +// +// IMPORTANT: Version is intentionally excluded from the comparison. Resources +// served by the same CRD at different API versions (e.g. v2beta1 vs v2beta2) +// share the same underlying storage in the Kubernetes API server. Comparing +// the full GroupVersionKind causes Difference() to treat a version change as +// a resource removal + addition, which makes Helm delete the resource it just +// created during upgrades. See https://github.com/helm/helm/issues/31768 +func isMatchingInfo(a, b *resource.Info) bool { + return a.Name == b.Name && a.Namespace == b.Namespace && a.Mapping.GroupVersionKind.GroupKind() == b.Mapping.GroupVersionKind.GroupKind() +} diff --git a/vendor/helm.sh/helm/v4/pkg/kube/resource_policy.go b/vendor/helm.sh/helm/v4/pkg/kube/resource_policy.go new file mode 100644 index 00000000000..fb10897854b --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/kube/resource_policy.go @@ -0,0 +1,27 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kube // import "helm.sh/helm/v4/pkg/kube" + +// ResourcePolicyAnno is the annotation name for a resource policy +const ResourcePolicyAnno = "helm.sh/resource-policy" + +// KeepPolicy is the resource policy type for keep +// +// This resource policy type allows resources to skip being deleted +// +// during an uninstallRelease action. +const KeepPolicy = "keep" diff --git a/vendor/helm.sh/helm/v4/pkg/kube/result.go b/vendor/helm.sh/helm/v4/pkg/kube/result.go new file mode 100644 index 00000000000..c3e171c2e4d --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/kube/result.go @@ -0,0 +1,28 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kube + +// Result contains the information of created, updated, and deleted resources +// for various kube API calls along with helper methods for using those +// resources +type Result struct { + Created ResourceList + Updated ResourceList + Deleted ResourceList +} + +// If needed, we can add methods to the Result type for things like diffing diff --git a/vendor/helm.sh/helm/v4/pkg/kube/roundtripper.go b/vendor/helm.sh/helm/v4/pkg/kube/roundtripper.go new file mode 100644 index 00000000000..52cb5bad2ca --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/kube/roundtripper.go @@ -0,0 +1,80 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kube + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "strings" +) + +type RetryingRoundTripper struct { + Wrapped http.RoundTripper +} + +func (rt *RetryingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + return rt.roundTrip(req, 1, nil) +} + +func (rt *RetryingRoundTripper) roundTrip(req *http.Request, retry int, prevResp *http.Response) (*http.Response, error) { + if retry < 0 { + return prevResp, nil + } + resp, rtErr := rt.Wrapped.RoundTrip(req) + if rtErr != nil { + return resp, rtErr + } + if resp.StatusCode < 500 { + return resp, rtErr + } + if resp.Header.Get("content-type") != "application/json" { + return resp, rtErr + } + b, err := io.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return resp, err + } + + var ke kubernetesError + r := bytes.NewReader(b) + err = json.NewDecoder(r).Decode(&ke) + r.Seek(0, io.SeekStart) + resp.Body = io.NopCloser(r) + if err != nil { + return resp, err + } + if ke.Code < 500 { + return resp, nil + } + // Matches messages like "etcdserver: leader changed" + if strings.HasSuffix(ke.Message, "etcdserver: leader changed") { + return rt.roundTrip(req, retry-1, resp) + } + // Matches messages like "rpc error: code = Unknown desc = raft proposal dropped" + if strings.HasSuffix(ke.Message, "raft proposal dropped") { + return rt.roundTrip(req, retry-1, resp) + } + return resp, nil +} + +type kubernetesError struct { + Message string `json:"message"` + Code int `json:"code"` +} diff --git a/vendor/helm.sh/helm/v4/pkg/kube/statuswait.go b/vendor/helm.sh/helm/v4/pkg/kube/statuswait.go new file mode 100644 index 00000000000..28ee84bc286 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/kube/statuswait.go @@ -0,0 +1,292 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kube // import "helm.sh/helm/v4/pkg/kube" + +import ( + "context" + "errors" + "fmt" + "log/slog" + "sort" + "time" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/aggregator" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/collector" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders" + "github.com/fluxcd/cli-utils/pkg/kstatus/status" + "github.com/fluxcd/cli-utils/pkg/kstatus/watcher" + "github.com/fluxcd/cli-utils/pkg/object" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/client-go/dynamic" + watchtools "k8s.io/client-go/tools/watch" + + "helm.sh/helm/v4/internal/logging" + helmStatusReaders "helm.sh/helm/v4/internal/statusreaders" +) + +type statusWaiter struct { + client dynamic.Interface + restMapper meta.RESTMapper + ctx context.Context + watchUntilReadyCtx context.Context + waitCtx context.Context + waitWithJobsCtx context.Context + waitForDeleteCtx context.Context + readers []engine.StatusReader + logging.LogHolder +} + +// DefaultStatusWatcherTimeout is the timeout used by the status waiter when a +// zero timeout is provided. This prevents callers from accidentally passing a +// zero value (which would immediately cancel the context) and getting +// "context deadline exceeded" errors. SDK callers can rely on this default +// when they don't set a timeout. +var DefaultStatusWatcherTimeout = 30 * time.Second + +func alwaysReady(_ *unstructured.Unstructured) (*status.Result, error) { + return &status.Result{ + Status: status.CurrentStatus, + Message: "Resource is current", + }, nil +} + +func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.Duration) error { + if timeout == 0 { + timeout = DefaultStatusWatcherTimeout + } + ctx, cancel := w.contextWithTimeout(w.watchUntilReadyCtx, timeout) + defer cancel() + w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) + sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) + jobSR := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) + podSR := helmStatusReaders.NewCustomPodStatusReader(w.restMapper) + // We don't want to wait on any other resources as watchUntilReady is only for Helm hooks. + // If custom readers are defined they can be used as Helm hooks support any resource. + // We put them in front since the DelegatingStatusReader uses the first reader that matches. + genericSR := statusreaders.NewGenericStatusReader(w.restMapper, alwaysReady) + + sr := &statusreaders.DelegatingStatusReader{ + StatusReaders: append(w.readers, jobSR, podSR, genericSR), + } + sw.StatusReader = sr + return w.wait(ctx, resourceList, sw) +} + +func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) error { + if timeout == 0 { + timeout = DefaultStatusWatcherTimeout + } + ctx, cancel := w.contextWithTimeout(w.waitCtx, timeout) + defer cancel() + w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) + sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) + sw.StatusReader = statusreaders.NewStatusReader(w.restMapper, w.readers...) + return w.wait(ctx, resourceList, sw) +} + +func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Duration) error { + if timeout == 0 { + timeout = DefaultStatusWatcherTimeout + } + ctx, cancel := w.contextWithTimeout(w.waitWithJobsCtx, timeout) + defer cancel() + w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) + sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) + newCustomJobStatusReader := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) + readers := append([]engine.StatusReader(nil), w.readers...) + readers = append(readers, newCustomJobStatusReader) + customSR := statusreaders.NewStatusReader(w.restMapper, readers...) + sw.StatusReader = customSR + return w.wait(ctx, resourceList, sw) +} + +func (w *statusWaiter) WaitForDelete(resourceList ResourceList, timeout time.Duration) error { + if timeout == 0 { + timeout = DefaultStatusWatcherTimeout + } + ctx, cancel := w.contextWithTimeout(w.waitForDeleteCtx, timeout) + defer cancel() + w.Logger().Debug("waiting for resources to be deleted", "count", len(resourceList), "timeout", timeout) + sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) + return w.waitForDelete(ctx, resourceList, sw) +} + +func (w *statusWaiter) waitForDelete(ctx context.Context, resourceList ResourceList, sw watcher.StatusWatcher) error { + cancelCtx, cancel := context.WithCancel(ctx) + defer cancel() + resources := []object.ObjMetadata{} + for _, resource := range resourceList { + obj, err := object.RuntimeToObjMeta(resource.Object) + if err != nil { + return err + } + resources = append(resources, obj) + } + eventCh := sw.Watch(cancelCtx, resources, watcher.Options{ + RESTScopeStrategy: watcher.RESTScopeNamespace, + }) + statusCollector := collector.NewResourceStatusCollector(resources) + done := statusCollector.ListenWithObserver(eventCh, statusObserver(cancel, status.NotFoundStatus, w.Logger())) + <-done + + if statusCollector.Error != nil { + return statusCollector.Error + } + + errs := []error{} + for _, id := range resources { + rs := statusCollector.ResourceStatuses[id] + if rs.Status == status.NotFoundStatus || rs.Status == status.UnknownStatus { + continue + } + errs = append(errs, fmt.Errorf("resource %s/%s/%s still exists. status: %s, message: %s", + rs.Identifier.GroupKind.Kind, rs.Identifier.Namespace, rs.Identifier.Name, rs.Status, rs.Message)) + } + if err := ctx.Err(); err != nil { + errs = append(errs, err) + } + if len(errs) > 0 { + return errors.Join(errs...) + } + return nil +} + +func (w *statusWaiter) wait(ctx context.Context, resourceList ResourceList, sw watcher.StatusWatcher) error { + cancelCtx, cancel := context.WithCancel(ctx) + defer cancel() + resources := []object.ObjMetadata{} + for _, resource := range resourceList { + switch value := AsVersioned(resource).(type) { + case *appsv1.Deployment: + if value.Spec.Paused { + continue + } + } + obj, err := object.RuntimeToObjMeta(resource.Object) + if err != nil { + return err + } + resources = append(resources, obj) + } + + eventCh := sw.Watch(cancelCtx, resources, watcher.Options{ + RESTScopeStrategy: watcher.RESTScopeNamespace, + }) + statusCollector := collector.NewResourceStatusCollector(resources) + done := statusCollector.ListenWithObserver(eventCh, statusObserver(cancel, status.CurrentStatus, w.Logger())) + <-done + + if statusCollector.Error != nil { + return statusCollector.Error + } + + errs := []error{} + for _, id := range resources { + rs := statusCollector.ResourceStatuses[id] + if rs.Status == status.CurrentStatus { + continue + } + errs = append(errs, fmt.Errorf("resource %s/%s/%s not ready. status: %s, message: %s", + rs.Identifier.GroupKind.Kind, rs.Identifier.Namespace, rs.Identifier.Name, rs.Status, rs.Message)) + } + if err := ctx.Err(); err != nil { + errs = append(errs, err) + } + if len(errs) > 0 { + return errors.Join(errs...) + } + return nil +} + +func (w *statusWaiter) contextWithTimeout(methodCtx context.Context, timeout time.Duration) (context.Context, context.CancelFunc) { + if methodCtx == nil { + methodCtx = w.ctx + } + return contextWithTimeout(methodCtx, timeout) +} + +func contextWithTimeout(ctx context.Context, timeout time.Duration) (context.Context, context.CancelFunc) { + if ctx == nil { + ctx = context.Background() + } + return watchtools.ContextWithOptionalTimeout(ctx, timeout) +} + +func statusObserver(cancel context.CancelFunc, desired status.Status, logger *slog.Logger) collector.ObserverFunc { + return func(statusCollector *collector.ResourceStatusCollector, _ event.Event) { + var rss []*event.ResourceStatus + var nonDesiredResources []*event.ResourceStatus + for _, rs := range statusCollector.ResourceStatuses { + if rs == nil { + continue + } + // If a resource is already deleted before waiting has started, it will show as unknown. + // This check ensures we don't wait forever for a resource that is already deleted. + if rs.Status == status.UnknownStatus && desired == status.NotFoundStatus { + continue + } + // Failed is a terminal state. This check ensures we don't wait forever for a resource + // that has already failed, as intervention is required to resolve the failure. + if rs.Status == status.FailedStatus && desired == status.CurrentStatus { + continue + } + rss = append(rss, rs) + if rs.Status != desired { + nonDesiredResources = append(nonDesiredResources, rs) + } + } + + if aggregator.AggregateStatus(rss, desired) == desired { + logger.Debug("all resources achieved desired status", "desiredStatus", desired, "resourceCount", len(rss)) + cancel() + return + } + + if len(nonDesiredResources) > 0 { + // Log a single resource so the user knows what they're waiting for without an overwhelming amount of output + sort.Slice(nonDesiredResources, func(i, j int) bool { + return nonDesiredResources[i].Identifier.Name < nonDesiredResources[j].Identifier.Name + }) + first := nonDesiredResources[0] + logger.Debug("waiting for resource", "namespace", first.Identifier.Namespace, "name", first.Identifier.Name, "kind", first.Identifier.GroupKind.Kind, "expectedStatus", desired, "actualStatus", first.Status) + } + } +} + +type hookOnlyWaiter struct { + sw *statusWaiter +} + +func (w *hookOnlyWaiter) WatchUntilReady(resourceList ResourceList, timeout time.Duration) error { + return w.sw.WatchUntilReady(resourceList, timeout) +} + +func (w *hookOnlyWaiter) Wait(_ ResourceList, _ time.Duration) error { + return nil +} + +func (w *hookOnlyWaiter) WaitWithJobs(_ ResourceList, _ time.Duration) error { + return nil +} + +func (w *hookOnlyWaiter) WaitForDelete(_ ResourceList, _ time.Duration) error { + return nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/kube/wait.go b/vendor/helm.sh/helm/v4/pkg/kube/wait.go new file mode 100644 index 00000000000..9a276a4593f --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/kube/wait.go @@ -0,0 +1,345 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kube // import "helm.sh/helm/v4/pkg/kube" + +import ( + "context" + "fmt" + "log/slog" + "net/http" + "time" + + appsv1 "k8s.io/api/apps/v1" + appsv1beta1 "k8s.io/api/apps/v1beta1" + appsv1beta2 "k8s.io/api/apps/v1beta2" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/cli-runtime/pkg/resource" + "k8s.io/client-go/kubernetes" + cachetools "k8s.io/client-go/tools/cache" + watchtools "k8s.io/client-go/tools/watch" + + "k8s.io/apimachinery/pkg/util/wait" +) + +// legacyWaiter is the legacy implementation of the Waiter interface. This logic was used by default in Helm 3 +// Helm 4 now uses the StatusWaiter implementation instead +type legacyWaiter struct { + c ReadyChecker + kubeClient *kubernetes.Clientset + ctx context.Context +} + +func (hw *legacyWaiter) Wait(resources ResourceList, timeout time.Duration) error { + hw.c = NewReadyChecker(hw.kubeClient, PausedAsReady(true)) + return hw.waitForResources(resources, timeout) +} + +func (hw *legacyWaiter) WaitWithJobs(resources ResourceList, timeout time.Duration) error { + hw.c = NewReadyChecker(hw.kubeClient, PausedAsReady(true), CheckJobs(true)) + return hw.waitForResources(resources, timeout) +} + +// waitForResources polls to get the current status of all pods, PVCs, Services and +// Jobs(optional) until all are ready or a timeout is reached +func (hw *legacyWaiter) waitForResources(created ResourceList, timeout time.Duration) error { + slog.Debug("beginning wait for resources", "count", len(created), "timeout", timeout) + + ctx, cancel := hw.contextWithTimeout(timeout) + defer cancel() + + numberOfErrors := make([]int, len(created)) + for i := range numberOfErrors { + numberOfErrors[i] = 0 + } + + return wait.PollUntilContextCancel(ctx, 2*time.Second, true, func(ctx context.Context) (bool, error) { + waitRetries := 30 + for i, v := range created { + ready, err := hw.c.IsReady(ctx, v) + + if waitRetries > 0 && hw.isRetryableError(err, v) { + numberOfErrors[i]++ + if numberOfErrors[i] > waitRetries { + slog.Debug("max number of retries reached", "resource", v.Name, "retries", numberOfErrors[i]) + return false, err + } + slog.Debug("retrying resource readiness", "resource", v.Name, "currentRetries", numberOfErrors[i]-1, "maxRetries", waitRetries) + return false, nil + } + numberOfErrors[i] = 0 + if !ready { + return false, err + } + } + return true, nil + }) +} + +func (hw *legacyWaiter) isRetryableError(err error, resource *resource.Info) bool { + if err == nil { + return false + } + slog.Debug( + "error received when checking resource status", + slog.String("resource", resource.Name), + slog.Any("error", err), + ) + if ev, ok := err.(*apierrors.StatusError); ok { + statusCode := ev.Status().Code + retryable := hw.isRetryableHTTPStatusCode(statusCode) + slog.Debug( + "status code received", + slog.String("resource", resource.Name), + slog.Int("statusCode", int(statusCode)), + slog.Bool("retryable", retryable), + ) + return retryable + } + slog.Debug("retryable error assumed", "resource", resource.Name) + return true +} + +func (hw *legacyWaiter) isRetryableHTTPStatusCode(httpStatusCode int32) bool { + return httpStatusCode == 0 || httpStatusCode == http.StatusTooManyRequests || (httpStatusCode >= 500 && httpStatusCode != http.StatusNotImplemented) +} + +// WaitForDelete polls to check if all the resources are deleted or a timeout is reached +func (hw *legacyWaiter) WaitForDelete(deleted ResourceList, timeout time.Duration) error { + slog.Debug("beginning wait for resources to be deleted", "count", len(deleted), "timeout", timeout) + + startTime := time.Now() + ctx, cancel := hw.contextWithTimeout(timeout) + defer cancel() + + err := wait.PollUntilContextCancel(ctx, 2*time.Second, true, func(_ context.Context) (bool, error) { + for _, v := range deleted { + err := v.Get() + if err == nil || !apierrors.IsNotFound(err) { + return false, err + } + } + return true, nil + }) + + elapsed := time.Since(startTime).Round(time.Second) + if err != nil { + slog.Debug("wait for resources failed", slog.Duration("elapsed", elapsed), slog.Any("error", err)) + } else { + slog.Debug("wait for resources succeeded", slog.Duration("elapsed", elapsed)) + } + + return err +} + +// SelectorsForObject returns the pod label selector for a given object +// +// Modified version of https://github.com/kubernetes/kubernetes/blob/v1.14.1/pkg/kubectl/polymorphichelpers/helpers.go#L84 +func SelectorsForObject(object runtime.Object) (selector labels.Selector, err error) { + switch t := object.(type) { + case *extensionsv1beta1.ReplicaSet: + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + case *appsv1.ReplicaSet: + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + case *appsv1beta2.ReplicaSet: + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + case *corev1.ReplicationController: + selector = labels.SelectorFromSet(t.Spec.Selector) + case *appsv1.StatefulSet: + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + case *appsv1beta1.StatefulSet: + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + case *appsv1beta2.StatefulSet: + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + case *extensionsv1beta1.DaemonSet: + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + case *appsv1.DaemonSet: + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + case *appsv1beta2.DaemonSet: + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + case *extensionsv1beta1.Deployment: + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + case *appsv1.Deployment: + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + case *appsv1beta1.Deployment: + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + case *appsv1beta2.Deployment: + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + case *batchv1.Job: + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + case *corev1.Service: + if len(t.Spec.Selector) == 0 { + return nil, fmt.Errorf("invalid service '%s': Service is defined without a selector", t.Name) + } + selector = labels.SelectorFromSet(t.Spec.Selector) + + default: + return nil, fmt.Errorf("selector for %T not implemented", object) + } + + if err != nil { + return selector, fmt.Errorf("invalid label selector: %w", err) + } + + return selector, nil +} + +func (hw *legacyWaiter) watchTimeout(t time.Duration) func(*resource.Info) error { + return func(info *resource.Info) error { + return hw.watchUntilReady(t, info) + } +} + +// WatchUntilReady watches the resources given and waits until it is ready. +// +// This method is mainly for hook implementations. It watches for a resource to +// hit a particular milestone. The milestone depends on the Kind. +// +// For most kinds, it checks to see if the resource is marked as Added or Modified +// by the Kubernetes event stream. For some kinds, it does more: +// +// - Jobs: A job is marked "Ready" when it has successfully completed. This is +// ascertained by watching the Status fields in a job's output. +// - Pods: A pod is marked "Ready" when it has successfully completed. This is +// ascertained by watching the status.phase field in a pod's output. +// +// Handling for other kinds will be added as necessary. +func (hw *legacyWaiter) WatchUntilReady(resources ResourceList, timeout time.Duration) error { + // For jobs, there's also the option to do poll c.Jobs(namespace).Get(): + // https://github.com/adamreese/kubernetes/blob/master/test/e2e/job.go#L291-L300 + return perform(resources, hw.watchTimeout(timeout)) +} + +func (hw *legacyWaiter) watchUntilReady(timeout time.Duration, info *resource.Info) error { + kind := info.Mapping.GroupVersionKind.Kind + switch kind { + case "Job", "Pod": + default: + return nil + } + + slog.Debug("watching for resource changes", "kind", kind, "resource", info.Name, "timeout", timeout) + + // Use a selector on the name of the resource. This should be unique for the + // given version and kind + selector, err := fields.ParseSelector(fmt.Sprintf("metadata.name=%s", info.Name)) + if err != nil { + return err + } + lw := cachetools.NewListWatchFromClient(info.Client, info.Mapping.Resource.Resource, info.Namespace, selector) + + // What we watch for depends on the Kind. + // - For a Job, we watch for completion. + // - For all else, we watch until Ready. + // In the future, we might want to add some special logic for types + // like Ingress, Volume, etc. + + ctx, cancel := hw.contextWithTimeout(timeout) + defer cancel() + _, err = watchtools.UntilWithSync(ctx, lw, &unstructured.Unstructured{}, nil, func(e watch.Event) (bool, error) { + // Make sure the incoming object is versioned as we use unstructured + // objects when we build manifests + obj := convertWithMapper(e.Object, info.Mapping) + switch e.Type { + case watch.Added, watch.Modified: + // For things like a secret or a config map, this is the best indicator + // we get. We care mostly about jobs, where what we want to see is + // the status go into a good state. For other types, like ReplicaSet + // we don't really do anything to support these as hooks. + slog.Debug("add/modify event received", "resource", info.Name, "eventType", e.Type) + + switch kind { + case "Job": + return hw.waitForJob(obj, info.Name) + case "Pod": + return hw.waitForPodSuccess(obj, info.Name) + } + return true, nil + case watch.Deleted: + slog.Debug("deleted event received", "resource", info.Name) + return true, nil + case watch.Error: + // Handle error and return with an error. + slog.Error("error event received", "resource", info.Name) + return true, fmt.Errorf("failed to deploy %s", info.Name) + default: + return false, nil + } + }) + return err +} + +// waitForJob is a helper that waits for a job to complete. +// +// This operates on an event returned from a watcher. +func (hw *legacyWaiter) waitForJob(obj runtime.Object, name string) (bool, error) { + o, ok := obj.(*batchv1.Job) + if !ok { + return true, fmt.Errorf("expected %s to be a *batch.Job, got %T", name, obj) + } + + for _, c := range o.Status.Conditions { + if c.Type == batchv1.JobComplete && c.Status == "True" { + return true, nil + } else if c.Type == batchv1.JobFailed && c.Status == "True" { + slog.Error("job failed", "job", name, "reason", c.Reason) + return true, fmt.Errorf("job %s failed: %s", name, c.Reason) + } + } + + slog.Debug("job status update", "job", name, "active", o.Status.Active, "failed", o.Status.Failed, "succeeded", o.Status.Succeeded) + return false, nil +} + +// waitForPodSuccess is a helper that waits for a pod to complete. +// +// This operates on an event returned from a watcher. +func (hw *legacyWaiter) waitForPodSuccess(obj runtime.Object, name string) (bool, error) { + o, ok := obj.(*corev1.Pod) + if !ok { + return true, fmt.Errorf("expected %s to be a *v1.Pod, got %T", name, obj) + } + + switch o.Status.Phase { + case corev1.PodSucceeded: + slog.Debug("pod succeeded", "pod", o.Name) + return true, nil + case corev1.PodFailed: + slog.Error("pod failed", "pod", o.Name) + return true, fmt.Errorf("pod %s failed", o.Name) + case corev1.PodPending: + slog.Debug("pod pending", "pod", o.Name) + case corev1.PodRunning: + slog.Debug("pod running", "pod", o.Name) + case corev1.PodUnknown: + slog.Debug("pod unknown", "pod", o.Name) + } + + return false, nil +} + +func (hw *legacyWaiter) contextWithTimeout(timeout time.Duration) (context.Context, context.CancelFunc) { + return contextWithTimeout(hw.ctx, timeout) +} diff --git a/vendor/helm.sh/helm/v4/pkg/postrenderer/postrenderer.go b/vendor/helm.sh/helm/v4/pkg/postrenderer/postrenderer.go new file mode 100644 index 00000000000..55e6d3adf19 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/postrenderer/postrenderer.go @@ -0,0 +1,84 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package postrenderer + +import ( + "bytes" + "context" + "fmt" + "path/filepath" + + "helm.sh/helm/v4/internal/plugin/schema" + + "helm.sh/helm/v4/internal/plugin" + "helm.sh/helm/v4/pkg/cli" +) + +// PostRenderer is an interface different plugin runtimes +// it may be also be used without the factory for custom post-renderers +type PostRenderer interface { + // Run expects a single buffer filled with Helm rendered manifests. It + // expects the modified results to be returned on a separate buffer or an + // error if there was an issue or failure while running the post render step + Run(renderedManifests *bytes.Buffer) (modifiedManifests *bytes.Buffer, err error) +} + +// NewPostRendererPlugin creates a PostRenderer that uses the plugin's Runtime +func NewPostRendererPlugin(settings *cli.EnvSettings, pluginName string, args ...string) (PostRenderer, error) { + descriptor := plugin.Descriptor{ + Name: pluginName, + Type: "postrenderer/v1", + } + p, err := plugin.FindPlugin(filepath.SplitList(settings.PluginsDirectory), descriptor) + if err != nil { + return nil, err + } + + return &postRendererPlugin{ + plugin: p, + args: args, + settings: settings, + }, nil +} + +// postRendererPlugin implements PostRenderer by delegating to the plugin's Runtime +type postRendererPlugin struct { + plugin plugin.Plugin + args []string + settings *cli.EnvSettings +} + +// Run implements PostRenderer by using the plugin's Runtime +func (r *postRendererPlugin) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) { + input := &plugin.Input{ + Message: schema.InputMessagePostRendererV1{ + ExtraArgs: r.args, + Manifests: renderedManifests, + }, + } + output, err := r.plugin.Invoke(context.Background(), input) + if err != nil { + return nil, fmt.Errorf("failed to invoke post-renderer plugin %q: %w", r.plugin.Metadata().Name, err) + } + + outputMessage := output.Message.(schema.OutputMessagePostRendererV1) + + // If the binary returned almost nothing, it's likely that it didn't + // successfully render anything + if len(bytes.TrimSpace(outputMessage.Manifests.Bytes())) == 0 { + return nil, fmt.Errorf("post-renderer %q produced empty output", r.plugin.Metadata().Name) + } + + return outputMessage.Manifests, nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/provenance/doc.go b/vendor/helm.sh/helm/v4/pkg/provenance/doc.go new file mode 100644 index 00000000000..dd14568d94a --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/provenance/doc.go @@ -0,0 +1,38 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package provenance provides tools for establishing the authenticity of packages. + +In Helm, provenance is established via several factors. The primary factor is the +cryptographic signature of a package. Package authors may sign packages, which in turn +provide the necessary metadata to ensure the integrity of the package file, the +metadata, and the referenced Docker images. + +A provenance file is clear-signed. This provides cryptographic verification that +a particular block of information (metadata, archive file, images) have not +been tampered with or altered. To learn more, read the GnuPG documentation on +clear signatures: +https://www.gnupg.org/gph/en/manual/x135.html + +The cryptography used by Helm should be compatible with OpenGPG. For example, +you should be able to verify a signature by importing the desired public key +and using `gpg --verify`, `keybase pgp verify`, or similar: + + $ gpg --verify some.sig + gpg: Signature made Mon Jul 25 17:23:44 2016 MDT using RSA key ID 1FC18762 + gpg: Good signature from "Helm Testing (This key should only be used for testing. DO NOT TRUST.) " [ultimate] +*/ +package provenance // import "helm.sh/helm/v4/pkg/provenance" diff --git a/vendor/helm.sh/helm/v4/pkg/provenance/sign.go b/vendor/helm.sh/helm/v4/pkg/provenance/sign.go new file mode 100644 index 00000000000..57af1ad428a --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/provenance/sign.go @@ -0,0 +1,394 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package provenance + +import ( + "bytes" + "crypto" + "encoding/hex" + "errors" + "fmt" + "io" + "os" + "strings" + + "github.com/ProtonMail/go-crypto/openpgp" //nolint + "github.com/ProtonMail/go-crypto/openpgp/clearsign" //nolint + "github.com/ProtonMail/go-crypto/openpgp/packet" //nolint + "sigs.k8s.io/yaml" +) + +var defaultPGPConfig = packet.Config{ + DefaultHash: crypto.SHA512, +} + +// SumCollection represents a collection of file and image checksums. +// +// Files are of the form: +// +// FILENAME: "sha256:SUM" +// +// Images are of the form: +// +// "IMAGE:TAG": "sha256:SUM" +// +// Docker optionally supports sha512, and if this is the case, the hash marker +// will be 'sha512' instead of 'sha256'. +type SumCollection struct { + Files map[string]string `json:"files"` + Images map[string]string `json:"images,omitempty"` +} + +// Verification contains information about a verification operation. +type Verification struct { + // SignedBy contains the entity that signed a package. + SignedBy *openpgp.Entity + // FileHash is the hash, prepended with the scheme, for the file that was verified. + FileHash string + // FileName is the name of the file that FileHash verifies. + FileName string +} + +// Signatory signs things. +// +// Signatories can be constructed from a PGP private key file using NewFromFiles, +// or they can be constructed manually by setting the Entity to a valid +// PGP entity. +// +// The same Signatory can be used to sign or validate multiple packages. +type Signatory struct { + // The signatory for this instance of Helm. This is used for signing. + Entity *openpgp.Entity + // The keyring for this instance of Helm. This is used for verification. + KeyRing openpgp.EntityList +} + +// NewFromFiles constructs a new Signatory from the PGP key in the given filename. +// +// This will emit an error if it cannot find a valid GPG keyfile (entity) at the +// given location. +// +// Note that the keyfile may have just a public key, just a private key, or +// both. The Signatory methods may have different requirements of the keys. For +// example, ClearSign must have a valid `openpgp.Entity.PrivateKey` before it +// can sign something. +func NewFromFiles(keyfile, keyringfile string) (*Signatory, error) { + e, err := loadKey(keyfile) + if err != nil { + return nil, err + } + + ring, err := loadKeyRing(keyringfile) + if err != nil { + return nil, err + } + + return &Signatory{ + Entity: e, + KeyRing: ring, + }, nil +} + +// NewFromKeyring reads a keyring file and creates a Signatory. +// +// If id is not the empty string, this will also try to find an Entity in the +// keyring whose name matches, and set that as the signing entity. It will return +// an error if the id is not empty and also not found. +func NewFromKeyring(keyringfile, id string) (*Signatory, error) { + ring, err := loadKeyRing(keyringfile) + if err != nil { + return nil, err + } + + s := &Signatory{KeyRing: ring} + + // If the ID is empty, we can return now. + if id == "" { + return s, nil + } + + // We're gonna go all GnuPG on this and look for a string that _contains_. If + // two or more keys contain the string and none are a direct match, we error + // out. + var candidate *openpgp.Entity + vague := false + for _, e := range ring { + for n := range e.Identities { + if n == id { + s.Entity = e + return s, nil + } + if strings.Contains(n, id) { + if candidate != nil { + vague = true + } + candidate = e + } + } + } + if vague { + return s, fmt.Errorf("more than one key contain the id %q", id) + } + + s.Entity = candidate + return s, nil +} + +// PassphraseFetcher returns a passphrase for decrypting keys. +// +// This is used as a callback to read a passphrase from some other location. The +// given name is the Name field on the key, typically of the form: +// +// USER_NAME (COMMENT) +type PassphraseFetcher func(name string) ([]byte, error) + +// DecryptKey decrypts a private key in the Signatory. +// +// If the key is not encrypted, this will return without error. +// +// If the key does not exist, this will return an error. +// +// If the key exists, but cannot be unlocked with the passphrase returned by +// the PassphraseFetcher, this will return an error. +// +// If the key is successfully unlocked, it will return nil. +func (s *Signatory) DecryptKey(fn PassphraseFetcher) error { + if s.Entity == nil { + return errors.New("private key not found") + } else if s.Entity.PrivateKey == nil { + return errors.New("provided key is not a private key. Try providing a keyring with secret keys") + } + + // Nothing else to do if key is not encrypted. + if !s.Entity.PrivateKey.Encrypted { + return nil + } + + fname := "Unknown" + for i := range s.Entity.Identities { + if i != "" { + fname = i + break + } + } + + p, err := fn(fname) + if err != nil { + return err + } + + return s.Entity.PrivateKey.Decrypt(p) +} + +// ClearSign signs package data with the given key and pre-marshalled metadata. +// +// This is the core signing method that works with data in memory. +// The Signatory must have a valid Entity.PrivateKey for this to work. +func (s *Signatory) ClearSign(archiveData []byte, filename string, metadataBytes []byte) (string, error) { + if s.Entity == nil { + return "", errors.New("private key not found") + } else if s.Entity.PrivateKey == nil { + return "", errors.New("provided key is not a private key. Try providing a keyring with secret keys") + } + + out := bytes.NewBuffer(nil) + + b, err := messageBlock(archiveData, filename, metadataBytes) + if err != nil { + return "", err + } + + // Sign the buffer + w, err := clearsign.Encode(out, s.Entity.PrivateKey, &defaultPGPConfig) + if err != nil { + return "", err + } + + _, err = io.Copy(w, b) + + if err != nil { + // NB: We intentionally don't call `w.Close()` here! `w.Close()` is the method which + // actually does the PGP signing, and therefore is the part which uses the private key. + // In other words, if we call Close here, there's a risk that there's an attempt to use the + // private key to sign garbage data (since we know that io.Copy failed, `w` won't contain + // anything useful). + return "", fmt.Errorf("failed to write to clearsign encoder: %w", err) + } + + err = w.Close() + if err != nil { + return "", fmt.Errorf("failed to either sign or armor message block: %w", err) + } + + return out.String(), nil +} + +// Verify checks a signature and verifies that it is legit for package data. +// This is the core verification method that works with data in memory. +func (s *Signatory) Verify(archiveData, provData []byte, filename string) (*Verification, error) { + ver := &Verification{} + + // First verify the signature + block, _ := clearsign.Decode(provData) + if block == nil { + return ver, errors.New("signature block not found") + } + + by, err := s.verifySignature(block) + if err != nil { + return ver, err + } + ver.SignedBy = by + + // Second, verify the hash of the data. + sum, err := Digest(bytes.NewBuffer(archiveData)) + if err != nil { + return ver, err + } + sums, err := parseMessageBlock(block.Plaintext) + if err != nil { + return ver, err + } + + sum = "sha256:" + sum + if sha, ok := sums.Files[filename]; !ok { + return ver, fmt.Errorf("provenance does not contain a SHA for a file named %q", filename) + } else if sha != sum { + return ver, fmt.Errorf("sha256 sum does not match for %s: %q != %q", filename, sha, sum) + } + ver.FileHash = sum + ver.FileName = filename + + // TODO: when image signing is added, verify that here. + + return ver, nil +} + +// verifySignature verifies that the given block is validly signed, and returns the signer. +func (s *Signatory) verifySignature(block *clearsign.Block) (*openpgp.Entity, error) { + return openpgp.CheckDetachedSignature( + s.KeyRing, + bytes.NewReader(block.Bytes), + block.ArmoredSignature.Body, + &defaultPGPConfig, + ) +} + +// messageBlock creates a message block from archive data and pre-marshalled metadata +func messageBlock(archiveData []byte, filename string, metadataBytes []byte) (*bytes.Buffer, error) { + // Checksum the archive data + chash, err := Digest(bytes.NewBuffer(archiveData)) + if err != nil { + return nil, err + } + + sums := &SumCollection{ + Files: map[string]string{ + filename: "sha256:" + chash, + }, + } + + // Buffer the metadata + checksums YAML file + // FIXME: YAML uses ---\n as a file start indicator, but this is not legal in a PGP + // clearsign block. So we use ...\n, which is the YAML document end marker. + // http://yaml.org/spec/1.2/spec.html#id2800168 + b := bytes.NewBuffer(metadataBytes) + b.WriteString("\n...\n") + + data, err := yaml.Marshal(sums) + if err != nil { + return nil, err + } + b.Write(data) + + return b, nil +} + +// parseMessageBlock parses a message block and returns only checksums (metadata ignored like upstream) +func parseMessageBlock(data []byte) (*SumCollection, error) { + sc := &SumCollection{} + + // We ignore metadata, just like upstream - only need checksums for verification + if err := ParseMessageBlock(data, nil, sc); err != nil { + return sc, err + } + return sc, nil +} + +// ParseMessageBlock parses a message block containing metadata and checksums. +// +// This is the generic version that can work with any metadata type. +// The metadata parameter should be a pointer to a struct that can be unmarshaled from YAML. +func ParseMessageBlock(data []byte, metadata interface{}, sums *SumCollection) error { + parts := bytes.Split(data, []byte("\n...\n")) + if len(parts) < 2 { + return errors.New("message block must have at least two parts") + } + + if metadata != nil { + if err := yaml.Unmarshal(parts[0], metadata); err != nil { + return err + } + } + return yaml.Unmarshal(parts[1], sums) +} + +// loadKey loads a GPG key found at a particular path. +func loadKey(keypath string) (*openpgp.Entity, error) { + f, err := os.Open(keypath) + if err != nil { + return nil, err + } + defer f.Close() + + pr := packet.NewReader(f) + return openpgp.ReadEntity(pr) +} + +func loadKeyRing(ringpath string) (openpgp.EntityList, error) { + f, err := os.Open(ringpath) + if err != nil { + return nil, err + } + defer f.Close() + return openpgp.ReadKeyRing(f) +} + +// DigestFile calculates a SHA256 hash (like Docker) for a given file. +// +// It takes the path to the archive file, and returns a string representation of +// the SHA256 sum. +// +// This function can be used to generate a sum of any package archive file. +func DigestFile(filename string) (string, error) { + f, err := os.Open(filename) + if err != nil { + return "", err + } + defer f.Close() + return Digest(f) +} + +// Digest hashes a reader and returns a SHA256 digest. +// +// Helm uses SHA256 as its default hash for all non-cryptographic applications. +func Digest(in io.Reader) (string, error) { + hash := crypto.SHA256.New() + if _, err := io.Copy(hash, in); err != nil { + return "", nil + } + return hex.EncodeToString(hash.Sum(nil)), nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/pusher/doc.go b/vendor/helm.sh/helm/v4/pkg/pusher/doc.go new file mode 100644 index 00000000000..df89ab112b5 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/pusher/doc.go @@ -0,0 +1,21 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package pusher provides a generalized tool for uploading data by scheme. +This provides a method by which the plugin system can load arbitrary protocol +handlers based upon a URL scheme. +*/ +package pusher diff --git a/vendor/helm.sh/helm/v4/pkg/pusher/ocipusher.go b/vendor/helm.sh/helm/v4/pkg/pusher/ocipusher.go new file mode 100644 index 00000000000..f03188391e9 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/pusher/ocipusher.go @@ -0,0 +1,160 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package pusher + +import ( + "errors" + "fmt" + "io/fs" + "net" + "net/http" + "os" + "path" + "strings" + "time" + + "helm.sh/helm/v4/internal/tlsutil" + "helm.sh/helm/v4/pkg/chart/v2/loader" + "helm.sh/helm/v4/pkg/registry" +) + +// OCIPusher is the default OCI backend handler +type OCIPusher struct { + opts options +} + +// Push performs a Push from repo.Pusher. +func (pusher *OCIPusher) Push(chartRef, href string, options ...Option) error { + for _, opt := range options { + opt(&pusher.opts) + } + return pusher.push(chartRef, href) +} + +func (pusher *OCIPusher) push(chartRef, href string) error { + stat, err := os.Stat(chartRef) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("%s: no such file", chartRef) + } + return err + } + if stat.IsDir() { + return errors.New("cannot push directory, must provide chart archive (.tgz)") + } + + meta, err := loader.Load(chartRef) + if err != nil { + return err + } + + client := pusher.opts.registryClient + if client == nil { + c, err := pusher.newRegistryClient() + if err != nil { + return err + } + client = c + } + + chartBytes, err := os.ReadFile(chartRef) + if err != nil { + return err + } + + var pushOpts []registry.PushOption + provRef := fmt.Sprintf("%s.prov", chartRef) + if _, err := os.Stat(provRef); err == nil { + provBytes, err := os.ReadFile(provRef) + if err != nil { + return err + } + pushOpts = append(pushOpts, registry.PushOptProvData(provBytes)) + } + + ref := fmt.Sprintf("%s:%s", + path.Join(strings.TrimPrefix(href, fmt.Sprintf("%s://", registry.OCIScheme)), meta.Metadata.Name), + meta.Metadata.Version) + + // The time the chart was "created" is semantically the time the chart archive file was last written(modified) + chartArchiveFileCreatedTime := stat.ModTime() + pushOpts = append(pushOpts, registry.PushOptCreationTime(chartArchiveFileCreatedTime.Format(time.RFC3339))) + + _, err = client.Push(chartBytes, ref, pushOpts...) + return err +} + +// NewOCIPusher constructs a valid OCI client as a Pusher +func NewOCIPusher(ops ...Option) (Pusher, error) { + var client OCIPusher + + for _, opt := range ops { + opt(&client.opts) + } + + return &client, nil +} + +func (pusher *OCIPusher) newRegistryClient() (*registry.Client, error) { + if (pusher.opts.certFile != "" && pusher.opts.keyFile != "") || pusher.opts.caFile != "" || pusher.opts.insecureSkipTLSVerify { + tlsConf, err := tlsutil.NewTLSConfig( + tlsutil.WithInsecureSkipVerify(pusher.opts.insecureSkipTLSVerify), + tlsutil.WithCertKeyPairFiles(pusher.opts.certFile, pusher.opts.keyFile), + tlsutil.WithCAFile(pusher.opts.caFile), + ) + if err != nil { + return nil, fmt.Errorf("can't create TLS config for client: %w", err) + } + + registryClient, err := registry.NewClient( + registry.ClientOptHTTPClient(&http.Client{ + // From https://github.com/google/go-containerregistry/blob/31786c6cbb82d6ec4fb8eb79cd9387905130534e/pkg/v1/remote/options.go#L87 + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + // By default we wrap the transport in retries, so reduce the + // default dial timeout to 5s to avoid 5x 30s of connection + // timeouts when doing the "ping" on certain http registries. + Timeout: 5 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + TLSClientConfig: tlsConf, + }, + }), + registry.ClientOptEnableCache(true), + ) + if err != nil { + return nil, err + } + return registryClient, nil + } + + opts := []registry.ClientOption{registry.ClientOptEnableCache(true)} + if pusher.opts.plainHTTP { + opts = append(opts, registry.ClientOptPlainHTTP()) + } + + registryClient, err := registry.NewClient(opts...) + if err != nil { + return nil, err + } + return registryClient, nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/pusher/pusher.go b/vendor/helm.sh/helm/v4/pkg/pusher/pusher.go new file mode 100644 index 00000000000..8ce78b011ea --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/pusher/pusher.go @@ -0,0 +1,118 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package pusher + +import ( + "fmt" + "slices" + + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/registry" +) + +// options are generic parameters to be provided to the pusher during instantiation. +// +// Pushers may or may not ignore these parameters as they are passed in. +type options struct { + registryClient *registry.Client + certFile string + keyFile string + caFile string + insecureSkipTLSVerify bool + plainHTTP bool +} + +// Option allows specifying various settings configurable by the user for overriding the defaults +// used when performing Push operations with the Pusher. +type Option func(*options) + +// WithRegistryClient sets the registryClient option. +func WithRegistryClient(client *registry.Client) Option { + return func(opts *options) { + opts.registryClient = client + } +} + +// WithTLSClientConfig sets the client auth with the provided credentials. +func WithTLSClientConfig(certFile, keyFile, caFile string) Option { + return func(opts *options) { + opts.certFile = certFile + opts.keyFile = keyFile + opts.caFile = caFile + } +} + +// WithInsecureSkipTLSVerify determines if a TLS Certificate will be checked +func WithInsecureSkipTLSVerify(insecureSkipTLSVerify bool) Option { + return func(opts *options) { + opts.insecureSkipTLSVerify = insecureSkipTLSVerify + } +} + +func WithPlainHTTP(plainHTTP bool) Option { + return func(opts *options) { + opts.plainHTTP = plainHTTP + } +} + +// Pusher is an interface to support upload to the specified URL. +type Pusher interface { + // Push file content by url string + Push(chartRef, url string, options ...Option) error +} + +// Constructor is the function for every pusher which creates a specific instance +// according to the configuration +type Constructor func(options ...Option) (Pusher, error) + +// Provider represents any pusher and the schemes that it supports. +type Provider struct { + Schemes []string + New Constructor +} + +// Provides returns true if the given scheme is supported by this Provider. +func (p Provider) Provides(scheme string) bool { + return slices.Contains(p.Schemes, scheme) +} + +// Providers is a collection of Provider objects. +type Providers []Provider + +// ByScheme returns a Provider that handles the given scheme. +// +// If no provider handles this scheme, this will return an error. +func (p Providers) ByScheme(scheme string) (Pusher, error) { + for _, pp := range p { + if pp.Provides(scheme) { + return pp.New() + } + } + return nil, fmt.Errorf("scheme %q not supported", scheme) +} + +var ociProvider = Provider{ + Schemes: []string{registry.OCIScheme}, + New: NewOCIPusher, +} + +// All finds all of the registered pushers as a list of Provider instances. +// Currently, just the built-in pushers are collected. +func All(_ *cli.EnvSettings) Providers { + result := Providers{ociProvider} + return result +} diff --git a/vendor/helm.sh/helm/v4/pkg/registry/chart.go b/vendor/helm.sh/helm/v4/pkg/registry/chart.go new file mode 100644 index 00000000000..b00fc616d85 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/registry/chart.go @@ -0,0 +1,124 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package registry // import "helm.sh/helm/v4/pkg/registry" + +import ( + "bytes" + "strings" + "time" + + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +var immutableOciAnnotations = []string{ + ocispec.AnnotationVersion, + ocispec.AnnotationTitle, +} + +// extractChartMeta is used to extract a chart metadata from a byte array +func extractChartMeta(chartData []byte) (*chart.Metadata, error) { + ch, err := loader.LoadArchive(bytes.NewReader(chartData)) + if err != nil { + return nil, err + } + return ch.Metadata, nil +} + +// generateOCIAnnotations will generate OCI annotations to include within the OCI manifest +func generateOCIAnnotations(meta *chart.Metadata, creationTime string) map[string]string { + + // Get annotations from Chart attributes + ociAnnotations := generateChartOCIAnnotations(meta, creationTime) + + // Copy Chart annotations +annotations: + for chartAnnotationKey, chartAnnotationValue := range meta.Annotations { + + // Avoid overriding key properties + for _, immutableOciKey := range immutableOciAnnotations { + if immutableOciKey == chartAnnotationKey { + continue annotations + } + } + + // Add chart annotation + ociAnnotations[chartAnnotationKey] = chartAnnotationValue + } + + return ociAnnotations +} + +// generateChartOCIAnnotations will generate OCI annotations from the provided chart +func generateChartOCIAnnotations(meta *chart.Metadata, creationTime string) map[string]string { + chartOCIAnnotations := map[string]string{} + + chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationDescription, meta.Description) + chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationTitle, meta.Name) + chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationVersion, meta.Version) + chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationURL, meta.Home) + + if len(creationTime) == 0 { + creationTime = time.Now().UTC().Format(time.RFC3339) + } + + chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationCreated, creationTime) + + if len(meta.Sources) > 0 { + chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationSource, meta.Sources[0]) + } + + if len(meta.Maintainers) > 0 { + var maintainerSb strings.Builder + + for maintainerIdx, maintainer := range meta.Maintainers { + + if len(maintainer.Name) > 0 { + maintainerSb.WriteString(maintainer.Name) + } + + if len(maintainer.Email) > 0 { + maintainerSb.WriteString(" (") + maintainerSb.WriteString(maintainer.Email) + maintainerSb.WriteString(")") + } + + if maintainerIdx < len(meta.Maintainers)-1 { + maintainerSb.WriteString(", ") + } + + } + + chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationAuthors, maintainerSb.String()) + + } + + return chartOCIAnnotations +} + +// addToMap takes an existing map and adds an item if the value is not empty +func addToMap(inputMap map[string]string, newKey string, newValue string) map[string]string { + + // Add item to map if its + if len(strings.TrimSpace(newValue)) > 0 { + inputMap[newKey] = newValue + } + + return inputMap +} diff --git a/vendor/helm.sh/helm/v4/pkg/registry/client.go b/vendor/helm.sh/helm/v4/pkg/registry/client.go new file mode 100644 index 00000000000..72ba0b540f2 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/registry/client.go @@ -0,0 +1,928 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package registry // import "helm.sh/helm/v4/pkg/registry" + +import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/json" + "errors" + "fmt" + "io" + "log/slog" + "net/http" + "net/url" + "os" + "sort" + "strings" + + "github.com/Masterminds/semver/v3" + "github.com/opencontainers/image-spec/specs-go" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "oras.land/oras-go/v2" + "oras.land/oras-go/v2/content/memory" + "oras.land/oras-go/v2/registry" + "oras.land/oras-go/v2/registry/remote" + "oras.land/oras-go/v2/registry/remote/auth" + "oras.land/oras-go/v2/registry/remote/credentials" + "oras.land/oras-go/v2/registry/remote/retry" + + "helm.sh/helm/v4/internal/version" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/helmpath" +) + +// See https://github.com/helm/helm/issues/10166 +const registryUnderscoreMessage = ` +OCI artifact references (e.g. tags) do not support the plus sign (+). To support +storing semantic versions, Helm adopts the convention of changing plus (+) to +an underscore (_) in chart version tags when pushing to a registry and back to +a plus (+) when pulling from a registry.` + +type ( + // RemoteClient shadows the ORAS remote.Client interface + // (hiding the ORAS type from Helm client visibility) + // https://pkg.go.dev/oras.land/oras-go/pkg/registry/remote#Client + RemoteClient interface { + Do(req *http.Request) (*http.Response, error) + } + + // Client works with OCI-compliant registries + Client struct { + debug bool + enableCache bool + // path to repository config file e.g. ~/.docker/config.json + credentialsFile string + username string + password string + out io.Writer + authorizer *auth.Client + registryAuthorizer RemoteClient + credentialsStore credentials.Store + httpClient *http.Client + plainHTTP bool + } + + // ClientOption allows specifying various settings configurable by the user for overriding the defaults + // used when creating a new default client + // TODO(TerryHowe): ClientOption should return error in v5 + ClientOption func(*Client) +) + +// NewClient returns a new registry client with config +func NewClient(options ...ClientOption) (*Client, error) { + client := &Client{ + out: io.Discard, + } + for _, option := range options { + option(client) + } + if client.credentialsFile == "" { + client.credentialsFile = helmpath.ConfigPath(CredentialsFileBasename) + } + if client.httpClient == nil { + client.httpClient = &http.Client{ + Transport: NewTransport(client.debug), + } + } + + storeOptions := credentials.StoreOptions{ + AllowPlaintextPut: true, + DetectDefaultNativeStore: true, + } + store, err := credentials.NewStore(client.credentialsFile, storeOptions) + if err != nil { + return nil, err + } + dockerStore, err := credentials.NewStoreFromDocker(storeOptions) + if err != nil { + // should only fail if user home directory can't be determined + client.credentialsStore = store + } else { + // use Helm credentials with fallback to Docker + client.credentialsStore = credentials.NewStoreWithFallbacks(store, dockerStore) + } + + if client.authorizer == nil { + authorizer := auth.Client{ + Client: client.httpClient, + } + authorizer.SetUserAgent(version.GetUserAgent()) + + if client.username != "" && client.password != "" { + authorizer.Credential = func(_ context.Context, _ string) (auth.Credential, error) { + return auth.Credential{Username: client.username, Password: client.password}, nil + } + } else { + authorizer.Credential = credentials.Credential(client.credentialsStore) + } + + if client.enableCache { + authorizer.Cache = auth.NewCache() + } + client.authorizer = &authorizer + } + + return client, nil +} + +// Generic returns a GenericClient for low-level OCI operations +func (c *Client) Generic() *GenericClient { + return NewGenericClient(c) +} + +// ClientOptDebug returns a function that sets the debug setting on client options set +func ClientOptDebug(debug bool) ClientOption { + return func(client *Client) { + client.debug = debug + } +} + +// ClientOptEnableCache returns a function that sets the enableCache setting on a client options set +func ClientOptEnableCache(enableCache bool) ClientOption { + return func(client *Client) { + client.enableCache = enableCache + } +} + +// ClientOptBasicAuth returns a function that sets the username and password setting on client options set +func ClientOptBasicAuth(username, password string) ClientOption { + return func(client *Client) { + client.username = username + client.password = password + } +} + +// ClientOptWriter returns a function that sets the writer setting on client options set +func ClientOptWriter(out io.Writer) ClientOption { + return func(client *Client) { + client.out = out + } +} + +// ClientOptAuthorizer returns a function that sets the authorizer setting on a client options set. This +// can be used to override the default authorization mechanism. +// +// Depending on the use-case you may need to set both ClientOptAuthorizer and ClientOptRegistryAuthorizer. +func ClientOptAuthorizer(authorizer auth.Client) ClientOption { + return func(client *Client) { + client.authorizer = &authorizer + } +} + +// ClientOptRegistryAuthorizer returns a function that sets the registry authorizer setting on a client options set. This +// can be used to override the default authorization mechanism. +// +// Depending on the use-case you may need to set both ClientOptAuthorizer and ClientOptRegistryAuthorizer. +func ClientOptRegistryAuthorizer(registryAuthorizer RemoteClient) ClientOption { + return func(client *Client) { + client.registryAuthorizer = registryAuthorizer + } +} + +// ClientOptCredentialsFile returns a function that sets the credentialsFile setting on a client options set +func ClientOptCredentialsFile(credentialsFile string) ClientOption { + return func(client *Client) { + client.credentialsFile = credentialsFile + } +} + +// ClientOptHTTPClient returns a function that sets the httpClient setting on a client options set +func ClientOptHTTPClient(httpClient *http.Client) ClientOption { + return func(client *Client) { + client.httpClient = httpClient + } +} + +func ClientOptPlainHTTP() ClientOption { + return func(c *Client) { + c.plainHTTP = true + } +} + +type ( + // LoginOption allows specifying various settings on login + LoginOption func(*loginOperation) + + loginOperation struct { + host string + client *Client + } +) + +// warnIfHostHasPath checks if the host contains a repository path and logs a warning if it does. +// Returns true if the host contains a path component (i.e., contains a '/'). +func warnIfHostHasPath(host string) bool { + if strings.Contains(host, "/") { + registryHost := strings.Split(host, "/")[0] + slog.Warn("registry login currently only supports registry hostname, not a repository path", "host", host, "suggested", registryHost) + return true + } + return false +} + +// Login logs into a registry +func (c *Client) Login(host string, options ...LoginOption) error { + for _, option := range options { + option(&loginOperation{host, c}) + } + + warnIfHostHasPath(host) + + reg, err := remote.NewRegistry(host) + if err != nil { + return err + } + reg.PlainHTTP = c.plainHTTP + cred := auth.Credential{Username: c.username, Password: c.password} + c.authorizer.ForceAttemptOAuth2 = true + reg.Client = c.authorizer + + ctx := context.Background() + if err := reg.Ping(ctx); err != nil { + c.authorizer.ForceAttemptOAuth2 = false + if err := reg.Ping(ctx); err != nil { + return fmt.Errorf("authenticating to %q: %w", host, err) + } + } + // Always restore to false after probing, to avoid forcing POST to token endpoints like GHCR. + c.authorizer.ForceAttemptOAuth2 = false + + key := credentials.ServerAddressFromRegistry(host) + key = credentials.ServerAddressFromHostname(key) + if err := c.credentialsStore.Put(ctx, key, cred); err != nil { + return err + } + + _, _ = fmt.Fprintln(c.out, "Login Succeeded") + return nil +} + +// LoginOptBasicAuth returns a function that sets the username/password settings on login +func LoginOptBasicAuth(username string, password string) LoginOption { + return func(o *loginOperation) { + o.client.username = username + o.client.password = password + o.client.authorizer.Credential = auth.StaticCredential(o.host, auth.Credential{Username: username, Password: password}) + } +} + +// LoginOptPlainText returns a function that allows plaintext (HTTP) login +func LoginOptPlainText(isPlainText bool) LoginOption { + return func(o *loginOperation) { + o.client.plainHTTP = isPlainText + } +} + +func ensureTLSConfig(client *auth.Client, setConfig *tls.Config) (*tls.Config, error) { + var transport *http.Transport + + switch t := client.Client.Transport.(type) { + case *http.Transport: + transport = t + case *retry.Transport: + switch t := t.Base.(type) { + case *http.Transport: + transport = t + case *LoggingTransport: + switch t := t.RoundTripper.(type) { + case *http.Transport: + transport = t + } + } + } + + if transport == nil { + // we don't know how to access the http.Transport, most likely the + // auth.Client.Client was provided by API user + return nil, fmt.Errorf("unable to access TLS client configuration, the provided HTTP Transport is not supported, given: %T", client.Client.Transport) + } + + switch { + case setConfig != nil: + transport.TLSClientConfig = setConfig + case transport.TLSClientConfig == nil: + transport.TLSClientConfig = &tls.Config{} + } + + return transport.TLSClientConfig, nil +} + +// LoginOptInsecure returns a function that sets the insecure setting on login +func LoginOptInsecure(insecure bool) LoginOption { + return func(o *loginOperation) { + tlsConfig, err := ensureTLSConfig(o.client.authorizer, nil) + + if err != nil { + panic(err) + } + + tlsConfig.InsecureSkipVerify = insecure + } +} + +// LoginOptTLSClientConfig returns a function that sets the TLS settings on login. +func LoginOptTLSClientConfig(certFile, keyFile, caFile string) LoginOption { + return func(o *loginOperation) { + if (certFile == "" || keyFile == "") && caFile == "" { + return + } + tlsConfig, err := ensureTLSConfig(o.client.authorizer, nil) + if err != nil { + panic(err) + } + + if certFile != "" && keyFile != "" { + authCert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + panic(err) + } + tlsConfig.Certificates = []tls.Certificate{authCert} + } + + if caFile != "" { + certPool := x509.NewCertPool() + ca, err := os.ReadFile(caFile) + if err != nil { + panic(err) + } + if !certPool.AppendCertsFromPEM(ca) { + panic(fmt.Errorf("unable to parse CA file: %q", caFile)) + } + tlsConfig.RootCAs = certPool + } + } +} + +// LoginOptTLSClientConfigFromConfig returns a function that sets the TLS settings on login +// receiving the configuration in memory rather than from files. +func LoginOptTLSClientConfigFromConfig(conf *tls.Config) LoginOption { + return func(o *loginOperation) { + _, err := ensureTLSConfig(o.client.authorizer, conf) + if err != nil { + panic(err) + } + } +} + +type ( + // LogoutOption allows specifying various settings on logout + LogoutOption func(*logoutOperation) + + logoutOperation struct{} +) + +// Logout logs out of a registry +func (c *Client) Logout(host string, opts ...LogoutOption) error { + operation := &logoutOperation{} + for _, opt := range opts { + opt(operation) + } + + if err := credentials.Logout(context.Background(), c.credentialsStore, host); err != nil { + return err + } + _, _ = fmt.Fprintf(c.out, "Removing login credentials for %s\n", host) + return nil +} + +type ( + // PullOption allows specifying various settings on pull + PullOption func(*pullOperation) + + // PullResult is the result returned upon successful pull. + PullResult struct { + Manifest *DescriptorPullSummary `json:"manifest"` + Config *DescriptorPullSummary `json:"config"` + Chart *DescriptorPullSummaryWithMeta `json:"chart"` + Prov *DescriptorPullSummary `json:"prov"` + Ref string `json:"ref"` + } + + DescriptorPullSummary struct { + Data []byte `json:"-"` + Digest string `json:"digest"` + Size int64 `json:"size"` + } + + DescriptorPullSummaryWithMeta struct { + DescriptorPullSummary + Meta *chart.Metadata `json:"meta"` + } + + pullOperation struct { + withChart bool + withProv bool + ignoreMissingProv bool + } +) + +// processChartPull handles chart-specific processing of a generic pull result +func (c *Client) processChartPull(genericResult *GenericPullResult, operation *pullOperation) (*PullResult, error) { + var err error + + // Chart-specific validation + minNumDescriptors := 1 // 1 for the config + if operation.withChart { + minNumDescriptors++ + } + if operation.withProv && !operation.ignoreMissingProv { + minNumDescriptors++ + } + + numDescriptors := len(genericResult.Descriptors) + if numDescriptors < minNumDescriptors { + return nil, fmt.Errorf("manifest does not contain minimum number of descriptors (%d), descriptors found: %d", + minNumDescriptors, numDescriptors) + } + + // Find chart-specific descriptors + var configDescriptor *ocispec.Descriptor + var chartDescriptor *ocispec.Descriptor + var provDescriptor *ocispec.Descriptor + + for _, descriptor := range genericResult.Descriptors { + d := descriptor + switch d.MediaType { + case ConfigMediaType: + configDescriptor = &d + case ChartLayerMediaType: + chartDescriptor = &d + case ProvLayerMediaType: + provDescriptor = &d + case LegacyChartLayerMediaType: + chartDescriptor = &d + _, _ = fmt.Fprintf(c.out, "Warning: chart media type %s is deprecated\n", LegacyChartLayerMediaType) + } + } + + // Chart-specific validation + if configDescriptor == nil { + return nil, fmt.Errorf("could not load config with mediatype %s", ConfigMediaType) + } + if operation.withChart && chartDescriptor == nil { + return nil, fmt.Errorf("manifest does not contain a layer with mediatype %s", + ChartLayerMediaType) + } + + var provMissing bool + if operation.withProv && provDescriptor == nil { + if operation.ignoreMissingProv { + provMissing = true + } else { + return nil, fmt.Errorf("manifest does not contain a layer with mediatype %s", + ProvLayerMediaType) + } + } + + // Build chart-specific result + result := &PullResult{ + Manifest: &DescriptorPullSummary{ + Digest: genericResult.Manifest.Digest.String(), + Size: genericResult.Manifest.Size, + }, + Config: &DescriptorPullSummary{ + Digest: configDescriptor.Digest.String(), + Size: configDescriptor.Size, + }, + Chart: &DescriptorPullSummaryWithMeta{}, + Prov: &DescriptorPullSummary{}, + Ref: genericResult.Ref, + } + + // Fetch data using generic client + genericClient := c.Generic() + + result.Manifest.Data, err = genericClient.GetDescriptorData(genericResult.MemoryStore, genericResult.Manifest) + if err != nil { + return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", genericResult.Manifest.Digest, err) + } + + result.Config.Data, err = genericClient.GetDescriptorData(genericResult.MemoryStore, *configDescriptor) + if err != nil { + return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", configDescriptor.Digest, err) + } + + if err := json.Unmarshal(result.Config.Data, &result.Chart.Meta); err != nil { + return nil, err + } + + if operation.withChart { + result.Chart.Data, err = genericClient.GetDescriptorData(genericResult.MemoryStore, *chartDescriptor) + if err != nil { + return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", chartDescriptor.Digest, err) + } + result.Chart.Digest = chartDescriptor.Digest.String() + result.Chart.Size = chartDescriptor.Size + } + + if operation.withProv && !provMissing { + result.Prov.Data, err = genericClient.GetDescriptorData(genericResult.MemoryStore, *provDescriptor) + if err != nil { + return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", provDescriptor.Digest, err) + } + result.Prov.Digest = provDescriptor.Digest.String() + result.Prov.Size = provDescriptor.Size + } + + _, _ = fmt.Fprintf(c.out, "Pulled: %s\n", result.Ref) + _, _ = fmt.Fprintf(c.out, "Digest: %s\n", result.Manifest.Digest) + + if strings.Contains(result.Ref, "_") { + _, _ = fmt.Fprintf(c.out, "%s contains an underscore.\n", result.Ref) + _, _ = fmt.Fprint(c.out, registryUnderscoreMessage+"\n") + } + + return result, nil +} + +// Pull downloads a chart from a registry +func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) { + operation := &pullOperation{ + withChart: true, // By default, always download the chart layer + } + for _, option := range options { + option(operation) + } + if !operation.withChart && !operation.withProv { + return nil, errors.New( + "must specify at least one layer to pull (chart/prov)") + } + + // Build allowed media types for chart pull + allowedMediaTypes := []string{ + ocispec.MediaTypeImageIndex, + ocispec.MediaTypeImageManifest, + ConfigMediaType, + } + if operation.withChart { + allowedMediaTypes = append(allowedMediaTypes, ChartLayerMediaType, LegacyChartLayerMediaType) + } + if operation.withProv { + allowedMediaTypes = append(allowedMediaTypes, ProvLayerMediaType) + } + + // Use generic client for the pull operation + genericClient := c.Generic() + genericResult, err := genericClient.PullGeneric(ref, GenericPullOptions{ + AllowedMediaTypes: allowedMediaTypes, + }) + if err != nil { + return nil, err + } + + // Process the result with chart-specific logic + return c.processChartPull(genericResult, operation) +} + +// PullOptWithChart returns a function that sets the withChart setting on pull +func PullOptWithChart(withChart bool) PullOption { + return func(operation *pullOperation) { + operation.withChart = withChart + } +} + +// PullOptWithProv returns a function that sets the withProv setting on pull +func PullOptWithProv(withProv bool) PullOption { + return func(operation *pullOperation) { + operation.withProv = withProv + } +} + +// PullOptIgnoreMissingProv returns a function that sets the ignoreMissingProv setting on pull +func PullOptIgnoreMissingProv(ignoreMissingProv bool) PullOption { + return func(operation *pullOperation) { + operation.ignoreMissingProv = ignoreMissingProv + } +} + +type ( + // PushOption allows specifying various settings on push + PushOption func(*pushOperation) + + // PushResult is the result returned upon successful push. + PushResult struct { + Manifest *descriptorPushSummary `json:"manifest"` + Config *descriptorPushSummary `json:"config"` + Chart *descriptorPushSummaryWithMeta `json:"chart"` + Prov *descriptorPushSummary `json:"prov"` + Ref string `json:"ref"` + } + + descriptorPushSummary struct { + Digest string `json:"digest"` + Size int64 `json:"size"` + } + + descriptorPushSummaryWithMeta struct { + descriptorPushSummary + Meta *chart.Metadata `json:"meta"` + } + + pushOperation struct { + provData []byte + strictMode bool + creationTime string + } +) + +// Push uploads a chart to a registry. +func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResult, error) { + parsedRef, err := newReference(ref) + if err != nil { + return nil, err + } + + operation := &pushOperation{ + strictMode: true, // By default, enable strict mode + } + for _, option := range options { + option(operation) + } + meta, err := extractChartMeta(data) + if err != nil { + return nil, err + } + if operation.strictMode { + if !strings.HasSuffix(ref, fmt.Sprintf("/%s:%s", meta.Name, meta.Version)) { + return nil, errors.New( + "strict mode enabled, ref basename and tag must match the chart name and version") + } + } + + ctx := context.Background() + + memoryStore := memory.New() + chartDescriptor, err := oras.PushBytes(ctx, memoryStore, ChartLayerMediaType, data) + if err != nil { + return nil, err + } + + configData, err := json.Marshal(meta) + if err != nil { + return nil, err + } + + configDescriptor, err := oras.PushBytes(ctx, memoryStore, ConfigMediaType, configData) + if err != nil { + return nil, err + } + + layers := []ocispec.Descriptor{chartDescriptor} + var provDescriptor ocispec.Descriptor + if operation.provData != nil { + provDescriptor, err = oras.PushBytes(ctx, memoryStore, ProvLayerMediaType, operation.provData) + if err != nil { + return nil, err + } + + layers = append(layers, provDescriptor) + } + + // sort layers for determinism, similar to how ORAS v1 does it + sort.Slice(layers, func(i, j int) bool { + return layers[i].Digest < layers[j].Digest + }) + + ociAnnotations := generateOCIAnnotations(meta, operation.creationTime) + + manifestDescriptor, err := c.tagManifest(ctx, memoryStore, configDescriptor, + layers, ociAnnotations, parsedRef) + if err != nil { + return nil, err + } + + repository, err := remote.NewRepository(parsedRef.String()) + if err != nil { + return nil, err + } + repository.PlainHTTP = c.plainHTTP + repository.Client = c.authorizer + + manifestDescriptor, err = oras.ExtendedCopy(ctx, memoryStore, parsedRef.String(), repository, parsedRef.String(), oras.DefaultExtendedCopyOptions) + if err != nil { + return nil, err + } + + chartSummary := &descriptorPushSummaryWithMeta{ + Meta: meta, + } + chartSummary.Digest = chartDescriptor.Digest.String() + chartSummary.Size = chartDescriptor.Size + result := &PushResult{ + Manifest: &descriptorPushSummary{ + Digest: manifestDescriptor.Digest.String(), + Size: manifestDescriptor.Size, + }, + Config: &descriptorPushSummary{ + Digest: configDescriptor.Digest.String(), + Size: configDescriptor.Size, + }, + Chart: chartSummary, + Prov: &descriptorPushSummary{}, // prevent nil references + Ref: parsedRef.String(), + } + if operation.provData != nil { + result.Prov = &descriptorPushSummary{ + Digest: provDescriptor.Digest.String(), + Size: provDescriptor.Size, + } + } + _, _ = fmt.Fprintf(c.out, "Pushed: %s\n", result.Ref) + _, _ = fmt.Fprintf(c.out, "Digest: %s\n", result.Manifest.Digest) + if strings.Contains(parsedRef.orasReference.Reference, "_") { + _, _ = fmt.Fprintf(c.out, "%s contains an underscore.\n", result.Ref) + _, _ = fmt.Fprint(c.out, registryUnderscoreMessage+"\n") + } + + return result, err +} + +// PushOptProvData returns a function that sets the prov bytes setting on push +func PushOptProvData(provData []byte) PushOption { + return func(operation *pushOperation) { + operation.provData = provData + } +} + +// PushOptStrictMode returns a function that sets the strictMode setting on push +func PushOptStrictMode(strictMode bool) PushOption { + return func(operation *pushOperation) { + operation.strictMode = strictMode + } +} + +// PushOptCreationTime returns a function that sets the creation time +func PushOptCreationTime(creationTime string) PushOption { + return func(operation *pushOperation) { + operation.creationTime = creationTime + } +} + +// Tags provides a sorted list all semver compliant tags for a given repository +func (c *Client) Tags(ref string) ([]string, error) { + parsedReference, err := registry.ParseReference(ref) + if err != nil { + return nil, err + } + + ctx := context.Background() + repository, err := remote.NewRepository(parsedReference.String()) + if err != nil { + return nil, err + } + repository.PlainHTTP = c.plainHTTP + repository.Client = c.authorizer + + var tagVersions []*semver.Version + err = repository.Tags(ctx, "", func(tags []string) error { + for _, tag := range tags { + // Change underscore (_) back to plus (+) for Helm + // See https://github.com/helm/helm/issues/10166 + tagVersion, err := semver.StrictNewVersion(strings.ReplaceAll(tag, "_", "+")) + if err == nil { + tagVersions = append(tagVersions, tagVersion) + } + } + + return nil + }) + if err != nil { + return nil, err + } + + // Sort the collection + sort.Sort(sort.Reverse(semver.Collection(tagVersions))) + + tags := make([]string, len(tagVersions)) + + for iTv, tv := range tagVersions { + tags[iTv] = tv.String() + } + + return tags, nil + +} + +// Resolve a reference to a descriptor. +func (c *Client) Resolve(ref string) (desc ocispec.Descriptor, err error) { + remoteRepository, err := remote.NewRepository(ref) + if err != nil { + return desc, err + } + remoteRepository.PlainHTTP = c.plainHTTP + remoteRepository.Client = c.authorizer + + parsedReference, err := newReference(ref) + if err != nil { + return desc, err + } + + ctx := context.Background() + parsedString := parsedReference.String() + return remoteRepository.Resolve(ctx, parsedString) +} + +// ValidateReference for path and version +func (c *Client) ValidateReference(ref, version string, u *url.URL) (string, *url.URL, error) { + var tag string + + registryReference, err := newReference(u.Host + u.Path) + if err != nil { + return "", nil, err + } + + if version == "" { + // Use OCI URI tag as default + version = registryReference.Tag + } else { + if registryReference.Tag != "" && registryReference.Tag != version { + return "", nil, fmt.Errorf("chart reference and version mismatch: %s is not %s", version, registryReference.Tag) + } + } + + if registryReference.Digest != "" { + if version == "" { + // Install by digest only + return "", u, nil + } + u.Path = fmt.Sprintf("%s@%s", registryReference.Repository, registryReference.Digest) + + // Validate the tag if it was specified + path := registryReference.Registry + "/" + registryReference.Repository + ":" + version + desc, err := c.Resolve(path) + if err != nil { + // The resource does not have to be tagged when digest is specified + return "", u, nil + } + if desc.Digest.String() != registryReference.Digest { + return "", nil, fmt.Errorf("chart reference digest mismatch: %s is not %s", desc.Digest.String(), registryReference.Digest) + } + return registryReference.Digest, u, nil + } + + // Evaluate whether an explicit version has been provided. Otherwise, determine version to use + _, errSemVer := semver.NewVersion(version) + if errSemVer == nil { + tag = version + } else { + // Retrieve list of repository tags + tags, err := c.Tags(strings.TrimPrefix(ref, fmt.Sprintf("%s://", OCIScheme))) + if err != nil { + return "", nil, err + } + if len(tags) == 0 { + return "", nil, fmt.Errorf("unable to locate any tags in provided repository: %s", ref) + } + + // Determine if version provided + // If empty, try to get the highest available tag + // If exact version, try to find it + // If semver constraint string, try to find a match + tag, err = GetTagMatchingVersionOrConstraint(tags, version) + if err != nil { + return "", nil, err + } + } + + u.Path = fmt.Sprintf("%s:%s", registryReference.Repository, tag) + // desc, err := c.Resolve(u.Path) + + return "", u, err +} + +// tagManifest prepares and tags a manifest in memory storage +func (c *Client) tagManifest(ctx context.Context, memoryStore *memory.Store, + configDescriptor ocispec.Descriptor, layers []ocispec.Descriptor, + ociAnnotations map[string]string, parsedRef reference) (ocispec.Descriptor, error) { + + manifest := ocispec.Manifest{ + Versioned: specs.Versioned{SchemaVersion: 2}, + Config: configDescriptor, + Layers: layers, + Annotations: ociAnnotations, + } + + manifestData, err := json.Marshal(manifest) + if err != nil { + return ocispec.Descriptor{}, err + } + + return oras.TagBytes(ctx, memoryStore, ocispec.MediaTypeImageManifest, + manifestData, parsedRef.String()) +} diff --git a/vendor/helm.sh/helm/v4/pkg/registry/constants.go b/vendor/helm.sh/helm/v4/pkg/registry/constants.go new file mode 100644 index 00000000000..c455cf314de --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/registry/constants.go @@ -0,0 +1,37 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package registry // import "helm.sh/helm/v4/pkg/registry" + +const ( + // OCIScheme is the URL scheme for OCI-based requests + OCIScheme = "oci" + + // CredentialsFileBasename is the filename for auth credentials file + CredentialsFileBasename = "registry/config.json" + + // ConfigMediaType is the reserved media type for the Helm chart manifest config + ConfigMediaType = "application/vnd.cncf.helm.config.v1+json" + + // ChartLayerMediaType is the reserved media type for Helm chart package content + ChartLayerMediaType = "application/vnd.cncf.helm.chart.content.v1.tar+gzip" + + // ProvLayerMediaType is the reserved media type for Helm chart provenance files + ProvLayerMediaType = "application/vnd.cncf.helm.chart.provenance.v1.prov" + + // LegacyChartLayerMediaType is the legacy reserved media type for Helm chart package content. + LegacyChartLayerMediaType = "application/tar+gzip" +) diff --git a/vendor/helm.sh/helm/v4/pkg/registry/generic.go b/vendor/helm.sh/helm/v4/pkg/registry/generic.go new file mode 100644 index 00000000000..b46133d9170 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/registry/generic.go @@ -0,0 +1,161 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package registry + +import ( + "context" + "io" + "net/http" + "slices" + "sort" + "sync" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "oras.land/oras-go/v2" + "oras.land/oras-go/v2/content" + "oras.land/oras-go/v2/content/memory" + "oras.land/oras-go/v2/registry/remote" + "oras.land/oras-go/v2/registry/remote/auth" + "oras.land/oras-go/v2/registry/remote/credentials" +) + +// GenericClient provides low-level OCI operations without artifact-specific assumptions +type GenericClient struct { + debug bool + enableCache bool + credentialsFile string + username string + password string + out io.Writer + authorizer *auth.Client + registryAuthorizer RemoteClient + credentialsStore credentials.Store + httpClient *http.Client + plainHTTP bool +} + +// GenericPullOptions configures a generic pull operation +type GenericPullOptions struct { + // MediaTypes to include in the pull (empty means all) + AllowedMediaTypes []string + // Skip descriptors with these media types + SkipMediaTypes []string + // Custom PreCopy function for filtering + PreCopy func(context.Context, ocispec.Descriptor) error +} + +// GenericPullResult contains the result of a generic pull operation +type GenericPullResult struct { + Manifest ocispec.Descriptor + Descriptors []ocispec.Descriptor + MemoryStore *memory.Store + Ref string +} + +// NewGenericClient creates a new generic OCI client from an existing Client +func NewGenericClient(client *Client) *GenericClient { + return &GenericClient{ + debug: client.debug, + enableCache: client.enableCache, + credentialsFile: client.credentialsFile, + username: client.username, + password: client.password, + out: client.out, + authorizer: client.authorizer, + registryAuthorizer: client.registryAuthorizer, + credentialsStore: client.credentialsStore, + httpClient: client.httpClient, + plainHTTP: client.plainHTTP, + } +} + +// PullGeneric performs a generic OCI pull without artifact-specific assumptions +func (c *GenericClient) PullGeneric(ref string, options GenericPullOptions) (*GenericPullResult, error) { + parsedRef, err := newReference(ref) + if err != nil { + return nil, err + } + + memoryStore := memory.New() + var descriptors []ocispec.Descriptor + + // Set up a repository with authentication and configuration + repository, err := remote.NewRepository(parsedRef.String()) + if err != nil { + return nil, err + } + repository.PlainHTTP = c.plainHTTP + repository.Client = c.authorizer + + ctx := context.Background() + + // Prepare allowed media types for filtering + var allowedMediaTypes []string + if len(options.AllowedMediaTypes) > 0 { + allowedMediaTypes = make([]string, len(options.AllowedMediaTypes)) + copy(allowedMediaTypes, options.AllowedMediaTypes) + sort.Strings(allowedMediaTypes) + } + + var mu sync.Mutex + manifest, err := oras.Copy(ctx, repository, parsedRef.String(), memoryStore, "", oras.CopyOptions{ + CopyGraphOptions: oras.CopyGraphOptions{ + PreCopy: func(ctx context.Context, desc ocispec.Descriptor) error { + // Apply a custom PreCopy function if provided + if options.PreCopy != nil { + if err := options.PreCopy(ctx, desc); err != nil { + return err + } + } + + mediaType := desc.MediaType + + // Skip media types if specified + if slices.Contains(options.SkipMediaTypes, mediaType) { + return oras.SkipNode + } + + // Filter by allowed media types if specified + if len(allowedMediaTypes) > 0 { + if i := sort.SearchStrings(allowedMediaTypes, mediaType); i >= len(allowedMediaTypes) || allowedMediaTypes[i] != mediaType { + return oras.SkipNode + } + } + + mu.Lock() + descriptors = append(descriptors, desc) + mu.Unlock() + return nil + }, + }, + }) + if err != nil { + return nil, err + } + + return &GenericPullResult{ + Manifest: manifest, + Descriptors: descriptors, + MemoryStore: memoryStore, + Ref: parsedRef.String(), + }, nil +} + +// GetDescriptorData retrieves the data for a specific descriptor +func (c *GenericClient) GetDescriptorData(store *memory.Store, desc ocispec.Descriptor) ([]byte, error) { + return content.FetchAll(context.Background(), store, desc) +} diff --git a/vendor/helm.sh/helm/v4/pkg/registry/plugin.go b/vendor/helm.sh/helm/v4/pkg/registry/plugin.go new file mode 100644 index 00000000000..e4b4afa2487 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/registry/plugin.go @@ -0,0 +1,212 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package registry + +import ( + "encoding/json" + "fmt" + "strings" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +// Plugin-specific constants +const ( + // PluginArtifactType is the artifact type for Helm plugins + PluginArtifactType = "application/vnd.helm.plugin.v1+json" +) + +// PluginPullOptions configures a plugin pull operation +type PluginPullOptions struct { + // PluginName specifies the expected plugin name for layer validation + PluginName string +} + +// PluginPullResult contains the result of a plugin pull operation +type PluginPullResult struct { + Manifest ocispec.Descriptor + PluginData []byte + Prov struct { + Data []byte + } + Ref string + PluginName string +} + +// PullPlugin downloads a plugin from an OCI registry using artifact type +func (c *Client) PullPlugin(ref string, pluginName string, options ...PluginPullOption) (*PluginPullResult, error) { + operation := &pluginPullOperation{ + pluginName: pluginName, + } + for _, option := range options { + option(operation) + } + + // Use generic client for the pull operation with artifact type filtering + genericClient := c.Generic() + genericResult, err := genericClient.PullGeneric(ref, GenericPullOptions{ + // Allow manifests and all layer types - we'll validate artifact type after download + AllowedMediaTypes: []string{ + ocispec.MediaTypeImageManifest, + "application/vnd.oci.image.layer.v1.tar", + "application/vnd.oci.image.layer.v1.tar+gzip", + }, + }) + if err != nil { + return nil, err + } + + // Process the result with plugin-specific logic + return c.processPluginPull(genericResult, operation.pluginName) +} + +// processPluginPull handles plugin-specific processing of a generic pull result using artifact type +func (c *Client) processPluginPull(genericResult *GenericPullResult, pluginName string) (*PluginPullResult, error) { + // First validate that this is actually a plugin artifact + manifestData, err := c.Generic().GetDescriptorData(genericResult.MemoryStore, genericResult.Manifest) + if err != nil { + return nil, fmt.Errorf("unable to retrieve manifest: %w", err) + } + + // Parse the manifest to check artifact type + var manifest ocispec.Manifest + if err := json.Unmarshal(manifestData, &manifest); err != nil { + return nil, fmt.Errorf("unable to parse manifest: %w", err) + } + + // Validate artifact type (for OCI v1.1+ manifests) + if manifest.ArtifactType != "" && manifest.ArtifactType != PluginArtifactType { + return nil, fmt.Errorf("expected artifact type %s, got %s", PluginArtifactType, manifest.ArtifactType) + } + + // For backwards compatibility, also check config media type if no artifact type + if manifest.ArtifactType == "" && manifest.Config.MediaType != PluginArtifactType { + return nil, fmt.Errorf("expected config media type %s for legacy compatibility, got %s", PluginArtifactType, manifest.Config.MediaType) + } + + // Find the plugin tarball and optional provenance using NAME-VERSION.tgz format + var pluginDescriptor *ocispec.Descriptor + var provenanceDescriptor *ocispec.Descriptor + var foundProvenanceName string + + // Look for layers with the expected titles/annotations + for _, layer := range manifest.Layers { + d := layer + // Check for title annotation + if title, exists := d.Annotations[ocispec.AnnotationTitle]; exists { + // Check if this looks like a plugin tarball: {pluginName}-{version}.tgz + if pluginDescriptor == nil && strings.HasPrefix(title, pluginName+"-") && strings.HasSuffix(title, ".tgz") { + pluginDescriptor = &d + } + // Check if this looks like a plugin provenance: {pluginName}-{version}.tgz.prov + if provenanceDescriptor == nil && strings.HasPrefix(title, pluginName+"-") && strings.HasSuffix(title, ".tgz.prov") { + provenanceDescriptor = &d + foundProvenanceName = title + } + } + } + + // Plugin tarball is required + if pluginDescriptor == nil { + return nil, fmt.Errorf("required layer matching pattern %s-VERSION.tgz not found in manifest", pluginName) + } + + // Build plugin-specific result + result := &PluginPullResult{ + Manifest: genericResult.Manifest, + Ref: genericResult.Ref, + PluginName: pluginName, + } + + // Fetch plugin data using generic client + genericClient := c.Generic() + result.PluginData, err = genericClient.GetDescriptorData(genericResult.MemoryStore, *pluginDescriptor) + if err != nil { + return nil, fmt.Errorf("unable to retrieve plugin data with digest %s: %w", pluginDescriptor.Digest, err) + } + + // Fetch provenance data if available + if provenanceDescriptor != nil { + result.Prov.Data, err = genericClient.GetDescriptorData(genericResult.MemoryStore, *provenanceDescriptor) + if err != nil { + return nil, fmt.Errorf("unable to retrieve provenance data with digest %s: %w", provenanceDescriptor.Digest, err) + } + } + + _, _ = fmt.Fprintf(c.out, "Pulled plugin: %s\n", result.Ref) + _, _ = fmt.Fprintf(c.out, "Digest: %s\n", result.Manifest.Digest) + if result.Prov.Data != nil { + _, _ = fmt.Fprintf(c.out, "Provenance: %s\n", foundProvenanceName) + } + + if strings.Contains(result.Ref, "_") { + _, _ = fmt.Fprintf(c.out, "%s contains an underscore.\n", result.Ref) + _, _ = fmt.Fprint(c.out, registryUnderscoreMessage+"\n") + } + + return result, nil +} + +// Plugin pull operation types and options +type ( + pluginPullOperation struct { + pluginName string + withProv bool + } + + // PluginPullOption allows customizing plugin pull operations + PluginPullOption func(*pluginPullOperation) +) + +// PluginPullOptWithPluginName sets the plugin name for validation +func PluginPullOptWithPluginName(name string) PluginPullOption { + return func(operation *pluginPullOperation) { + operation.pluginName = name + } +} + +// GetPluginName extracts the plugin name from an OCI reference using proper reference parsing +func GetPluginName(source string) (string, error) { + ref, err := newReference(source) + if err != nil { + return "", fmt.Errorf("invalid OCI reference: %w", err) + } + + // Extract plugin name from the repository path + // e.g., "ghcr.io/user/plugin-name:v1.0.0" -> Repository: "user/plugin-name" + repository := ref.Repository + if repository == "" { + return "", fmt.Errorf("invalid OCI reference: missing repository") + } + + // Get the last part of the repository path as the plugin name + parts := strings.Split(repository, "/") + pluginName := parts[len(parts)-1] + + if pluginName == "" { + return "", fmt.Errorf("invalid OCI reference: cannot determine plugin name from repository %s", repository) + } + + return pluginName, nil +} + +// PullPluginOptWithProv configures the pull to fetch provenance data +func PullPluginOptWithProv(withProv bool) PluginPullOption { + return func(operation *pluginPullOperation) { + operation.withProv = withProv + } +} diff --git a/vendor/helm.sh/helm/v4/pkg/registry/reference.go b/vendor/helm.sh/helm/v4/pkg/registry/reference.go new file mode 100644 index 00000000000..9a98cf5c9c4 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/registry/reference.go @@ -0,0 +1,84 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package registry + +import ( + "fmt" + "strings" + + "oras.land/oras-go/v2/registry" +) + +type reference struct { + orasReference registry.Reference + Registry string + Repository string + Tag string + Digest string +} + +// newReference will parse and validate the reference, and clean tags when +// applicable tags are only cleaned when plus (+) signs are present and are +// converted to underscores (_) before pushing +// See https://github.com/helm/helm/issues/10166 +func newReference(raw string) (result reference, err error) { + // Remove the oci:// prefix if it is there + raw = strings.TrimPrefix(raw, OCIScheme+"://") + + // The sole possible reference modification is replacing plus (+) signs + // present in tags with underscores (_). To do this properly, we first + // need to identify a tag, and then pass it on to the reference parser + // NOTE: Passing immediately to the reference parser will fail since (+) + // signs are an invalid tag character, and simply replacing all plus (+) + // occurrences could invalidate other portions of the URI + lastIndex := strings.LastIndex(raw, "@") + if lastIndex >= 0 { + result.Digest = raw[(lastIndex + 1):] + raw = raw[:lastIndex] + } + parts := strings.Split(raw, ":") + if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], "/") { + tag := parts[len(parts)-1] + + if tag != "" { + // Replace any plus (+) signs with known underscore (_) conversion + newTag := strings.ReplaceAll(tag, "+", "_") + raw = strings.ReplaceAll(raw, tag, newTag) + } + } + + result.orasReference, err = registry.ParseReference(raw) + if err != nil { + return result, err + } + result.Registry = result.orasReference.Registry + result.Repository = result.orasReference.Repository + result.Tag = result.orasReference.Reference + return result, nil +} + +func (r *reference) String() string { + if r.Tag == "" { + return r.orasReference.String() + "@" + r.Digest + } + return r.orasReference.String() +} + +// IsOCI determines whether a URL is to be treated as an OCI URL +func IsOCI(url string) bool { + return strings.HasPrefix(url, fmt.Sprintf("%s://", OCIScheme)) +} diff --git a/vendor/helm.sh/helm/v4/pkg/registry/tag.go b/vendor/helm.sh/helm/v4/pkg/registry/tag.go new file mode 100644 index 00000000000..bfb4b1ef64e --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/registry/tag.go @@ -0,0 +1,59 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package registry // import "helm.sh/helm/v4/pkg/registry" + +import ( + "fmt" + + "github.com/Masterminds/semver/v3" +) + +func GetTagMatchingVersionOrConstraint(tags []string, versionString string) (string, error) { + var constraint *semver.Constraints + if versionString == "" { + // If the string is empty, set a wildcard constraint + constraint, _ = semver.NewConstraint("*") + } else { + // when customer inputs a specific version, check whether there's an exact match first + for _, v := range tags { + if versionString == v { + return v, nil + } + } + + // Otherwise set constraint to the string given + var err error + constraint, err = semver.NewConstraint(versionString) + if err != nil { + return "", err + } + } + + // Otherwise try to find the first available version matching the string, + // in case it is a constraint + for _, v := range tags { + test, err := semver.NewVersion(v) + if err != nil { + continue + } + if constraint.Check(test) { + return v, nil + } + } + + return "", fmt.Errorf("could not locate a version matching provided version string %s", versionString) +} diff --git a/vendor/helm.sh/helm/v3/pkg/registry/transport.go b/vendor/helm.sh/helm/v4/pkg/registry/transport.go similarity index 85% rename from vendor/helm.sh/helm/v3/pkg/registry/transport.go rename to vendor/helm.sh/helm/v4/pkg/registry/transport.go index 4d8a59ac65f..f039a815991 100644 --- a/vendor/helm.sh/helm/v3/pkg/registry/transport.go +++ b/vendor/helm.sh/helm/v4/pkg/registry/transport.go @@ -23,7 +23,6 @@ import ( "log/slog" "mime" "net/http" - "os" "strings" "sync/atomic" @@ -33,7 +32,7 @@ import ( var ( // requestCount records the number of logged request-response pairs and will // be used as the unique id for the next pair. - requestCount uint64 + requestCount atomic.Uint64 // toScrub is a set of headers that should be scrubbed from the log. toScrub = []string{ @@ -49,7 +48,6 @@ const payloadSizeLimit int64 = 16 * 1024 // 16 KiB // request and add hooks to report HTTP tracing events. type LoggingTransport struct { http.RoundTripper - logger *slog.Logger } // NewTransport creates and returns a new instance of LoggingTransport @@ -73,17 +71,7 @@ func NewTransport(debug bool) *retry.Transport { transport = t.Clone() } if debug { - replace := func(groups []string, a slog.Attr) slog.Attr { - // Remove time. - if a.Key == slog.TimeKey && len(groups) == 0 { - return slog.Attr{} - } - return a - } - logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ - ReplaceAttr: replace, - Level: slog.LevelDebug})) - transport = &LoggingTransport{RoundTripper: transport, logger: logger} + transport = &LoggingTransport{RoundTripper: transport} } return retry.NewTransport(transport) @@ -91,16 +79,16 @@ func NewTransport(debug bool) *retry.Transport { // RoundTrip calls base round trip while keeping track of the current request. func (t *LoggingTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) { - id := atomic.AddUint64(&requestCount, 1) - 1 + id := requestCount.Add(1) - 1 - t.logger.Debug("Request", "id", id, "url", req.URL, "method", req.Method, "header", logHeader(req.Header)) + slog.Debug(req.Method, "id", id, "url", req.URL, "header", logHeader(req.Header)) resp, err = t.RoundTripper.RoundTrip(req) if err != nil { - t.logger.Debug("Response", "id", id, "error", err) + slog.Debug("Response"[:len(req.Method)], "id", id, "error", err) } else if resp != nil { - t.logger.Debug("Response", "id", id, "status", resp.Status, "header", logHeader(resp.Header), "body", logResponseBody(resp)) + slog.Debug("Response"[:len(req.Method)], "id", id, "status", resp.Status, "header", logHeader(resp.Header), "body", logResponseBody(resp)) } else { - t.logger.Debug("Response", "id", id, "response", "nil") + slog.Debug("Response"[:len(req.Method)], "id", id, "response", "nil") } return resp, err @@ -109,7 +97,7 @@ func (t *LoggingTransport) RoundTrip(req *http.Request) (resp *http.Response, er // logHeader prints out the provided header keys and values, with auth header scrubbed. func logHeader(header http.Header) string { if len(header) > 0 { - headers := []string{} + var headers []string for k, v := range header { for _, h := range toScrub { if strings.EqualFold(k, h) { diff --git a/vendor/helm.sh/helm/v4/pkg/release/common.go b/vendor/helm.sh/helm/v4/pkg/release/common.go new file mode 100644 index 00000000000..d33c9664697 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/release/common.go @@ -0,0 +1,116 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package release + +import ( + "errors" + "fmt" + "time" + + "helm.sh/helm/v4/pkg/chart" + v1release "helm.sh/helm/v4/pkg/release/v1" +) + +var NewAccessor func(rel Releaser) (Accessor, error) = newDefaultAccessor //nolint:revive + +var NewHookAccessor func(rel Hook) (HookAccessor, error) = newDefaultHookAccessor //nolint:revive + +func newDefaultAccessor(rel Releaser) (Accessor, error) { + switch v := rel.(type) { + case v1release.Release: + return &v1Accessor{&v}, nil + case *v1release.Release: + return &v1Accessor{v}, nil + default: + return nil, fmt.Errorf("unsupported release type: %T", rel) + } +} + +func newDefaultHookAccessor(hook Hook) (HookAccessor, error) { + switch h := hook.(type) { + case v1release.Hook: + return &v1HookAccessor{&h}, nil + case *v1release.Hook: + return &v1HookAccessor{h}, nil + default: + return nil, errors.New("unsupported release hook type") + } +} + +type v1Accessor struct { + rel *v1release.Release +} + +func (a *v1Accessor) Name() string { + return a.rel.Name +} + +func (a *v1Accessor) Namespace() string { + return a.rel.Namespace +} + +func (a *v1Accessor) Version() int { + return a.rel.Version +} + +func (a *v1Accessor) Hooks() []Hook { + var hooks = make([]Hook, len(a.rel.Hooks)) + for i, h := range a.rel.Hooks { + hooks[i] = h + } + return hooks +} + +func (a *v1Accessor) Manifest() string { + return a.rel.Manifest +} + +func (a *v1Accessor) Notes() string { + return a.rel.Info.Notes +} + +func (a *v1Accessor) Labels() map[string]string { + return a.rel.Labels +} + +func (a *v1Accessor) Chart() chart.Charter { + return a.rel.Chart +} + +func (a *v1Accessor) Status() string { + return a.rel.Info.Status.String() +} + +func (a *v1Accessor) ApplyMethod() string { + return a.rel.ApplyMethod +} + +func (a *v1Accessor) DeployedAt() time.Time { + return a.rel.Info.LastDeployed +} + +type v1HookAccessor struct { + hook *v1release.Hook +} + +func (a *v1HookAccessor) Path() string { + return a.hook.Path +} + +func (a *v1HookAccessor) Manifest() string { + return a.hook.Manifest +} diff --git a/vendor/helm.sh/helm/v4/pkg/release/common/status.go b/vendor/helm.sh/helm/v4/pkg/release/common/status.go new file mode 100644 index 00000000000..fd501030164 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/release/common/status.go @@ -0,0 +1,49 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +// Status is the status of a release +type Status string + +// Describe the status of a release +// NOTE: Make sure to update cmd/helm/status.go when adding or modifying any of these statuses. +const ( + // StatusUnknown indicates that a release is in an uncertain state. + StatusUnknown Status = "unknown" + // StatusDeployed indicates that the release has been pushed to Kubernetes. + StatusDeployed Status = "deployed" + // StatusUninstalled indicates that a release has been uninstalled from Kubernetes. + StatusUninstalled Status = "uninstalled" + // StatusSuperseded indicates that this release object is outdated and a newer one exists. + StatusSuperseded Status = "superseded" + // StatusFailed indicates that the release was not successfully deployed. + StatusFailed Status = "failed" + // StatusUninstalling indicates that an uninstall operation is underway. + StatusUninstalling Status = "uninstalling" + // StatusPendingInstall indicates that an install operation is underway. + StatusPendingInstall Status = "pending-install" + // StatusPendingUpgrade indicates that an upgrade operation is underway. + StatusPendingUpgrade Status = "pending-upgrade" + // StatusPendingRollback indicates that a rollback operation is underway. + StatusPendingRollback Status = "pending-rollback" +) + +func (x Status) String() string { return string(x) } + +// IsPending determines if this status is a state or a transition. +func (x Status) IsPending() bool { + return x == StatusPendingInstall || x == StatusPendingUpgrade || x == StatusPendingRollback +} diff --git a/vendor/helm.sh/helm/v4/pkg/release/interfaces.go b/vendor/helm.sh/helm/v4/pkg/release/interfaces.go new file mode 100644 index 00000000000..aaa5a756f3b --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/release/interfaces.go @@ -0,0 +1,46 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package release + +import ( + "time" + + "helm.sh/helm/v4/pkg/chart" +) + +type Releaser interface{} + +type Hook interface{} + +type Accessor interface { + Name() string + Namespace() string + Version() int + Hooks() []Hook + Manifest() string + Notes() string + Labels() map[string]string + Chart() chart.Charter + Status() string + ApplyMethod() string + DeployedAt() time.Time +} + +type HookAccessor interface { + Path() string + Manifest() string +} diff --git a/vendor/helm.sh/helm/v4/pkg/release/responses.go b/vendor/helm.sh/helm/v4/pkg/release/responses.go new file mode 100644 index 00000000000..6e0a0eaec1c --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/release/responses.go @@ -0,0 +1,24 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package release + +// UninstallReleaseResponse represents a successful response to an uninstall request. +type UninstallReleaseResponse struct { + // Release is the release that was marked deleted. + Release Releaser `json:"release,omitempty"` + // Info is an uninstall message + Info string `json:"info,omitempty"` +} diff --git a/vendor/helm.sh/helm/v4/pkg/release/v1/hook.go b/vendor/helm.sh/helm/v4/pkg/release/v1/hook.go new file mode 100644 index 00000000000..f0a370c1588 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/release/v1/hook.go @@ -0,0 +1,189 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "encoding/json" + "time" +) + +// HookEvent specifies the hook event +type HookEvent string + +// Hook event types +const ( + HookPreInstall HookEvent = "pre-install" + HookPostInstall HookEvent = "post-install" + HookPreDelete HookEvent = "pre-delete" + HookPostDelete HookEvent = "post-delete" + HookPreUpgrade HookEvent = "pre-upgrade" + HookPostUpgrade HookEvent = "post-upgrade" + HookPreRollback HookEvent = "pre-rollback" + HookPostRollback HookEvent = "post-rollback" + HookTest HookEvent = "test" +) + +func (x HookEvent) String() string { return string(x) } + +// HookDeletePolicy specifies the hook delete policy +type HookDeletePolicy string + +// Hook delete policy types +const ( + HookSucceeded HookDeletePolicy = "hook-succeeded" + HookFailed HookDeletePolicy = "hook-failed" + HookBeforeHookCreation HookDeletePolicy = "before-hook-creation" +) + +func (x HookDeletePolicy) String() string { return string(x) } + +// HookOutputLogPolicy specifies the hook output log policy +type HookOutputLogPolicy string + +// Hook output log policy types +const ( + HookOutputOnSucceeded HookOutputLogPolicy = "hook-succeeded" + HookOutputOnFailed HookOutputLogPolicy = "hook-failed" +) + +func (x HookOutputLogPolicy) String() string { return string(x) } + +// HookAnnotation is the label name for a hook +const HookAnnotation = "helm.sh/hook" + +// HookWeightAnnotation is the label name for a hook weight +const HookWeightAnnotation = "helm.sh/hook-weight" + +// HookDeleteAnnotation is the label name for the delete policy for a hook +const HookDeleteAnnotation = "helm.sh/hook-delete-policy" + +// HookOutputLogAnnotation is the label name for the output log policy for a hook +const HookOutputLogAnnotation = "helm.sh/hook-output-log-policy" + +// Hook defines a hook object. +type Hook struct { + Name string `json:"name,omitempty"` + // Kind is the Kubernetes kind. + Kind string `json:"kind,omitempty"` + // Path is the chart-relative path to the template. + Path string `json:"path,omitempty"` + // Manifest is the manifest contents. + Manifest string `json:"manifest,omitempty"` + // Events are the events that this hook fires on. + Events []HookEvent `json:"events,omitempty"` + // LastRun indicates the date/time this was last run. + LastRun HookExecution `json:"last_run,omitempty"` + // Weight indicates the sort order for execution among similar Hook type + Weight int `json:"weight,omitempty"` + // DeletePolicies are the policies that indicate when to delete the hook + DeletePolicies []HookDeletePolicy `json:"delete_policies,omitempty"` + // OutputLogPolicies defines whether we should copy hook logs back to main process + OutputLogPolicies []HookOutputLogPolicy `json:"output_log_policies,omitempty"` +} + +// A HookExecution records the result for the last execution of a hook for a given release. +type HookExecution struct { + // StartedAt indicates the date/time this hook was started + StartedAt time.Time `json:"started_at,omitzero"` + // CompletedAt indicates the date/time this hook was completed. + CompletedAt time.Time `json:"completed_at,omitzero"` + // Phase indicates whether the hook completed successfully + Phase HookPhase `json:"phase"` +} + +// A HookPhase indicates the state of a hook execution +type HookPhase string + +const ( + // HookPhaseUnknown indicates that a hook is in an unknown state + HookPhaseUnknown HookPhase = "Unknown" + // HookPhaseRunning indicates that a hook is currently executing + HookPhaseRunning HookPhase = "Running" + // HookPhaseSucceeded indicates that hook execution succeeded + HookPhaseSucceeded HookPhase = "Succeeded" + // HookPhaseFailed indicates that hook execution failed + HookPhaseFailed HookPhase = "Failed" +) + +// String converts a hook phase to a printable string +func (x HookPhase) String() string { return string(x) } + +// hookExecutionJSON is used for custom JSON marshaling/unmarshaling +type hookExecutionJSON struct { + StartedAt *time.Time `json:"started_at,omitempty"` + CompletedAt *time.Time `json:"completed_at,omitempty"` + Phase HookPhase `json:"phase"` +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +// It handles empty string time fields by treating them as zero values. +func (h *HookExecution) UnmarshalJSON(data []byte) error { + // First try to unmarshal into a map to handle empty string time fields + var raw map[string]interface{} + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + // Replace empty string time fields with nil + for _, field := range []string{"started_at", "completed_at"} { + if val, ok := raw[field]; ok { + if str, ok := val.(string); ok && str == "" { + raw[field] = nil + } + } + } + + // Re-marshal with cleaned data + cleaned, err := json.Marshal(raw) + if err != nil { + return err + } + + // Unmarshal into temporary struct with pointer time fields + var tmp hookExecutionJSON + if err := json.Unmarshal(cleaned, &tmp); err != nil { + return err + } + + // Copy values to HookExecution struct + if tmp.StartedAt != nil { + h.StartedAt = *tmp.StartedAt + } + if tmp.CompletedAt != nil { + h.CompletedAt = *tmp.CompletedAt + } + h.Phase = tmp.Phase + + return nil +} + +// MarshalJSON implements the json.Marshaler interface. +// It omits zero-value time fields from the JSON output. +func (h HookExecution) MarshalJSON() ([]byte, error) { + tmp := hookExecutionJSON{ + Phase: h.Phase, + } + + if !h.StartedAt.IsZero() { + tmp.StartedAt = &h.StartedAt + } + if !h.CompletedAt.IsZero() { + tmp.CompletedAt = &h.CompletedAt + } + + return json.Marshal(tmp) +} diff --git a/vendor/helm.sh/helm/v4/pkg/release/v1/info.go b/vendor/helm.sh/helm/v4/pkg/release/v1/info.go new file mode 100644 index 00000000000..f895fdf6ca9 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/release/v1/info.go @@ -0,0 +1,125 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "encoding/json" + "time" + + "helm.sh/helm/v4/pkg/release/common" + + "k8s.io/apimachinery/pkg/runtime" +) + +// Info describes release information. +type Info struct { + // FirstDeployed is when the release was first deployed. + FirstDeployed time.Time `json:"first_deployed,omitzero"` + // LastDeployed is when the release was last deployed. + LastDeployed time.Time `json:"last_deployed,omitzero"` + // Deleted tracks when this object was deleted. + Deleted time.Time `json:"deleted,omitzero"` + // Description is human-friendly "log entry" about this release. + Description string `json:"description,omitempty"` + // Status is the current state of the release + Status common.Status `json:"status,omitempty"` + // Contains the rendered templates/NOTES.txt if available + Notes string `json:"notes,omitempty"` + // Contains the deployed resources information + Resources map[string][]runtime.Object `json:"resources,omitempty"` +} + +// infoJSON is used for custom JSON marshaling/unmarshaling +type infoJSON struct { + FirstDeployed *time.Time `json:"first_deployed,omitempty"` + LastDeployed *time.Time `json:"last_deployed,omitempty"` + Deleted *time.Time `json:"deleted,omitempty"` + Description string `json:"description,omitempty"` + Status common.Status `json:"status,omitempty"` + Notes string `json:"notes,omitempty"` + Resources map[string][]runtime.Object `json:"resources,omitempty"` +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +// It handles empty string time fields by treating them as zero values. +func (i *Info) UnmarshalJSON(data []byte) error { + // First try to unmarshal into a map to handle empty string time fields + var raw map[string]interface{} + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + // Replace empty string time fields with nil + for _, field := range []string{"first_deployed", "last_deployed", "deleted"} { + if val, ok := raw[field]; ok { + if str, ok := val.(string); ok && str == "" { + raw[field] = nil + } + } + } + + // Re-marshal with cleaned data + cleaned, err := json.Marshal(raw) + if err != nil { + return err + } + + // Unmarshal into temporary struct with pointer time fields + var tmp infoJSON + if err := json.Unmarshal(cleaned, &tmp); err != nil { + return err + } + + // Copy values to Info struct + if tmp.FirstDeployed != nil { + i.FirstDeployed = *tmp.FirstDeployed + } + if tmp.LastDeployed != nil { + i.LastDeployed = *tmp.LastDeployed + } + if tmp.Deleted != nil { + i.Deleted = *tmp.Deleted + } + i.Description = tmp.Description + i.Status = tmp.Status + i.Notes = tmp.Notes + i.Resources = tmp.Resources + + return nil +} + +// MarshalJSON implements the json.Marshaler interface. +// It omits zero-value time fields from the JSON output. +func (i Info) MarshalJSON() ([]byte, error) { + tmp := infoJSON{ + Description: i.Description, + Status: i.Status, + Notes: i.Notes, + Resources: i.Resources, + } + + if !i.FirstDeployed.IsZero() { + tmp.FirstDeployed = &i.FirstDeployed + } + if !i.LastDeployed.IsZero() { + tmp.LastDeployed = &i.LastDeployed + } + if !i.Deleted.IsZero() { + tmp.Deleted = &i.Deleted + } + + return json.Marshal(tmp) +} diff --git a/vendor/helm.sh/helm/v4/pkg/release/v1/mock.go b/vendor/helm.sh/helm/v4/pkg/release/v1/mock.go new file mode 100644 index 00000000000..dc135a24ae4 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/release/v1/mock.go @@ -0,0 +1,142 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "fmt" + "math/rand" + "time" + + "helm.sh/helm/v4/pkg/chart/common" + chart "helm.sh/helm/v4/pkg/chart/v2" + rcommon "helm.sh/helm/v4/pkg/release/common" +) + +// MockHookTemplate is the hook template used for all mock release objects. +var MockHookTemplate = `apiVersion: v1 +kind: Job +metadata: + annotations: + "helm.sh/hook": pre-install +` + +// MockManifest is the manifest used for all mock release objects. +var MockManifest = `apiVersion: v1 +kind: Secret +metadata: + name: fixture +` + +// MockReleaseOptions allows for user-configurable options on mock release objects. +type MockReleaseOptions struct { + Name string + Version int + Chart *chart.Chart + Status rcommon.Status + Namespace string + Labels map[string]string +} + +// Mock creates a mock release object based on options set by MockReleaseOptions. This function should typically not be used outside of testing. +func Mock(opts *MockReleaseOptions) *Release { + date := time.Unix(242085845, 0).UTC() + + name := opts.Name + if name == "" { + name = "testrelease-" + fmt.Sprint(rand.Intn(100)) + } + + version := 1 + if opts.Version != 0 { + version = opts.Version + } + + namespace := opts.Namespace + if namespace == "" { + namespace = "default" + } + var labels map[string]string + if len(opts.Labels) > 0 { + labels = opts.Labels + } + + ch := opts.Chart + if opts.Chart == nil { + ch = &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "foo", + Version: "0.1.0-beta.1", + AppVersion: "1.0", + Annotations: map[string]string{ + "category": "web-apps", + "supported": "true", + }, + Dependencies: []*chart.Dependency{ + { + Name: "cool-plugin", + Version: "1.0.0", + Repository: "https://coolplugin.io/charts", + Condition: "coolPlugin.enabled", + Enabled: true, + }, + { + Name: "crds", + Version: "2.7.1", + Condition: "crds.enabled", + }, + }, + }, + Templates: []*common.File{ + {Name: "templates/foo.tpl", ModTime: time.Now(), Data: []byte(MockManifest)}, + }, + } + } + + scode := rcommon.StatusDeployed + if len(opts.Status) > 0 { + scode = opts.Status + } + + info := &Info{ + FirstDeployed: date, + LastDeployed: date, + Status: scode, + Description: "Release mock", + Notes: "Some mock release notes!", + } + + return &Release{ + Name: name, + Info: info, + Chart: ch, + Config: map[string]interface{}{"name": "value"}, + Version: version, + Namespace: namespace, + Hooks: []*Hook{ + { + Name: "pre-install-hook", + Kind: "Job", + Path: "pre-install-hook.yaml", + Manifest: MockHookTemplate, + LastRun: HookExecution{}, + Events: []HookEvent{HookPreInstall}, + }, + }, + Manifest: MockManifest, + Labels: labels, + } +} diff --git a/vendor/helm.sh/helm/v4/pkg/release/v1/release.go b/vendor/helm.sh/helm/v4/pkg/release/v1/release.go new file mode 100644 index 00000000000..454ee6eb75d --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/release/v1/release.go @@ -0,0 +1,60 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/release/common" +) + +type ApplyMethod string + +const ApplyMethodClientSideApply ApplyMethod = "csa" +const ApplyMethodServerSideApply ApplyMethod = "ssa" + +// Release describes a deployment of a chart, together with the chart +// and the variables used to deploy that chart. +type Release struct { + // Name is the name of the release + Name string `json:"name,omitempty"` + // Info provides information about a release + Info *Info `json:"info,omitempty"` + // Chart is the chart that was released. + Chart *chart.Chart `json:"chart,omitempty"` + // Config is the set of extra Values added to the chart. + // These values override the default values inside of the chart. + Config map[string]interface{} `json:"config,omitempty"` + // Manifest is the string representation of the rendered template. + Manifest string `json:"manifest,omitempty"` + // Hooks are all of the hooks declared for this release. + Hooks []*Hook `json:"hooks,omitempty"` + // Version is an int which represents the revision of the release. + Version int `json:"version,omitempty"` + // Namespace is the kubernetes namespace of the release. + Namespace string `json:"namespace,omitempty"` + // Labels of the release. + // Disabled encoding into Json cause labels are stored in storage driver metadata field. + Labels map[string]string `json:"-"` + // ApplyMethod stores whether server-side or client-side apply was used for the release + // Unset (empty string) should be treated as the default of client-side apply + ApplyMethod string `json:"apply_method,omitempty"` // "ssa" | "csa" +} + +// SetStatus is a helper for setting the status on a release. +func (r *Release) SetStatus(status common.Status, msg string) { + r.Info.Status = status + r.Info.Description = msg +} diff --git a/vendor/helm.sh/helm/v4/pkg/release/v1/util/filter.go b/vendor/helm.sh/helm/v4/pkg/release/v1/util/filter.go new file mode 100644 index 00000000000..dc60195cff2 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/release/v1/util/filter.go @@ -0,0 +1,81 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util // import "helm.sh/helm/v4/pkg/release/v1/util" + +import ( + "helm.sh/helm/v4/pkg/release/common" + rspb "helm.sh/helm/v4/pkg/release/v1" +) + +// FilterFunc returns true if the release object satisfies +// the predicate of the underlying filter func. +type FilterFunc func(*rspb.Release) bool + +// Check applies the FilterFunc to the release object. +func (fn FilterFunc) Check(rls *rspb.Release) bool { + if rls == nil { + return false + } + return fn(rls) +} + +// Filter applies the filter(s) to the list of provided releases +// returning the list that satisfies the filtering predicate. +func (fn FilterFunc) Filter(rels []*rspb.Release) (rets []*rspb.Release) { + for _, rel := range rels { + if fn.Check(rel) { + rets = append(rets, rel) + } + } + return +} + +// Any returns a FilterFunc that filters a list of releases +// determined by the predicate 'f0 || f1 || ... || fn'. +func Any(filters ...FilterFunc) FilterFunc { + return func(rls *rspb.Release) bool { + for _, filter := range filters { + if filter(rls) { + return true + } + } + return false + } +} + +// All returns a FilterFunc that filters a list of releases +// determined by the predicate 'f0 && f1 && ... && fn'. +func All(filters ...FilterFunc) FilterFunc { + return func(rls *rspb.Release) bool { + for _, filter := range filters { + if !filter(rls) { + return false + } + } + return true + } +} + +// StatusFilter filters a set of releases by status code. +func StatusFilter(status common.Status) FilterFunc { + return FilterFunc(func(rls *rspb.Release) bool { + if rls == nil { + return true + } + return rls.Info.Status == status + }) +} diff --git a/vendor/helm.sh/helm/v4/pkg/release/v1/util/kind_sorter.go b/vendor/helm.sh/helm/v4/pkg/release/v1/util/kind_sorter.go new file mode 100644 index 00000000000..bc074340fb6 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/release/v1/util/kind_sorter.go @@ -0,0 +1,165 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "sort" + + release "helm.sh/helm/v4/pkg/release/v1" +) + +// KindSortOrder is an ordering of Kinds. +type KindSortOrder []string + +// InstallOrder is the order in which manifests should be installed (by Kind). +// +// Those occurring earlier in the list get installed before those occurring later in the list. +var InstallOrder KindSortOrder = []string{ + "PriorityClass", + "Namespace", + "NetworkPolicy", + "ResourceQuota", + "LimitRange", + "PodSecurityPolicy", + "PodDisruptionBudget", + "ServiceAccount", + "Secret", + "SecretList", + "ConfigMap", + "StorageClass", + "PersistentVolume", + "PersistentVolumeClaim", + "CustomResourceDefinition", + "ClusterRole", + "ClusterRoleList", + "ClusterRoleBinding", + "ClusterRoleBindingList", + "Role", + "RoleList", + "RoleBinding", + "RoleBindingList", + "Service", + "DaemonSet", + "Pod", + "ReplicationController", + "ReplicaSet", + "Deployment", + "HorizontalPodAutoscaler", + "StatefulSet", + "Job", + "CronJob", + "IngressClass", + "Ingress", + "APIService", + "MutatingWebhookConfiguration", + "ValidatingWebhookConfiguration", +} + +// UninstallOrder is the order in which manifests should be uninstalled (by Kind). +// +// Those occurring earlier in the list get uninstalled before those occurring later in the list. +var UninstallOrder KindSortOrder = []string{ + // For uninstall, we remove validation before mutation to ensure webhooks don't block removal + "ValidatingWebhookConfiguration", + "MutatingWebhookConfiguration", + "APIService", + "Ingress", + "IngressClass", + "Service", + "CronJob", + "Job", + "StatefulSet", + "HorizontalPodAutoscaler", + "Deployment", + "ReplicaSet", + "ReplicationController", + "Pod", + "DaemonSet", + "RoleBindingList", + "RoleBinding", + "RoleList", + "Role", + "ClusterRoleBindingList", + "ClusterRoleBinding", + "ClusterRoleList", + "ClusterRole", + "CustomResourceDefinition", + "PersistentVolumeClaim", + "PersistentVolume", + "StorageClass", + "ConfigMap", + "SecretList", + "Secret", + "ServiceAccount", + "PodDisruptionBudget", + "PodSecurityPolicy", + "LimitRange", + "ResourceQuota", + "NetworkPolicy", + "Namespace", + "PriorityClass", +} + +// sort manifests by kind. +// +// Results are sorted by 'ordering', keeping order of items with equal kind/priority +func sortManifestsByKind(manifests []Manifest, ordering KindSortOrder) []Manifest { + sort.SliceStable(manifests, func(i, j int) bool { + return lessByKind(manifests[i], manifests[j], manifests[i].Head.Kind, manifests[j].Head.Kind, ordering) + }) + + return manifests +} + +// sort hooks by kind, using an out-of-place sort to preserve the input parameters. +// +// Results are sorted by 'ordering', keeping order of items with equal kind/priority +func sortHooksByKind(hooks []*release.Hook, ordering KindSortOrder) []*release.Hook { + h := hooks + sort.SliceStable(h, func(i, j int) bool { + return lessByKind(h[i], h[j], h[i].Kind, h[j].Kind, ordering) + }) + + return h +} + +func lessByKind(_ interface{}, _ interface{}, kindA string, kindB string, o KindSortOrder) bool { + ordering := make(map[string]int, len(o)) + for v, k := range o { + ordering[k] = v + } + + first, aok := ordering[kindA] + second, bok := ordering[kindB] + + if !aok && !bok { + // if both are unknown then sort alphabetically by kind, keep original order if same kind + if kindA != kindB { + return kindA < kindB + } + return first < second + } + // unknown kind is last + if !aok { + return false + } + if !bok { + return true + } + // sort different kinds, keep original order if same priority + return first < second +} diff --git a/vendor/helm.sh/helm/v4/pkg/release/v1/util/manifest.go b/vendor/helm.sh/helm/v4/pkg/release/v1/util/manifest.go new file mode 100644 index 00000000000..9a87949f8ed --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/release/v1/util/manifest.go @@ -0,0 +1,72 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +// SimpleHead defines what the structure of the head of a manifest file +type SimpleHead struct { + Version string `json:"apiVersion"` + Kind string `json:"kind,omitempty"` + Metadata *struct { + Name string `json:"name"` + Annotations map[string]string `json:"annotations"` + } `json:"metadata,omitempty"` +} + +var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*") + +// SplitManifests takes a string of manifest and returns a map contains individual manifests +func SplitManifests(bigFile string) map[string]string { + // Basically, we're quickly splitting a stream of YAML documents into an + // array of YAML docs. The file name is just a place holder, but should be + // integer-sortable so that manifests get output in the same order as the + // input (see `BySplitManifestsOrder`). + tpl := "manifest-%d" + res := map[string]string{} + // Making sure that any extra whitespace in YAML stream doesn't interfere in splitting documents correctly. + bigFileTmp := strings.TrimSpace(bigFile) + docs := sep.Split(bigFileTmp, -1) + var count int + for _, d := range docs { + if d == "" { + continue + } + + d = strings.TrimSpace(d) + res[fmt.Sprintf(tpl, count)] = d + count = count + 1 + } + return res +} + +// BySplitManifestsOrder sorts by in-file manifest order, as provided in function `SplitManifests` +type BySplitManifestsOrder []string + +func (a BySplitManifestsOrder) Len() int { return len(a) } +func (a BySplitManifestsOrder) Less(i, j int) bool { + // Split `manifest-%d` + anum, _ := strconv.ParseInt(a[i][len("manifest-"):], 10, 0) + bnum, _ := strconv.ParseInt(a[j][len("manifest-"):], 10, 0) + return anum < bnum +} +func (a BySplitManifestsOrder) Swap(i, j int) { a[i], a[j] = a[j], a[i] } diff --git a/vendor/helm.sh/helm/v4/pkg/release/v1/util/manifest_sorter.go b/vendor/helm.sh/helm/v4/pkg/release/v1/util/manifest_sorter.go new file mode 100644 index 00000000000..6f7b4ea8b2f --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/release/v1/util/manifest_sorter.go @@ -0,0 +1,244 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + "log/slog" + "path" + "sort" + "strconv" + "strings" + + "sigs.k8s.io/yaml" + + "helm.sh/helm/v4/pkg/chart/common" + release "helm.sh/helm/v4/pkg/release/v1" +) + +// Manifest represents a manifest file, which has a name and some content. +type Manifest struct { + Name string + Content string + Head *SimpleHead +} + +// manifestFile represents a file that contains a manifest. +type manifestFile struct { + entries map[string]string + path string +} + +// result is an intermediate structure used during sorting. +type result struct { + hooks []*release.Hook + generic []Manifest +} + +// TODO: Refactor this out. It's here because naming conventions were not followed through. +// So fix the Test hook names and then remove this. +var events = map[string]release.HookEvent{ + release.HookPreInstall.String(): release.HookPreInstall, + release.HookPostInstall.String(): release.HookPostInstall, + release.HookPreDelete.String(): release.HookPreDelete, + release.HookPostDelete.String(): release.HookPostDelete, + release.HookPreUpgrade.String(): release.HookPreUpgrade, + release.HookPostUpgrade.String(): release.HookPostUpgrade, + release.HookPreRollback.String(): release.HookPreRollback, + release.HookPostRollback.String(): release.HookPostRollback, + release.HookTest.String(): release.HookTest, + // Support test-success for backward compatibility with Helm 2 tests + "test-success": release.HookTest, +} + +// SortManifests takes a map of filename/YAML contents, splits the file +// by manifest entries, and sorts the entries into hook types. +// +// The resulting hooks struct will be populated with all of the generated hooks. +// Any file that does not declare one of the hook types will be placed in the +// 'generic' bucket. +// +// Files that do not parse into the expected format are simply placed into a map and +// returned. +func SortManifests(files map[string]string, _ common.VersionSet, ordering KindSortOrder) ([]*release.Hook, []Manifest, error) { + result := &result{} + + var sortedFilePaths []string + for filePath := range files { + sortedFilePaths = append(sortedFilePaths, filePath) + } + sort.Strings(sortedFilePaths) + + for _, filePath := range sortedFilePaths { + content := files[filePath] + + // Skip partials. We could return these as a separate map, but there doesn't + // seem to be any need for that at this time. + if strings.HasPrefix(path.Base(filePath), "_") { + continue + } + // Skip empty files and log this. + if strings.TrimSpace(content) == "" { + continue + } + + manifestFile := &manifestFile{ + entries: SplitManifests(content), + path: filePath, + } + + if err := manifestFile.sort(result); err != nil { + return result.hooks, result.generic, err + } + } + + return sortHooksByKind(result.hooks, ordering), sortManifestsByKind(result.generic, ordering), nil +} + +// sort takes a manifestFile object which may contain multiple resource definition +// entries and sorts each entry by hook types, and saves the resulting hooks and +// generic manifests (or non-hooks) to the result struct. +// +// To determine hook type, it looks for a YAML structure like this: +// +// kind: SomeKind +// apiVersion: v1 +// metadata: +// annotations: +// helm.sh/hook: pre-install +// +// To determine the policy to delete the hook, it looks for a YAML structure like this: +// +// kind: SomeKind +// apiVersion: v1 +// metadata: +// annotations: +// helm.sh/hook-delete-policy: hook-succeeded +// +// To determine the policy to output logs of the hook (for Pod and Job only), it looks for a YAML structure like this: +// +// kind: Pod +// apiVersion: v1 +// metadata: +// annotations: +// helm.sh/hook-output-log-policy: hook-succeeded,hook-failed +func (file *manifestFile) sort(result *result) error { + // Go through manifests in order found in file (function `SplitManifests` creates integer-sortable keys) + var sortedEntryKeys []string + for entryKey := range file.entries { + sortedEntryKeys = append(sortedEntryKeys, entryKey) + } + sort.Sort(BySplitManifestsOrder(sortedEntryKeys)) + + for _, entryKey := range sortedEntryKeys { + m := file.entries[entryKey] + + var entry SimpleHead + if err := yaml.Unmarshal([]byte(m), &entry); err != nil { + return fmt.Errorf("YAML parse error on %s: %w", file.path, err) + } + + if !hasAnyAnnotation(entry) { + result.generic = append(result.generic, Manifest{ + Name: file.path, + Content: m, + Head: &entry, + }) + continue + } + + hookTypes, ok := entry.Metadata.Annotations[release.HookAnnotation] + if !ok { + result.generic = append(result.generic, Manifest{ + Name: file.path, + Content: m, + Head: &entry, + }) + continue + } + + hw := calculateHookWeight(entry) + + h := &release.Hook{ + Name: entry.Metadata.Name, + Kind: entry.Kind, + Path: file.path, + Manifest: m, + Events: []release.HookEvent{}, + Weight: hw, + DeletePolicies: []release.HookDeletePolicy{}, + OutputLogPolicies: []release.HookOutputLogPolicy{}, + } + + isUnknownHook := false + for hookType := range strings.SplitSeq(hookTypes, ",") { + hookType = strings.ToLower(strings.TrimSpace(hookType)) + e, ok := events[hookType] + if !ok { + isUnknownHook = true + break + } + h.Events = append(h.Events, e) + } + + if isUnknownHook { + slog.Info("skipping unknown hooks", "hookTypes", hookTypes) + continue + } + + result.hooks = append(result.hooks, h) + + operateAnnotationValues(entry, release.HookDeleteAnnotation, func(value string) { + h.DeletePolicies = append(h.DeletePolicies, release.HookDeletePolicy(value)) + }) + + operateAnnotationValues(entry, release.HookOutputLogAnnotation, func(value string) { + h.OutputLogPolicies = append(h.OutputLogPolicies, release.HookOutputLogPolicy(value)) + }) + } + + return nil +} + +// hasAnyAnnotation returns true if the given entry has any annotations at all. +func hasAnyAnnotation(entry SimpleHead) bool { + return entry.Metadata != nil && + entry.Metadata.Annotations != nil && + len(entry.Metadata.Annotations) != 0 +} + +// calculateHookWeight finds the weight in the hook weight annotation. +// +// If no weight is found, the assigned weight is 0 +func calculateHookWeight(entry SimpleHead) int { + hws := entry.Metadata.Annotations[release.HookWeightAnnotation] + hw, err := strconv.Atoi(hws) + if err != nil { + hw = 0 + } + return hw +} + +// operateAnnotationValues finds the given annotation and runs the operate function with the value of that annotation +func operateAnnotationValues(entry SimpleHead, annotation string, operate func(p string)) { + if dps, ok := entry.Metadata.Annotations[annotation]; ok { + for dp := range strings.SplitSeq(dps, ",") { + dp = strings.ToLower(strings.TrimSpace(dp)) + operate(dp) + } + } +} diff --git a/vendor/helm.sh/helm/v4/pkg/release/v1/util/sorter.go b/vendor/helm.sh/helm/v4/pkg/release/v1/util/sorter.go new file mode 100644 index 00000000000..47506fbf29e --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/release/v1/util/sorter.go @@ -0,0 +1,61 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util // import "helm.sh/helm/v4/pkg/release/v1/util" + +import ( + "sort" + + rspb "helm.sh/helm/v4/pkg/release/v1" +) + +// Reverse reverses the list of releases sorted by the sort func. +func Reverse(list []*rspb.Release, sortFn func([]*rspb.Release)) { + sortFn(list) + for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 { + list[i], list[j] = list[j], list[i] + } +} + +// SortByName returns the list of releases sorted +// in lexicographical order. +func SortByName(list []*rspb.Release) { + sort.Slice(list, func(i, j int) bool { + return list[i].Name < list[j].Name + }) +} + +// SortByDate returns the list of releases sorted by a +// release's last deployed time (in seconds). +func SortByDate(list []*rspb.Release) { + sort.Slice(list, func(i, j int) bool { + ti := list[i].Info.LastDeployed.Unix() + tj := list[j].Info.LastDeployed.Unix() + if ti != tj { + return ti < tj + } + // Use name as tie-breaker for stable sorting + return list[i].Name < list[j].Name + }) +} + +// SortByRevision returns the list of releases sorted by a +// release's revision number (release.Version). +func SortByRevision(list []*rspb.Release) { + sort.Slice(list, func(i, j int) bool { + return list[i].Version < list[j].Version + }) +} diff --git a/vendor/helm.sh/helm/v4/pkg/repo/v1/chartrepo.go b/vendor/helm.sh/helm/v4/pkg/repo/v1/chartrepo.go new file mode 100644 index 00000000000..deef7474e3d --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/repo/v1/chartrepo.go @@ -0,0 +1,276 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package repo // import "helm.sh/helm/v4/pkg/repo/v1" + +import ( + "bytes" + "crypto/rand" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "log/slog" + "net/url" + "os" + "path/filepath" + "strings" + + "helm.sh/helm/v4/internal/fileutil" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/helmpath" +) + +// Entry represents a collection of parameters for chart repository +type Entry struct { + Name string `json:"name"` + URL string `json:"url"` + Username string `json:"username"` + Password string `json:"password"` + CertFile string `json:"certFile"` + KeyFile string `json:"keyFile"` + CAFile string `json:"caFile"` + InsecureSkipTLSVerify bool `json:"insecure_skip_tls_verify"` + PassCredentialsAll bool `json:"pass_credentials_all"` +} + +// ChartRepository represents a chart repository +type ChartRepository struct { + Config *Entry + IndexFile *IndexFile + Client getter.Getter + CachePath string +} + +// NewChartRepository constructs ChartRepository +func NewChartRepository(cfg *Entry, getters getter.Providers) (*ChartRepository, error) { + u, err := url.Parse(cfg.URL) + if err != nil { + return nil, fmt.Errorf("invalid chart URL format: %s", cfg.URL) + } + + client, err := getters.ByScheme(u.Scheme) + if err != nil { + return nil, fmt.Errorf("could not find protocol handler for: %s", u.Scheme) + } + + return &ChartRepository{ + Config: cfg, + IndexFile: NewIndexFile(), + Client: client, + CachePath: helmpath.CachePath("repository"), + }, nil +} + +// DownloadIndexFile fetches the index from a repository. +func (r *ChartRepository) DownloadIndexFile() (string, error) { + indexURL, err := ResolveReferenceURL(r.Config.URL, "index.yaml") + if err != nil { + return "", err + } + + resp, err := r.Client.Get(indexURL, + getter.WithURL(r.Config.URL), + getter.WithInsecureSkipVerifyTLS(r.Config.InsecureSkipTLSVerify), + getter.WithTLSClientConfig(r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile), + getter.WithBasicAuth(r.Config.Username, r.Config.Password), + getter.WithPassCredentialsAll(r.Config.PassCredentialsAll), + ) + if err != nil { + return "", err + } + + index, err := io.ReadAll(resp) + if err != nil { + return "", err + } + + indexFile, err := loadIndex(index, r.Config.URL) + if err != nil { + return "", err + } + + // Create the chart list file in the cache directory + var charts strings.Builder + for name := range indexFile.Entries { + fmt.Fprintln(&charts, name) + } + chartsFile := filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name)) + os.MkdirAll(filepath.Dir(chartsFile), 0755) + + fileutil.AtomicWriteFile(chartsFile, bytes.NewReader([]byte(charts.String())), 0644) + + // Create the index file in the cache directory + fname := filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name)) + os.MkdirAll(filepath.Dir(fname), 0755) + return fname, fileutil.AtomicWriteFile(fname, bytes.NewReader(index), 0644) +} + +type findChartInRepoURLOptions struct { + Username string + Password string + PassCredentialsAll bool + InsecureSkipTLSVerify bool + CertFile string + KeyFile string + CAFile string + ChartVersion string +} + +type FindChartInRepoURLOption func(*findChartInRepoURLOptions) + +// WithChartVersion specifies the chart version to find +func WithChartVersion(chartVersion string) FindChartInRepoURLOption { + return func(options *findChartInRepoURLOptions) { + options.ChartVersion = chartVersion + } +} + +// WithUsernamePassword specifies the username/password credntials for the repository +func WithUsernamePassword(username, password string) FindChartInRepoURLOption { + return func(options *findChartInRepoURLOptions) { + options.Username = username + options.Password = password + } +} + +// WithPassCredentialsAll flags whether credentials should be passed on to other domains +func WithPassCredentialsAll(passCredentialsAll bool) FindChartInRepoURLOption { + return func(options *findChartInRepoURLOptions) { + options.PassCredentialsAll = passCredentialsAll + } +} + +// WithClientTLS species the cert, key, and CA files for client mTLS +func WithClientTLS(certFile, keyFile, caFile string) FindChartInRepoURLOption { + return func(options *findChartInRepoURLOptions) { + options.CertFile = certFile + options.KeyFile = keyFile + options.CAFile = caFile + } +} + +// WithInsecureSkipTLSVerify skips TLS verification for repository communication +func WithInsecureSkipTLSVerify(insecureSkipTLSVerify bool) FindChartInRepoURLOption { + return func(options *findChartInRepoURLOptions) { + options.InsecureSkipTLSVerify = insecureSkipTLSVerify + } +} + +// FindChartInRepoURL finds chart in chart repository pointed by repoURL +// without adding repo to repositories +func FindChartInRepoURL(repoURL string, chartName string, getters getter.Providers, options ...FindChartInRepoURLOption) (string, error) { + + opts := findChartInRepoURLOptions{} + for _, option := range options { + option(&opts) + } + + // Download and write the index file to a temporary location + buf := make([]byte, 20) + rand.Read(buf) + name := strings.ReplaceAll(base64.StdEncoding.EncodeToString(buf), "/", "-") + + c := Entry{ + URL: repoURL, + Username: opts.Username, + Password: opts.Password, + PassCredentialsAll: opts.PassCredentialsAll, + CertFile: opts.CertFile, + KeyFile: opts.KeyFile, + CAFile: opts.CAFile, + Name: name, + InsecureSkipTLSVerify: opts.InsecureSkipTLSVerify, + } + r, err := NewChartRepository(&c, getters) + if err != nil { + return "", err + } + idx, err := r.DownloadIndexFile() + if err != nil { + return "", fmt.Errorf("looks like %q is not a valid chart repository or cannot be reached: %w", repoURL, err) + } + defer func() { + os.RemoveAll(filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name))) + os.RemoveAll(filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name))) + }() + + // Read the index file for the repository to get chart information and return chart URL + repoIndex, err := LoadIndexFile(idx) + if err != nil { + return "", err + } + + errMsg := fmt.Sprintf("chart %q", chartName) + if opts.ChartVersion != "" { + errMsg = fmt.Sprintf("%s version %q", errMsg, opts.ChartVersion) + } + cv, err := repoIndex.Get(chartName, opts.ChartVersion) + if err != nil { + return "", ChartNotFoundError{ + Chart: errMsg, + RepoURL: repoURL, + } + } + + if len(cv.URLs) == 0 { + return "", fmt.Errorf("%s has no downloadable URLs", errMsg) + } + + chartURL := cv.URLs[0] + + absoluteChartURL, err := ResolveReferenceURL(repoURL, chartURL) + if err != nil { + return "", fmt.Errorf("failed to make chart URL absolute: %w", err) + } + + return absoluteChartURL, nil +} + +// ResolveReferenceURL resolves refURL relative to baseURL. +// If refURL is absolute, it simply returns refURL. +func ResolveReferenceURL(baseURL, refURL string) (string, error) { + parsedRefURL, err := url.Parse(refURL) + if err != nil { + return "", fmt.Errorf("failed to parse %s as URL: %w", refURL, err) + } + + if parsedRefURL.IsAbs() { + return refURL, nil + } + + parsedBaseURL, err := url.Parse(baseURL) + if err != nil { + return "", fmt.Errorf("failed to parse %s as URL: %w", baseURL, err) + } + + // We need a trailing slash for ResolveReference to work, but make sure there isn't already one + parsedBaseURL.RawPath = strings.TrimSuffix(parsedBaseURL.RawPath, "/") + "/" + parsedBaseURL.Path = strings.TrimSuffix(parsedBaseURL.Path, "/") + "/" + + resolvedURL := parsedBaseURL.ResolveReference(parsedRefURL) + resolvedURL.RawQuery = parsedBaseURL.RawQuery + return resolvedURL.String(), nil +} + +func (e *Entry) String() string { + buf, err := json.Marshal(e) + if err != nil { + slog.Error("failed to marshal entry", slog.Any("error", err)) + panic(err) + } + return string(buf) +} diff --git a/vendor/helm.sh/helm/v4/pkg/repo/v1/doc.go b/vendor/helm.sh/helm/v4/pkg/repo/v1/doc.go new file mode 100644 index 00000000000..fc54bbf7afc --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/repo/v1/doc.go @@ -0,0 +1,94 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package repo implements the Helm Chart Repository. + +A chart repository is an HTTP server that provides information on charts. A local +repository cache is an on-disk representation of a chart repository. + +There are two important file formats for chart repositories. + +The first is the 'index.yaml' format, which is expressed like this: + + apiVersion: v1 + entries: + frobnitz: + - created: 2016-09-29T12:14:34.830161306-06:00 + description: This is a frobnitz. + digest: 587bd19a9bd9d2bc4a6d25ab91c8c8e7042c47b4ac246e37bf8e1e74386190f4 + home: http://example.com + keywords: + - frobnitz + - sprocket + - dodad + maintainers: + - email: helm@example.com + name: The Helm Team + - email: nobody@example.com + name: Someone Else + name: frobnitz + urls: + - http://example-charts.com/testdata/repository/frobnitz-1.2.3.tgz + version: 1.2.3 + sprocket: + - created: 2016-09-29T12:14:34.830507606-06:00 + description: This is a sprocket" + digest: 8505ff813c39502cc849a38e1e4a8ac24b8e6e1dcea88f4c34ad9b7439685ae6 + home: http://example.com + keywords: + - frobnitz + - sprocket + - dodad + maintainers: + - email: helm@example.com + name: The Helm Team + - email: nobody@example.com + name: Someone Else + name: sprocket + urls: + - http://example-charts.com/testdata/repository/sprocket-1.2.0.tgz + version: 1.2.0 + generated: 2016-09-29T12:14:34.829721375-06:00 + +An index.yaml file contains the necessary descriptive information about what +charts are available in a repository, and how to get them. + +The second file format is the repositories.yaml file format. This file is for +facilitating local cached copies of one or more chart repositories. + +The format of a repository.yaml file is: + + apiVersion: v1 + generated: TIMESTAMP + repositories: + - name: stable + url: http://example.com/charts + cache: stable-index.yaml + - name: incubator + url: http://example.com/incubator + cache: incubator-index.yaml + +This file maps three bits of information about a repository: + + - The name the user uses to refer to it + - The fully qualified URL to the repository (index.yaml will be appended) + - The name of the local cachefile + +The format for both files was changed after Helm v2.0.0-Alpha.4. Helm is not +backwards compatible with those earlier versions. +*/ +package repo diff --git a/vendor/helm.sh/helm/v4/pkg/repo/v1/error.go b/vendor/helm.sh/helm/v4/pkg/repo/v1/error.go new file mode 100644 index 00000000000..16264ed2603 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/repo/v1/error.go @@ -0,0 +1,35 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package repo + +import ( + "fmt" +) + +type ChartNotFoundError struct { + RepoURL string + Chart string +} + +func (e ChartNotFoundError) Error() string { + return fmt.Sprintf("%s not found in %s repository", e.Chart, e.RepoURL) +} + +func (e ChartNotFoundError) Is(err error) bool { + _, ok := err.(ChartNotFoundError) + return ok +} diff --git a/vendor/helm.sh/helm/v4/pkg/repo/v1/index.go b/vendor/helm.sh/helm/v4/pkg/repo/v1/index.go new file mode 100644 index 00000000000..7969d64e9dc --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/repo/v1/index.go @@ -0,0 +1,419 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package repo + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "log/slog" + "os" + "path" + "path/filepath" + "sort" + "strings" + "time" + + "github.com/Masterminds/semver/v3" + "sigs.k8s.io/yaml" + + "helm.sh/helm/v4/internal/fileutil" + "helm.sh/helm/v4/internal/urlutil" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" + "helm.sh/helm/v4/pkg/provenance" +) + +// APIVersionV1 is the v1 API version for index and repository files. +const APIVersionV1 = "v1" + +var ( + // ErrNoAPIVersion indicates that an API version was not specified. + ErrNoAPIVersion = errors.New("no API version specified") + // ErrNoChartVersion indicates that a chart with the given version is not found. + ErrNoChartVersion = errors.New("no chart version found") + // ErrNoChartName indicates that a chart with the given name is not found. + ErrNoChartName = errors.New("no chart name found") + // ErrEmptyIndexYaml indicates that the content of index.yaml is empty. + ErrEmptyIndexYaml = errors.New("empty index.yaml file") +) + +// ChartVersions is a list of versioned chart references. +// Implements a sorter on Version. +type ChartVersions []*ChartVersion + +// Len returns the length. +func (c ChartVersions) Len() int { return len(c) } + +// Swap swaps the position of two items in the versions slice. +func (c ChartVersions) Swap(i, j int) { c[i], c[j] = c[j], c[i] } + +// Less returns true if the version of entry a is less than the version of entry b. +func (c ChartVersions) Less(a, b int) bool { + // Failed parse pushes to the back. + i, err := semver.NewVersion(c[a].Version) + if err != nil { + return true + } + j, err := semver.NewVersion(c[b].Version) + if err != nil { + return false + } + return i.LessThan(j) +} + +// IndexFile represents the index file in a chart repository +type IndexFile struct { + // This is used ONLY for validation against chartmuseum's index files and is discarded after validation. + ServerInfo map[string]interface{} `json:"serverInfo,omitempty"` + APIVersion string `json:"apiVersion"` + Generated time.Time `json:"generated"` + Entries map[string]ChartVersions `json:"entries"` + PublicKeys []string `json:"publicKeys,omitempty"` + + // Annotations are additional mappings uninterpreted by Helm. They are made available for + // other applications to add information to the index file. + Annotations map[string]string `json:"annotations,omitempty"` +} + +// NewIndexFile initializes an index. +func NewIndexFile() *IndexFile { + return &IndexFile{ + APIVersion: APIVersionV1, + Generated: time.Now(), + Entries: map[string]ChartVersions{}, + PublicKeys: []string{}, + } +} + +// LoadIndexFile takes a file at the given path and returns an IndexFile object +func LoadIndexFile(path string) (*IndexFile, error) { + b, err := os.ReadFile(path) + if err != nil { + return nil, err + } + i, err := loadIndex(b, path) + if err != nil { + return nil, fmt.Errorf("error loading %s: %w", path, err) + } + return i, nil +} + +// MustAdd adds a file to the index +// This can leave the index in an unsorted state +func (i IndexFile) MustAdd(md *chart.Metadata, filename, baseURL, digest string) error { + if i.Entries == nil { + return errors.New("entries not initialized") + } + + if md.APIVersion == "" { + md.APIVersion = chart.APIVersionV1 + } + if err := md.Validate(); err != nil { + return fmt.Errorf("validate failed for %s: %w", filename, err) + } + + u := filename + if baseURL != "" { + _, file := filepath.Split(filename) + var err error + u, err = urlutil.URLJoin(baseURL, file) + if err != nil { + u = path.Join(baseURL, file) + } + } + cr := &ChartVersion{ + URLs: []string{u}, + Metadata: md, + Digest: digest, + Created: time.Now(), + } + ee := i.Entries[md.Name] + i.Entries[md.Name] = append(ee, cr) + return nil +} + +// Add adds a file to the index and logs an error. +// +// Deprecated: Use index.MustAdd instead. +func (i IndexFile) Add(md *chart.Metadata, filename, baseURL, digest string) { + if err := i.MustAdd(md, filename, baseURL, digest); err != nil { + slog.Error("skipping loading invalid entry for chart %q %q from %s: %s", md.Name, md.Version, filename, err) + } +} + +// Has returns true if the index has an entry for a chart with the given name and exact version. +func (i IndexFile) Has(name, version string) bool { + _, err := i.Get(name, version) + return err == nil +} + +// SortEntries sorts the entries by version in descending order. +// +// In canonical form, the individual version records should be sorted so that +// the most recent release for every version is in the 0th slot in the +// Entries.ChartVersions array. That way, tooling can predict the newest +// version without needing to parse SemVers. +func (i IndexFile) SortEntries() { + for _, versions := range i.Entries { + sort.Sort(sort.Reverse(versions)) + } +} + +// Get returns the ChartVersion for the given name. +// +// If version is empty, this will return the chart with the latest stable version, +// prerelease versions will be skipped. +func (i IndexFile) Get(name, version string) (*ChartVersion, error) { + vs, ok := i.Entries[name] + if !ok { + return nil, ErrNoChartName + } + if len(vs) == 0 { + return nil, ErrNoChartVersion + } + + var constraint *semver.Constraints + if version == "" { + constraint, _ = semver.NewConstraint("*") + } else { + var err error + constraint, err = semver.NewConstraint(version) + if err != nil { + return nil, err + } + } + + // when customer inputs specific version, check whether there's an exact match first + if len(version) != 0 { + for _, ver := range vs { + if version == ver.Version { + return ver, nil + } + } + } + + for _, ver := range vs { + test, err := semver.NewVersion(ver.Version) + if err != nil { + continue + } + + if constraint.Check(test) { + if len(version) != 0 { + slog.Warn("unable to find exact version requested; falling back to closest available version", "chart", name, "requested", version, "selected", ver.Version) + } + return ver, nil + } + } + return nil, fmt.Errorf("no chart version found for %s-%s", name, version) +} + +// WriteFile writes an index file to the given destination path. +// +// The mode on the file is set to 'mode'. +func (i IndexFile) WriteFile(dest string, mode os.FileMode) error { + b, err := yaml.Marshal(i) + if err != nil { + return err + } + return fileutil.AtomicWriteFile(dest, bytes.NewReader(b), mode) +} + +// WriteJSONFile writes an index file in JSON format to the given destination +// path. +// +// The mode on the file is set to 'mode'. +func (i IndexFile) WriteJSONFile(dest string, mode os.FileMode) error { + b, err := json.MarshalIndent(i, "", " ") + if err != nil { + return err + } + return fileutil.AtomicWriteFile(dest, bytes.NewReader(b), mode) +} + +// Merge merges the given index file into this index. +// +// This merges by name and version. +// +// If one of the entries in the given index does _not_ already exist, it is added. +// In all other cases, the existing record is preserved. +// +// This can leave the index in an unsorted state +func (i *IndexFile) Merge(f *IndexFile) { + for _, cvs := range f.Entries { + for _, cv := range cvs { + if !i.Has(cv.Name, cv.Version) { + e := i.Entries[cv.Name] + i.Entries[cv.Name] = append(e, cv) + } + } + } +} + +// ChartVersion represents a chart entry in the IndexFile +type ChartVersion struct { + *chart.Metadata + URLs []string `json:"urls"` + Created time.Time `json:"created,omitempty"` + Removed bool `json:"removed,omitempty"` + Digest string `json:"digest,omitempty"` + + // ChecksumDeprecated is deprecated in Helm 3, and therefore ignored. Helm 3 replaced + // this with Digest. However, with a strict YAML parser enabled, a field must be + // present on the struct for backwards compatibility. + ChecksumDeprecated string `json:"checksum,omitempty"` + + // EngineDeprecated is deprecated in Helm 3, and therefore ignored. However, with a strict + // YAML parser enabled, this field must be present. + EngineDeprecated string `json:"engine,omitempty"` + + // TillerVersionDeprecated is deprecated in Helm 3, and therefore ignored. However, with a strict + // YAML parser enabled, this field must be present. + TillerVersionDeprecated string `json:"tillerVersion,omitempty"` + + // URLDeprecated is deprecated in Helm 3, superseded by URLs. It is ignored. However, + // with a strict YAML parser enabled, this must be present on the struct. + URLDeprecated string `json:"url,omitempty"` +} + +// IndexDirectory reads a (flat) directory and generates an index. +// +// It indexes only charts that have been packaged (*.tgz). +// +// The index returned will be in an unsorted state +func IndexDirectory(dir, baseURL string) (*IndexFile, error) { + archives, err := filepath.Glob(filepath.Join(dir, "*.tgz")) + if err != nil { + return nil, err + } + moreArchives, err := filepath.Glob(filepath.Join(dir, "**/*.tgz")) + if err != nil { + return nil, err + } + archives = append(archives, moreArchives...) + + index := NewIndexFile() + for _, arch := range archives { + fname, err := filepath.Rel(dir, arch) + if err != nil { + return index, err + } + + var parentDir string + parentDir, fname = filepath.Split(fname) + // filepath.Split appends an extra slash to the end of parentDir. We want to strip that out. + parentDir = strings.TrimSuffix(parentDir, string(os.PathSeparator)) + parentURL, err := urlutil.URLJoin(baseURL, parentDir) + if err != nil { + parentURL = path.Join(baseURL, parentDir) + } + + c, err := loader.Load(arch) + if err != nil { + // Assume this is not a chart. + continue + } + hash, err := provenance.DigestFile(arch) + if err != nil { + return index, err + } + if err := index.MustAdd(c.Metadata, fname, parentURL, hash); err != nil { + return index, fmt.Errorf("failed adding to %s to index: %w", fname, err) + } + } + return index, nil +} + +// loadIndex loads an index file and does minimal validity checking. +// +// The source parameter is only used for logging. +// This will fail if API Version is not set (ErrNoAPIVersion) or if the unmarshal fails. +func loadIndex(data []byte, source string) (*IndexFile, error) { + i := &IndexFile{} + + if len(data) == 0 { + return i, ErrEmptyIndexYaml + } + + if err := jsonOrYamlUnmarshal(data, i); err != nil { + return i, err + } + + for name, cvs := range i.Entries { + for idx := len(cvs) - 1; idx >= 0; idx-- { + if cvs[idx] == nil { + slog.Warn(fmt.Sprintf("skipping loading invalid entry for chart %q from %s: empty entry", name, source)) + cvs = append(cvs[:idx], cvs[idx+1:]...) + continue + } + // When metadata section missing, initialize with no data + if cvs[idx].Metadata == nil { + cvs[idx].Metadata = &chart.Metadata{} + } + if cvs[idx].APIVersion == "" { + cvs[idx].APIVersion = chart.APIVersionV1 + } + if err := cvs[idx].Validate(); ignoreSkippableChartValidationError(err) != nil { + slog.Warn(fmt.Sprintf("skipping loading invalid entry for chart %q %q from %s: %s", name, cvs[idx].Version, source, err)) + cvs = append(cvs[:idx], cvs[idx+1:]...) + } + } + // adjust slice to only contain a set of valid versions + i.Entries[name] = cvs + } + i.SortEntries() + if i.APIVersion == "" { + return i, ErrNoAPIVersion + } + return i, nil +} + +// jsonOrYamlUnmarshal unmarshals the given byte slice containing JSON or YAML +// into the provided interface. +// +// It automatically detects whether the data is in JSON or YAML format by +// checking its validity as JSON. If the data is valid JSON, it will use the +// `encoding/json` package to unmarshal it. Otherwise, it will use the +// `sigs.k8s.io/yaml` package to unmarshal the YAML data. +func jsonOrYamlUnmarshal(b []byte, i interface{}) error { + if json.Valid(b) { + return json.Unmarshal(b, i) + } + return yaml.UnmarshalStrict(b, i) +} + +// ignoreSkippableChartValidationError inspect the given error and returns nil if +// the error isn't important for index loading +// +// In particular, charts may introduce validations that don't impact repository indexes +// And repository indexes may be generated by older/non-compliant software, which doesn't +// conform to all validations. +func ignoreSkippableChartValidationError(err error) error { + verr, ok := err.(chart.ValidationError) + if !ok { + return err + } + + // https://github.com/helm/helm/issues/12748 (JFrog repository strips alias field) + if strings.HasPrefix(verr.Error(), "validation: more than one dependency with name or alias") { + return nil + } + + return err +} diff --git a/vendor/helm.sh/helm/v4/pkg/repo/v1/repo.go b/vendor/helm.sh/helm/v4/pkg/repo/v1/repo.go new file mode 100644 index 00000000000..38d2b0ca19d --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/repo/v1/repo.go @@ -0,0 +1,125 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package repo // import "helm.sh/helm/v4/pkg/repo/v1" + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "sigs.k8s.io/yaml" +) + +// File represents the repositories.yaml file +type File struct { + APIVersion string `json:"apiVersion"` + Generated time.Time `json:"generated"` + Repositories []*Entry `json:"repositories"` +} + +// NewFile generates an empty repositories file. +// +// Generated and APIVersion are automatically set. +func NewFile() *File { + return &File{ + APIVersion: APIVersionV1, + Generated: time.Now(), + Repositories: []*Entry{}, + } +} + +// LoadFile takes a file at the given path and returns a File object +func LoadFile(path string) (*File, error) { + r := new(File) + b, err := os.ReadFile(path) + if err != nil { + return r, fmt.Errorf("couldn't load repositories file (%s): %w", path, err) + } + + err = yaml.Unmarshal(b, r) + return r, err +} + +// Add adds one or more repo entries to a repo file. +func (r *File) Add(re ...*Entry) { + r.Repositories = append(r.Repositories, re...) +} + +// Update attempts to replace one or more repo entries in a repo file. If an +// entry with the same name doesn't exist in the repo file it will add it. +func (r *File) Update(re ...*Entry) { + for _, target := range re { + r.update(target) + } +} + +func (r *File) update(e *Entry) { + for j, repo := range r.Repositories { + if repo.Name == e.Name { + r.Repositories[j] = e + return + } + } + r.Add(e) +} + +// Has returns true if the given name is already a repository name. +func (r *File) Has(name string) bool { + entry := r.Get(name) + return entry != nil +} + +// Get returns an entry with the given name if it exists, otherwise returns nil +func (r *File) Get(name string) *Entry { + for _, entry := range r.Repositories { + if entry.Name == name { + return entry + } + } + return nil +} + +// Remove removes the entry from the list of repositories. +func (r *File) Remove(name string) bool { + cp := []*Entry{} + found := false + for _, rf := range r.Repositories { + if rf == nil { + continue + } + if rf.Name == name { + found = true + continue + } + cp = append(cp, rf) + } + r.Repositories = cp + return found +} + +// WriteFile writes a repositories file to the given path. +func (r *File) WriteFile(path string, perm os.FileMode) error { + data, err := yaml.Marshal(r) + if err != nil { + return err + } + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + return err + } + return os.WriteFile(path, data, perm) +} diff --git a/vendor/helm.sh/helm/v4/pkg/storage/driver/cfgmaps.go b/vendor/helm.sh/helm/v4/pkg/storage/driver/cfgmaps.go new file mode 100644 index 00000000000..f82ade5e939 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/storage/driver/cfgmaps.go @@ -0,0 +1,288 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package driver // import "helm.sh/helm/v4/pkg/storage/driver" + +import ( + "context" + "fmt" + "log/slog" + "strconv" + "strings" + "time" + + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kblabels "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/validation" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + + "helm.sh/helm/v4/internal/logging" + "helm.sh/helm/v4/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" +) + +var _ Driver = (*ConfigMaps)(nil) + +// ConfigMapsDriverName is the string name of the driver. +const ConfigMapsDriverName = "ConfigMap" + +// ConfigMaps is a wrapper around an implementation of a kubernetes +// ConfigMapsInterface. +type ConfigMaps struct { + impl corev1.ConfigMapInterface + + // Embed a LogHolder to provide logger functionality + logging.LogHolder +} + +// NewConfigMaps initializes a new ConfigMaps wrapping an implementation of +// the kubernetes ConfigMapsInterface. +func NewConfigMaps(impl corev1.ConfigMapInterface) *ConfigMaps { + c := &ConfigMaps{ + impl: impl, + } + c.SetLogger(slog.Default().Handler()) + return c +} + +// Name returns the name of the driver. +func (cfgmaps *ConfigMaps) Name() string { + return ConfigMapsDriverName +} + +// Get fetches the release named by key. The corresponding release is returned +// or error if not found. +func (cfgmaps *ConfigMaps) Get(key string) (release.Releaser, error) { + // fetch the configmap holding the release named by key + obj, err := cfgmaps.impl.Get(context.Background(), key, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + return nil, ErrReleaseNotFound + } + + cfgmaps.Logger().Debug("failed to get release", slog.String("key", key), slog.Any("error", err)) + return nil, err + } + // found the configmap, decode the base64 data string + r, err := decodeRelease(obj.Data["release"]) + if err != nil { + cfgmaps.Logger().Debug("failed to decode data", slog.String("key", key), slog.Any("error", err)) + return nil, err + } + r.Labels = filterSystemLabels(obj.Labels) + // return the release object + return r, nil +} + +// List fetches all releases and returns the list releases such +// that filter(release) == true. An error is returned if the +// configmap fails to retrieve the releases. +func (cfgmaps *ConfigMaps) List(filter func(release.Releaser) bool) ([]release.Releaser, error) { + lsel := kblabels.Set{"owner": "helm"}.AsSelector() + opts := metav1.ListOptions{LabelSelector: lsel.String()} + + list, err := cfgmaps.impl.List(context.Background(), opts) + if err != nil { + cfgmaps.Logger().Debug("failed to list releases", slog.Any("error", err)) + return nil, err + } + + var results []release.Releaser + + // iterate over the configmaps object list + // and decode each release + for _, item := range list.Items { + rls, err := decodeRelease(item.Data["release"]) + if err != nil { + cfgmaps.Logger().Debug("failed to decode release", slog.Any("item", item), slog.Any("error", err)) + continue + } + + rls.Labels = item.Labels + + if filter(rls) { + results = append(results, rls) + } + } + return results, nil +} + +// Query fetches all releases that match the provided map of labels. +// An error is returned if the configmap fails to retrieve the releases. +func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]release.Releaser, error) { + ls := kblabels.Set{} + for k, v := range labels { + if errs := validation.IsValidLabelValue(v); len(errs) != 0 { + return nil, fmt.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; ")) + } + ls[k] = v + } + + opts := metav1.ListOptions{LabelSelector: ls.AsSelector().String()} + + list, err := cfgmaps.impl.List(context.Background(), opts) + if err != nil { + cfgmaps.Logger().Debug("failed to query with labels", slog.Any("error", err)) + return nil, err + } + + if len(list.Items) == 0 { + return nil, ErrReleaseNotFound + } + + var results []release.Releaser + for _, item := range list.Items { + rls, err := decodeRelease(item.Data["release"]) + if err != nil { + cfgmaps.Logger().Debug("failed to decode release", slog.Any("error", err)) + continue + } + rls.Labels = item.Labels + results = append(results, rls) + } + return results, nil +} + +// Create creates a new ConfigMap holding the release. If the +// ConfigMap already exists, ErrReleaseExists is returned. +func (cfgmaps *ConfigMaps) Create(key string, rls release.Releaser) error { + // set labels for configmaps object meta data + var lbs labels + + rac, err := release.NewAccessor(rls) + if err != nil { + return err + } + + lbs.init() + lbs.fromMap(rac.Labels()) + lbs.set("createdAt", fmt.Sprintf("%v", time.Now().Unix())) + + rel, err := releaserToV1Release(rls) + if err != nil { + return err + } + + // create a new configmap to hold the release + obj, err := newConfigMapsObject(key, rel, lbs) + if err != nil { + cfgmaps.Logger().Debug("failed to encode release", slog.String("name", rac.Name()), slog.Any("error", err)) + return err + } + // push the configmap object out into the kubiverse + if _, err := cfgmaps.impl.Create(context.Background(), obj, metav1.CreateOptions{}); err != nil { + if apierrors.IsAlreadyExists(err) { + return ErrReleaseExists + } + + cfgmaps.Logger().Debug("failed to create release", slog.Any("error", err)) + return err + } + return nil +} + +// Update updates the ConfigMap holding the release. If not found +// the ConfigMap is created to hold the release. +func (cfgmaps *ConfigMaps) Update(key string, rel release.Releaser) error { + // set labels for configmaps object meta data + var lbs labels + + rls, err := releaserToV1Release(rel) + if err != nil { + return err + } + + lbs.init() + lbs.fromMap(rls.Labels) + lbs.set("modifiedAt", fmt.Sprintf("%v", time.Now().Unix())) + + // create a new configmap object to hold the release + obj, err := newConfigMapsObject(key, rls, lbs) + if err != nil { + cfgmaps.Logger().Debug( + "failed to encode release", + slog.String("name", rls.Name), + slog.Any("error", err), + ) + return err + } + // push the configmap object out into the kubiverse + _, err = cfgmaps.impl.Update(context.Background(), obj, metav1.UpdateOptions{}) + if err != nil { + cfgmaps.Logger().Debug("failed to update release", slog.Any("error", err)) + return err + } + return nil +} + +// Delete deletes the ConfigMap holding the release named by key. +func (cfgmaps *ConfigMaps) Delete(key string) (rls release.Releaser, err error) { + // fetch the release to check existence + if rls, err = cfgmaps.Get(key); err != nil { + return nil, err + } + // delete the release + if err = cfgmaps.impl.Delete(context.Background(), key, metav1.DeleteOptions{}); err != nil { + return rls, err + } + return rls, nil +} + +// newConfigMapsObject constructs a kubernetes ConfigMap object +// to store a release. Each configmap data entry is the base64 +// encoded gzipped string of a release. +// +// The following labels are used within each configmap: +// +// "modifiedAt" - timestamp indicating when this configmap was last modified. (set in Update) +// "createdAt" - timestamp indicating when this configmap was created. (set in Create) +// "version" - version of the release. +// "status" - status of the release (see pkg/release/status.go for variants) +// "owner" - owner of the configmap, currently "helm". +// "name" - name of the release. +func newConfigMapsObject(key string, rls *rspb.Release, lbs labels) (*v1.ConfigMap, error) { + const owner = "helm" + + // encode the release + s, err := encodeRelease(rls) + if err != nil { + return nil, err + } + + if lbs == nil { + lbs.init() + } + + // apply custom labels + lbs.fromMap(rls.Labels) + + // apply labels + lbs.set("name", rls.Name) + lbs.set("owner", owner) + lbs.set("status", rls.Info.Status.String()) + lbs.set("version", strconv.Itoa(rls.Version)) + + // create and return configmap object + return &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: key, + Labels: lbs.toMap(), + }, + Data: map[string]string{"release": s}, + }, nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/storage/driver/driver.go b/vendor/helm.sh/helm/v4/pkg/storage/driver/driver.go new file mode 100644 index 00000000000..6efd1dbaa6f --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/storage/driver/driver.go @@ -0,0 +1,120 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package driver // import "helm.sh/helm/v4/pkg/storage/driver" + +import ( + "errors" + "fmt" + + "helm.sh/helm/v4/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" +) + +var ( + // ErrReleaseNotFound indicates that a release is not found. + ErrReleaseNotFound = errors.New("release: not found") + // ErrReleaseExists indicates that a release already exists. + ErrReleaseExists = errors.New("release: already exists") + // ErrInvalidKey indicates that a release key could not be parsed. + ErrInvalidKey = errors.New("release: invalid key") + // ErrNoDeployedReleases indicates that there are no releases with the given key in the deployed state + ErrNoDeployedReleases = errors.New("has no deployed releases") +) + +// StorageDriverError records an error and the release name that caused it +type StorageDriverError struct { + ReleaseName string + Err error +} + +func (e *StorageDriverError) Error() string { + return fmt.Sprintf("%q %s", e.ReleaseName, e.Err.Error()) +} + +func (e *StorageDriverError) Unwrap() error { return e.Err } + +func NewErrNoDeployedReleases(releaseName string) error { + return &StorageDriverError{ + ReleaseName: releaseName, + Err: ErrNoDeployedReleases, + } +} + +// Creator is the interface that wraps the Create method. +// +// Create stores the release or returns ErrReleaseExists +// if an identical release already exists. +type Creator interface { + Create(key string, rls release.Releaser) error +} + +// Updator is the interface that wraps the Update method. +// +// Update updates an existing release or returns +// ErrReleaseNotFound if the release does not exist. +type Updator interface { + Update(key string, rls release.Releaser) error +} + +// Deletor is the interface that wraps the Delete method. +// +// Delete deletes the release named by key or returns +// ErrReleaseNotFound if the release does not exist. +type Deletor interface { + Delete(key string) (release.Releaser, error) +} + +// Queryor is the interface that wraps the Get and List methods. +// +// Get returns the release named by key or returns ErrReleaseNotFound +// if the release does not exist. +// +// List returns the set of all releases that satisfy the filter predicate. +// +// Query returns the set of all releases that match the provided label set. +type Queryor interface { + Get(key string) (release.Releaser, error) + List(filter func(release.Releaser) bool) ([]release.Releaser, error) + Query(labels map[string]string) ([]release.Releaser, error) +} + +// Driver is the interface composed of Creator, Updator, Deletor, and Queryor +// interfaces. It defines the behavior for storing, updating, deleted, +// and retrieving Helm releases from some underlying storage mechanism, +// e.g. memory, configmaps. +type Driver interface { + Creator + Updator + Deletor + Queryor + Name() string +} + +// releaserToV1Release is a helper function to convert a v1 release passed by interface +// into the type object. +func releaserToV1Release(rel release.Releaser) (*rspb.Release, error) { + switch r := rel.(type) { + case rspb.Release: + return &r, nil + case *rspb.Release: + return r, nil + case nil: + return nil, nil + default: + return nil, fmt.Errorf("unsupported release type: %T", rel) + } +} diff --git a/vendor/helm.sh/helm/v4/pkg/storage/driver/labels.go b/vendor/helm.sh/helm/v4/pkg/storage/driver/labels.go new file mode 100644 index 00000000000..eb7118fe5fa --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/storage/driver/labels.go @@ -0,0 +1,48 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package driver + +// labels is a map of key value pairs to be included as metadata in a configmap object. +type labels map[string]string + +func (lbs *labels) init() { *lbs = labels(make(map[string]string)) } +func (lbs labels) get(key string) string { return lbs[key] } +func (lbs labels) set(key, val string) { lbs[key] = val } + +func (lbs labels) keys() (ls []string) { + for key := range lbs { + ls = append(ls, key) + } + return +} + +func (lbs labels) match(set labels) bool { + for _, key := range set.keys() { + if lbs.get(key) != set.get(key) { + return false + } + } + return true +} + +func (lbs labels) toMap() map[string]string { return lbs } + +func (lbs *labels) fromMap(kvs map[string]string) { + for k, v := range kvs { + lbs.set(k, v) + } +} diff --git a/vendor/helm.sh/helm/v4/pkg/storage/driver/memory.go b/vendor/helm.sh/helm/v4/pkg/storage/driver/memory.go new file mode 100644 index 00000000000..7ea4a014a61 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/storage/driver/memory.go @@ -0,0 +1,255 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package driver + +import ( + "log/slog" + "strconv" + "strings" + "sync" + + "helm.sh/helm/v4/internal/logging" + "helm.sh/helm/v4/pkg/release" +) + +var _ Driver = (*Memory)(nil) + +const ( + // MemoryDriverName is the string name of this driver. + MemoryDriverName = "Memory" + + defaultNamespace = "default" +) + +// A map of release names to list of release records +type memReleases map[string]records + +// Memory is the in-memory storage driver implementation. +type Memory struct { + sync.RWMutex + namespace string + // A map of namespaces to releases + cache map[string]memReleases + // Embed a LogHolder to provide logger functionality + logging.LogHolder +} + +// NewMemory initializes a new memory driver. +func NewMemory() *Memory { + m := &Memory{cache: map[string]memReleases{}, namespace: "default"} + m.SetLogger(slog.Default().Handler()) + return m +} + +// SetNamespace sets a specific namespace in which releases will be accessed. +// An empty string indicates all namespaces (for the list operation) +func (mem *Memory) SetNamespace(ns string) { + mem.namespace = ns +} + +// Name returns the name of the driver. +func (mem *Memory) Name() string { + return MemoryDriverName +} + +// Get returns the release named by key or returns ErrReleaseNotFound. +func (mem *Memory) Get(key string) (release.Releaser, error) { + defer unlock(mem.rlock()) + + keyWithoutPrefix := strings.TrimPrefix(key, "sh.helm.release.v1.") + switch elems := strings.Split(keyWithoutPrefix, ".v"); len(elems) { + case 2: + name, ver := elems[0], elems[1] + if _, err := strconv.Atoi(ver); err != nil { + return nil, ErrInvalidKey + } + if recs, ok := mem.cache[mem.namespace][name]; ok { + if r := recs.Get(key); r != nil { + return r.rls, nil + } + } + return nil, ErrReleaseNotFound + default: + return nil, ErrInvalidKey + } +} + +// List returns the list of all releases such that filter(release) == true +func (mem *Memory) List(filter func(release.Releaser) bool) ([]release.Releaser, error) { + defer unlock(mem.rlock()) + + var ls []release.Releaser + for namespace := range mem.cache { + if mem.namespace != "" { + // Should only list releases of this namespace + namespace = mem.namespace + } + for _, recs := range mem.cache[namespace] { + recs.Iter(func(_ int, rec *record) bool { + if filter(rec.rls) { + ls = append(ls, rec.rls) + } + return true + }) + } + if mem.namespace != "" { + // Should only list releases of this namespace + break + } + } + return ls, nil +} + +// Query returns the set of releases that match the provided set of labels +func (mem *Memory) Query(keyvals map[string]string) ([]release.Releaser, error) { + defer unlock(mem.rlock()) + + var lbs labels + + lbs.init() + lbs.fromMap(keyvals) + + var ls []release.Releaser + for namespace := range mem.cache { + if mem.namespace != "" { + // Should only query releases of this namespace + namespace = mem.namespace + } + for _, recs := range mem.cache[namespace] { + recs.Iter(func(_ int, rec *record) bool { + // A query for a release name that doesn't exist (has been deleted) + // can cause rec to be nil. + if rec == nil { + return false + } + if rec.lbs.match(lbs) { + ls = append(ls, rec.rls) + } + return true + }) + } + if mem.namespace != "" { + // Should only query releases of this namespace + break + } + } + + if len(ls) == 0 { + return nil, ErrReleaseNotFound + } + + return ls, nil +} + +// Create creates a new release or returns ErrReleaseExists. +func (mem *Memory) Create(key string, rel release.Releaser) error { + defer unlock(mem.wlock()) + + rls, err := releaserToV1Release(rel) + if err != nil { + return err + } + // For backwards compatibility, we protect against an unset namespace + namespace := rls.Namespace + if namespace == "" { + namespace = defaultNamespace + } + mem.SetNamespace(namespace) + + if _, ok := mem.cache[namespace]; !ok { + mem.cache[namespace] = memReleases{} + } + + if recs, ok := mem.cache[namespace][rls.Name]; ok { + if err := recs.Add(newRecord(key, rls)); err != nil { + return err + } + mem.cache[namespace][rls.Name] = recs + return nil + } + mem.cache[namespace][rls.Name] = records{newRecord(key, rls)} + return nil +} + +// Update updates a release or returns ErrReleaseNotFound. +func (mem *Memory) Update(key string, rel release.Releaser) error { + defer unlock(mem.wlock()) + + rls, err := releaserToV1Release(rel) + if err != nil { + return err + } + + // For backwards compatibility, we protect against an unset namespace + namespace := rls.Namespace + if namespace == "" { + namespace = defaultNamespace + } + mem.SetNamespace(namespace) + + if _, ok := mem.cache[namespace]; ok { + if rs, ok := mem.cache[namespace][rls.Name]; ok && rs.Exists(key) { + rs.Replace(key, newRecord(key, rls)) + return nil + } + } + return ErrReleaseNotFound +} + +// Delete deletes a release or returns ErrReleaseNotFound. +func (mem *Memory) Delete(key string) (release.Releaser, error) { + defer unlock(mem.wlock()) + + keyWithoutPrefix := strings.TrimPrefix(key, "sh.helm.release.v1.") + elems := strings.Split(keyWithoutPrefix, ".v") + + if len(elems) != 2 { + return nil, ErrInvalidKey + } + + name, ver := elems[0], elems[1] + if _, err := strconv.Atoi(ver); err != nil { + return nil, ErrInvalidKey + } + if _, ok := mem.cache[mem.namespace]; ok { + if recs, ok := mem.cache[mem.namespace][name]; ok { + if r := recs.Remove(key); r != nil { + // recs.Remove changes the slice reference, so we have to re-assign it. + mem.cache[mem.namespace][name] = recs + return r.rls, nil + } + } + } + return nil, ErrReleaseNotFound +} + +// wlock locks mem for writing +func (mem *Memory) wlock() func() { + mem.Lock() + return func() { mem.Unlock() } +} + +// rlock locks mem for reading +func (mem *Memory) rlock() func() { + mem.RLock() + return func() { mem.RUnlock() } +} + +// unlock calls fn which reverses a mem.rlock or mem.wlock. e.g: +// ```defer unlock(mem.rlock())```, locks mem for reading at the +// call point of defer and unlocks upon exiting the block. +func unlock(fn func()) { fn() } diff --git a/vendor/helm.sh/helm/v4/pkg/storage/driver/records.go b/vendor/helm.sh/helm/v4/pkg/storage/driver/records.go new file mode 100644 index 00000000000..6b4efef3a04 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/storage/driver/records.go @@ -0,0 +1,124 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package driver // import "helm.sh/helm/v4/pkg/storage/driver" + +import ( + "sort" + "strconv" + + rspb "helm.sh/helm/v4/pkg/release/v1" +) + +// records holds a list of in-memory release records +type records []*record + +func (rs records) Len() int { return len(rs) } +func (rs records) Swap(i, j int) { rs[i], rs[j] = rs[j], rs[i] } +func (rs records) Less(i, j int) bool { return rs[i].rls.Version < rs[j].rls.Version } + +func (rs *records) Add(r *record) error { + if r == nil { + return nil + } + + if rs.Exists(r.key) { + return ErrReleaseExists + } + + *rs = append(*rs, r) + sort.Sort(*rs) + + return nil +} + +func (rs records) Get(key string) *record { + if i, ok := rs.Index(key); ok { + return rs[i] + } + return nil +} + +func (rs *records) Iter(fn func(int, *record) bool) { + cp := make([]*record, len(*rs)) + copy(cp, *rs) + + for i, r := range cp { + if !fn(i, r) { + return + } + } +} + +func (rs *records) Index(key string) (int, bool) { + for i, r := range *rs { + if r.key == key { + return i, true + } + } + return -1, false +} + +func (rs records) Exists(key string) bool { + _, ok := rs.Index(key) + return ok +} + +func (rs *records) Remove(key string) (r *record) { + if i, ok := rs.Index(key); ok { + return rs.removeAt(i) + } + return nil +} + +func (rs *records) Replace(key string, rec *record) *record { + if i, ok := rs.Index(key); ok { + old := (*rs)[i] + (*rs)[i] = rec + return old + } + return nil +} + +func (rs *records) removeAt(index int) *record { + r := (*rs)[index] + (*rs)[index] = nil + copy((*rs)[index:], (*rs)[index+1:]) + *rs = (*rs)[:len(*rs)-1] + return r +} + +// record is the data structure used to cache releases +// for the in-memory storage driver +type record struct { + key string + lbs labels + rls *rspb.Release +} + +// newRecord creates a new in-memory release record +func newRecord(key string, rls *rspb.Release) *record { + var lbs labels + + lbs.init() + lbs.set("name", rls.Name) + lbs.set("owner", "helm") + lbs.set("status", rls.Info.Status.String()) + lbs.set("version", strconv.Itoa(rls.Version)) + + // return &record{key: key, lbs: lbs, rls: proto.Clone(rls).(*rspb.Release)} + return &record{key: key, lbs: lbs, rls: rls} +} diff --git a/vendor/helm.sh/helm/v4/pkg/storage/driver/secrets.go b/vendor/helm.sh/helm/v4/pkg/storage/driver/secrets.go new file mode 100644 index 00000000000..a73f3cf050b --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/storage/driver/secrets.go @@ -0,0 +1,287 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package driver // import "helm.sh/helm/v4/pkg/storage/driver" + +import ( + "context" + "fmt" + "log/slog" + "strconv" + "strings" + "time" + + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kblabels "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/validation" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + + "helm.sh/helm/v4/internal/logging" + "helm.sh/helm/v4/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" +) + +var _ Driver = (*Secrets)(nil) + +// SecretsDriverName is the string name of the driver. +const SecretsDriverName = "Secret" + +// Secrets is a wrapper around an implementation of a kubernetes +// SecretsInterface. +type Secrets struct { + impl corev1.SecretInterface + // Embed a LogHolder to provide logger functionality + logging.LogHolder +} + +// NewSecrets initializes a new Secrets wrapping an implementation of +// the kubernetes SecretsInterface. +func NewSecrets(impl corev1.SecretInterface) *Secrets { + s := &Secrets{ + impl: impl, + } + s.SetLogger(slog.Default().Handler()) + return s +} + +// Name returns the name of the driver. +func (secrets *Secrets) Name() string { + return SecretsDriverName +} + +// Get fetches the release named by key. The corresponding release is returned +// or error if not found. +func (secrets *Secrets) Get(key string) (release.Releaser, error) { + // fetch the secret holding the release named by key + obj, err := secrets.impl.Get(context.Background(), key, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + return nil, ErrReleaseNotFound + } + return nil, fmt.Errorf("get: failed to get %q: %w", key, err) + } + // found the secret, decode the base64 data string + r, err := decodeRelease(string(obj.Data["release"])) + if err != nil { + return r, fmt.Errorf("get: failed to decode data %q: %w", key, err) + } + r.Labels = filterSystemLabels(obj.Labels) + return r, nil +} + +// List fetches all releases and returns the list releases such +// that filter(release) == true. An error is returned if the +// secret fails to retrieve the releases. +func (secrets *Secrets) List(filter func(release.Releaser) bool) ([]release.Releaser, error) { + lsel := kblabels.Set{"owner": "helm"}.AsSelector() + opts := metav1.ListOptions{LabelSelector: lsel.String()} + + list, err := secrets.impl.List(context.Background(), opts) + if err != nil { + return nil, fmt.Errorf("list: failed to list: %w", err) + } + + var results []release.Releaser + + // iterate over the secrets object list + // and decode each release + for _, item := range list.Items { + rls, err := decodeRelease(string(item.Data["release"])) + if err != nil { + secrets.Logger().Debug( + "list failed to decode release", slog.String("key", item.Name), + slog.Any("error", err), + ) + continue + } + + rls.Labels = item.Labels + + if filter(rls) { + results = append(results, rls) + } + } + return results, nil +} + +// Query fetches all releases that match the provided map of labels. +// An error is returned if the secret fails to retrieve the releases. +func (secrets *Secrets) Query(labels map[string]string) ([]release.Releaser, error) { + ls := kblabels.Set{} + for k, v := range labels { + if errs := validation.IsValidLabelValue(v); len(errs) != 0 { + return nil, fmt.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; ")) + } + ls[k] = v + } + + opts := metav1.ListOptions{LabelSelector: ls.AsSelector().String()} + + list, err := secrets.impl.List(context.Background(), opts) + if err != nil { + return nil, fmt.Errorf("query: failed to query with labels: %w", err) + } + + if len(list.Items) == 0 { + return nil, ErrReleaseNotFound + } + + var results []release.Releaser + for _, item := range list.Items { + rls, err := decodeRelease(string(item.Data["release"])) + if err != nil { + secrets.Logger().Debug( + "failed to decode release", + slog.String("key", item.Name), + slog.Any("error", err), + ) + continue + } + rls.Labels = item.Labels + results = append(results, rls) + } + return results, nil +} + +// Create creates a new Secret holding the release. If the +// Secret already exists, ErrReleaseExists is returned. +func (secrets *Secrets) Create(key string, rel release.Releaser) error { + // set labels for secrets object meta data + var lbs labels + + rls, err := releaserToV1Release(rel) + if err != nil { + return err + } + + lbs.init() + lbs.fromMap(rls.Labels) + lbs.set("createdAt", fmt.Sprintf("%v", time.Now().Unix())) + + // create a new secret to hold the release + obj, err := newSecretsObject(key, rls, lbs) + if err != nil { + return fmt.Errorf("create: failed to encode release %q: %w", rls.Name, err) + } + // push the secret object out into the kubiverse + if _, err := secrets.impl.Create(context.Background(), obj, metav1.CreateOptions{}); err != nil { + if apierrors.IsAlreadyExists(err) { + return ErrReleaseExists + } + + return fmt.Errorf("create: failed to create: %w", err) + } + return nil +} + +// Update updates the Secret holding the release. If not found +// the Secret is created to hold the release. +func (secrets *Secrets) Update(key string, rel release.Releaser) error { + // set labels for secrets object meta data + var lbs labels + + rls, err := releaserToV1Release(rel) + if err != nil { + return err + } + + lbs.init() + lbs.fromMap(rls.Labels) + lbs.set("modifiedAt", fmt.Sprintf("%v", time.Now().Unix())) + + // create a new secret object to hold the release + obj, err := newSecretsObject(key, rls, lbs) + if err != nil { + return fmt.Errorf("update: failed to encode release %q: %w", rls.Name, err) + } + // push the secret object out into the kubiverse + _, err = secrets.impl.Update(context.Background(), obj, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("update: failed to update: %w", err) + } + return nil +} + +// Delete deletes the Secret holding the release named by key. +func (secrets *Secrets) Delete(key string) (rls release.Releaser, err error) { + // fetch the release to check existence + if rls, err = secrets.Get(key); err != nil { + return nil, err + } + // delete the release + err = secrets.impl.Delete(context.Background(), key, metav1.DeleteOptions{}) + if err != nil { + return nil, err + } + return rls, nil +} + +// newSecretsObject constructs a kubernetes Secret object +// to store a release. Each secret data entry is the base64 +// encoded gzipped string of a release. +// +// The following labels are used within each secret: +// +// "modifiedAt" - timestamp indicating when this secret was last modified. (set in Update) +// "createdAt" - timestamp indicating when this secret was created. (set in Create) +// "version" - version of the release. +// "status" - status of the release (see pkg/release/status.go for variants) +// "owner" - owner of the secret, currently "helm". +// "name" - name of the release. +func newSecretsObject(key string, rls *rspb.Release, lbs labels) (*v1.Secret, error) { + const owner = "helm" + + // encode the release + s, err := encodeRelease(rls) + if err != nil { + return nil, err + } + + if lbs == nil { + lbs.init() + } + + // apply custom labels + lbs.fromMap(rls.Labels) + + // apply labels + lbs.set("name", rls.Name) + lbs.set("owner", owner) + lbs.set("status", rls.Info.Status.String()) + lbs.set("version", strconv.Itoa(rls.Version)) + + // create and return secret object. + // Helm 3 introduced setting the 'Type' field + // in the Kubernetes storage object. + // Helm defines the field content as follows: + // /.v + // Type field for Helm 3: helm.sh/release.v1 + // Note: Version starts at 'v1' for Helm 3 and + // should be incremented if the release object + // metadata is modified. + // This would potentially be a breaking change + // and should only happen between major versions. + return &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: key, + Labels: lbs.toMap(), + }, + Type: "helm.sh/release.v1", + Data: map[string][]byte{"release": []byte(s)}, + }, nil +} diff --git a/vendor/helm.sh/helm/v4/pkg/storage/driver/sql.go b/vendor/helm.sh/helm/v4/pkg/storage/driver/sql.go new file mode 100644 index 00000000000..21d9f6679ab --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/storage/driver/sql.go @@ -0,0 +1,727 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package driver // import "helm.sh/helm/v4/pkg/storage/driver" + +import ( + "fmt" + "log/slog" + "maps" + "sort" + "strconv" + "time" + + "github.com/jmoiron/sqlx" + migrate "github.com/rubenv/sql-migrate" + + sq "github.com/Masterminds/squirrel" + + // Import pq for postgres dialect + _ "github.com/lib/pq" + + "helm.sh/helm/v4/internal/logging" + "helm.sh/helm/v4/pkg/release" + rspb "helm.sh/helm/v4/pkg/release/v1" +) + +var _ Driver = (*SQL)(nil) + +var labelMap = map[string]struct{}{ + "modifiedAt": {}, + "createdAt": {}, + "version": {}, + "status": {}, + "owner": {}, + "name": {}, +} + +const postgreSQLDialect = "postgres" + +// SQLDriverName is the string name of this driver. +const SQLDriverName = "SQL" + +const sqlReleaseTableName = "releases_v1" +const sqlCustomLabelsTableName = "custom_labels_v1" + +const ( + sqlReleaseTableKeyColumn = "key" + sqlReleaseTableTypeColumn = "type" + sqlReleaseTableBodyColumn = "body" + sqlReleaseTableNameColumn = "name" + sqlReleaseTableNamespaceColumn = "namespace" + sqlReleaseTableVersionColumn = "version" + sqlReleaseTableStatusColumn = "status" + sqlReleaseTableOwnerColumn = "owner" + sqlReleaseTableCreatedAtColumn = "createdAt" + sqlReleaseTableModifiedAtColumn = "modifiedAt" + + sqlCustomLabelsTableReleaseKeyColumn = "releaseKey" + sqlCustomLabelsTableReleaseNamespaceColumn = "releaseNamespace" + sqlCustomLabelsTableKeyColumn = "key" + sqlCustomLabelsTableValueColumn = "value" +) + +// Following limits based on k8s labels limits - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set +const ( + sqlCustomLabelsTableKeyMaxLength = 253 + 1 + 63 + sqlCustomLabelsTableValueMaxLength = 63 +) + +const ( + sqlReleaseDefaultOwner = "helm" + sqlReleaseDefaultType = "helm.sh/release.v1" +) + +// SQL is the sql storage driver implementation. +type SQL struct { + db *sqlx.DB + namespace string + statementBuilder sq.StatementBuilderType + // Embed a LogHolder to provide logger functionality + logging.LogHolder +} + +// Name returns the name of the driver. +func (s *SQL) Name() string { + return SQLDriverName +} + +// Check if all migrations al +func (s *SQL) checkAlreadyApplied(migrations []*migrate.Migration) bool { + // make map (set) of ids for fast search + migrationsIDs := make(map[string]struct{}) + for _, migration := range migrations { + migrationsIDs[migration.Id] = struct{}{} + } + + // get list of applied migrations + migrate.SetDisableCreateTable(true) + records, err := migrate.GetMigrationRecords(s.db.DB, postgreSQLDialect) + migrate.SetDisableCreateTable(false) + if err != nil { + s.Logger().Debug("failed to get migration records", slog.Any("error", err)) + return false + } + + for _, record := range records { + if _, ok := migrationsIDs[record.Id]; ok { + s.Logger().Debug("found previous migration", "id", record.Id, "appliedAt", record.AppliedAt) + delete(migrationsIDs, record.Id) + } + } + + // check if all migrations applied + if len(migrationsIDs) != 0 { + for id := range migrationsIDs { + s.Logger().Debug("find unapplied migration", "id", id) + } + return false + } + return true +} + +func (s *SQL) ensureDBSetup() error { + + migrations := &migrate.MemoryMigrationSource{ + Migrations: []*migrate.Migration{ + { + Id: "init", + Up: []string{ + fmt.Sprintf(` + CREATE TABLE %s ( + %s VARCHAR(90), + %s VARCHAR(64) NOT NULL, + %s TEXT NOT NULL, + %s VARCHAR(64) NOT NULL, + %s VARCHAR(64) NOT NULL, + %s INTEGER NOT NULL, + %s TEXT NOT NULL, + %s TEXT NOT NULL, + %s INTEGER NOT NULL, + %s INTEGER NOT NULL DEFAULT 0, + PRIMARY KEY(%s, %s) + ); + CREATE INDEX ON %s (%s, %s); + CREATE INDEX ON %s (%s); + CREATE INDEX ON %s (%s); + CREATE INDEX ON %s (%s); + CREATE INDEX ON %s (%s); + CREATE INDEX ON %s (%s); + + GRANT ALL ON %s TO PUBLIC; + + ALTER TABLE %s ENABLE ROW LEVEL SECURITY; + `, + sqlReleaseTableName, + sqlReleaseTableKeyColumn, + sqlReleaseTableTypeColumn, + sqlReleaseTableBodyColumn, + sqlReleaseTableNameColumn, + sqlReleaseTableNamespaceColumn, + sqlReleaseTableVersionColumn, + sqlReleaseTableStatusColumn, + sqlReleaseTableOwnerColumn, + sqlReleaseTableCreatedAtColumn, + sqlReleaseTableModifiedAtColumn, + sqlReleaseTableKeyColumn, + sqlReleaseTableNamespaceColumn, + sqlReleaseTableName, + sqlReleaseTableKeyColumn, + sqlReleaseTableNamespaceColumn, + sqlReleaseTableName, + sqlReleaseTableVersionColumn, + sqlReleaseTableName, + sqlReleaseTableStatusColumn, + sqlReleaseTableName, + sqlReleaseTableOwnerColumn, + sqlReleaseTableName, + sqlReleaseTableCreatedAtColumn, + sqlReleaseTableName, + sqlReleaseTableModifiedAtColumn, + sqlReleaseTableName, + sqlReleaseTableName, + ), + }, + Down: []string{ + fmt.Sprintf(` + DROP TABLE %s; + `, sqlReleaseTableName), + }, + }, + { + Id: "custom_labels", + Up: []string{ + fmt.Sprintf(` + CREATE TABLE %s ( + %s VARCHAR(64), + %s VARCHAR(67), + %s VARCHAR(%d), + %s VARCHAR(%d) + ); + CREATE INDEX ON %s (%s, %s); + + GRANT ALL ON %s TO PUBLIC; + ALTER TABLE %s ENABLE ROW LEVEL SECURITY; + `, + sqlCustomLabelsTableName, + sqlCustomLabelsTableReleaseKeyColumn, + sqlCustomLabelsTableReleaseNamespaceColumn, + sqlCustomLabelsTableKeyColumn, + sqlCustomLabelsTableKeyMaxLength, + sqlCustomLabelsTableValueColumn, + sqlCustomLabelsTableValueMaxLength, + sqlCustomLabelsTableName, + sqlCustomLabelsTableReleaseKeyColumn, + sqlCustomLabelsTableReleaseNamespaceColumn, + sqlCustomLabelsTableName, + sqlCustomLabelsTableName, + ), + }, + Down: []string{ + fmt.Sprintf(` + DELETE TABLE %s; + `, sqlCustomLabelsTableName), + }, + }, + }, + } + + // Check that init migration already applied + if s.checkAlreadyApplied(migrations.Migrations) { + return nil + } + + // Populate the database with the relations we need if they don't exist yet + _, err := migrate.Exec(s.db.DB, postgreSQLDialect, migrations, migrate.Up) + return err +} + +// SQLReleaseWrapper describes how Helm releases are stored in an SQL database +type SQLReleaseWrapper struct { + // The primary key, made of {release-name}.{release-version} + Key string `db:"key"` + + // See https://github.com/helm/helm/blob/c9fe3d118caec699eb2565df9838673af379ce12/pkg/storage/driver/secrets.go#L231 + Type string `db:"type"` + + // The rspb.Release body, as a base64-encoded string + Body string `db:"body"` + + // Release "labels" that can be used as filters in the storage.Query(labels map[string]string) + // we implemented. Note that allowing Helm users to filter against new dimensions will require a + // new migration to be added, and the Create and/or update functions to be updated accordingly. + Name string `db:"name"` + Namespace string `db:"namespace"` + Version int `db:"version"` + Status string `db:"status"` + Owner string `db:"owner"` + CreatedAt int `db:"createdAt"` + ModifiedAt int `db:"modifiedAt"` +} + +type SQLReleaseCustomLabelWrapper struct { + ReleaseKey string `db:"release_key"` + ReleaseNamespace string `db:"release_namespace"` + Key string `db:"key"` + Value string `db:"value"` +} + +// NewSQL initializes a new sql driver. +func NewSQL(connectionString string, namespace string) (*SQL, error) { + db, err := sqlx.Connect(postgreSQLDialect, connectionString) + if err != nil { + return nil, err + } + + driver := &SQL{ + db: db, + statementBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar), + } + + if err := driver.ensureDBSetup(); err != nil { + return nil, err + } + + driver.namespace = namespace + driver.SetLogger(slog.Default().Handler()) + + return driver, nil +} + +// Get returns the release named by key. +func (s *SQL) Get(key string) (release.Releaser, error) { + var record SQLReleaseWrapper + + qb := s.statementBuilder. + Select(sqlReleaseTableBodyColumn). + From(sqlReleaseTableName). + Where(sq.Eq{sqlReleaseTableKeyColumn: key}). + Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}) + + query, args, err := qb.ToSql() + if err != nil { + s.Logger().Debug("failed to build query", slog.Any("error", err)) + return nil, err + } + + // Get will return an error if the result is empty + if err := s.db.Get(&record, query, args...); err != nil { + s.Logger().Debug("got SQL error when getting release", slog.String("key", key), slog.Any("error", err)) + return nil, ErrReleaseNotFound + } + + release, err := decodeRelease(record.Body) + if err != nil { + s.Logger().Debug("failed to decode data", slog.String("key", key), slog.Any("error", err)) + return nil, err + } + + if release.Labels, err = s.getReleaseCustomLabels(key, s.namespace); err != nil { + s.Logger().Debug( + "failed to get release custom labels", + slog.String("namespace", s.namespace), + slog.String("key", key), + slog.Any("error", err), + ) + return nil, err + } + + return release, nil +} + +// List returns the list of all releases such that filter(release) == true +func (s *SQL) List(filter func(release.Releaser) bool) ([]release.Releaser, error) { + sb := s.statementBuilder. + Select(sqlReleaseTableKeyColumn, sqlReleaseTableNamespaceColumn, sqlReleaseTableBodyColumn). + From(sqlReleaseTableName). + Where(sq.Eq{sqlReleaseTableOwnerColumn: sqlReleaseDefaultOwner}) + + // If a namespace was specified, we only list releases from that namespace + if s.namespace != "" { + sb = sb.Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}) + } + + query, args, err := sb.ToSql() + if err != nil { + s.Logger().Debug("failed to build query", slog.Any("error", err)) + return nil, err + } + + var records = []SQLReleaseWrapper{} + if err := s.db.Select(&records, query, args...); err != nil { + s.Logger().Debug("failed to list", slog.Any("error", err)) + return nil, err + } + + var releases []release.Releaser + for _, record := range records { + release, err := decodeRelease(record.Body) + if err != nil { + s.Logger().Debug("failed to decode release", slog.Any("record", record), slog.Any("error", err)) + continue + } + + if release.Labels, err = s.getReleaseCustomLabels(record.Key, record.Namespace); err != nil { + s.Logger().Debug( + "failed to get release custom labels", + slog.String("namespace", record.Namespace), + slog.String("key", record.Key), + slog.Any("error", err), + ) + return nil, err + } + maps.Copy(release.Labels, getReleaseSystemLabels(release)) + + if filter(release) { + releases = append(releases, release) + } + } + + return releases, nil +} + +// Query returns the set of releases that match the provided set of labels. +func (s *SQL) Query(labels map[string]string) ([]release.Releaser, error) { + sb := s.statementBuilder. + Select(sqlReleaseTableKeyColumn, sqlReleaseTableNamespaceColumn, sqlReleaseTableBodyColumn). + From(sqlReleaseTableName) + + keys := make([]string, 0, len(labels)) + for key := range labels { + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + if _, ok := labelMap[key]; ok { + sb = sb.Where(sq.Eq{key: labels[key]}) + } else { + s.Logger().Debug("unknown label", "key", key) + return nil, fmt.Errorf("unknown label %s", key) + } + } + + // If a namespace was specified, we only list releases from that namespace + if s.namespace != "" { + sb = sb.Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}) + } + + // Build our query + query, args, err := sb.ToSql() + if err != nil { + s.Logger().Debug("failed to build query", slog.Any("error", err)) + return nil, err + } + + var records = []SQLReleaseWrapper{} + if err := s.db.Select(&records, query, args...); err != nil { + s.Logger().Debug("failed to query with labels", slog.Any("error", err)) + return nil, err + } + + if len(records) == 0 { + return nil, ErrReleaseNotFound + } + + var releases []release.Releaser + for _, record := range records { + release, err := decodeRelease(record.Body) + if err != nil { + s.Logger().Debug("failed to decode release", slog.Any("record", record), slog.Any("error", err)) + continue + } + + if release.Labels, err = s.getReleaseCustomLabels(record.Key, record.Namespace); err != nil { + s.Logger().Debug( + "failed to get release custom labels", + slog.String("namespace", record.Namespace), + slog.String("key", record.Key), + slog.Any("error", err), + ) + return nil, err + } + + releases = append(releases, release) + } + + if len(releases) == 0 { + return nil, ErrReleaseNotFound + } + + return releases, nil +} + +// Create creates a new release. +func (s *SQL) Create(key string, rel release.Releaser) error { + rls, err := releaserToV1Release(rel) + if err != nil { + return err + } + + namespace := rls.Namespace + if namespace == "" { + namespace = defaultNamespace + } + s.namespace = namespace + + body, err := encodeRelease(rls) + if err != nil { + s.Logger().Debug("failed to encode release", slog.Any("error", err)) + return err + } + + transaction, err := s.db.Beginx() + if err != nil { + s.Logger().Debug("failed to start SQL transaction", slog.Any("error", err)) + return fmt.Errorf("error beginning transaction: %v", err) + } + + insertQuery, args, err := s.statementBuilder. + Insert(sqlReleaseTableName). + Columns( + sqlReleaseTableKeyColumn, + sqlReleaseTableTypeColumn, + sqlReleaseTableBodyColumn, + sqlReleaseTableNameColumn, + sqlReleaseTableNamespaceColumn, + sqlReleaseTableVersionColumn, + sqlReleaseTableStatusColumn, + sqlReleaseTableOwnerColumn, + sqlReleaseTableCreatedAtColumn, + ). + Values( + key, + sqlReleaseDefaultType, + body, + rls.Name, + namespace, + int(rls.Version), + rls.Info.Status.String(), + sqlReleaseDefaultOwner, + int(time.Now().Unix()), + ).ToSql() + if err != nil { + s.Logger().Debug("failed to build insert query", slog.Any("error", err)) + return err + } + + if _, err := transaction.Exec(insertQuery, args...); err != nil { + defer transaction.Rollback() + + selectQuery, args, buildErr := s.statementBuilder. + Select(sqlReleaseTableKeyColumn). + From(sqlReleaseTableName). + Where(sq.Eq{sqlReleaseTableKeyColumn: key}). + Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). + ToSql() + if buildErr != nil { + s.Logger().Debug("failed to build select query", "error", buildErr) + return err + } + + var record SQLReleaseWrapper + if err := transaction.Get(&record, selectQuery, args...); err == nil { + s.Logger().Debug("release already exists", slog.String("key", key)) + return ErrReleaseExists + } + + s.Logger().Debug("failed to store release in SQL database", slog.String("key", key), slog.Any("error", err)) + return err + } + + // Filtering labels before insert cause in SQL storage driver system releases are stored in separate columns of release table + for k, v := range filterSystemLabels(rls.Labels) { + insertLabelsQuery, args, err := s.statementBuilder. + Insert(sqlCustomLabelsTableName). + Columns( + sqlCustomLabelsTableReleaseKeyColumn, + sqlCustomLabelsTableReleaseNamespaceColumn, + sqlCustomLabelsTableKeyColumn, + sqlCustomLabelsTableValueColumn, + ). + Values( + key, + namespace, + k, + v, + ).ToSql() + + if err != nil { + defer transaction.Rollback() + s.Logger().Debug("failed to build insert query", slog.Any("error", err)) + return err + } + + if _, err := transaction.Exec(insertLabelsQuery, args...); err != nil { + defer transaction.Rollback() + s.Logger().Debug("failed to write Labels", slog.Any("error", err)) + return err + } + } + defer transaction.Commit() + + return nil +} + +// Update updates a release. +func (s *SQL) Update(key string, rel release.Releaser) error { + rls, err := releaserToV1Release(rel) + if err != nil { + return err + } + namespace := rls.Namespace + if namespace == "" { + namespace = defaultNamespace + } + s.namespace = namespace + + body, err := encodeRelease(rls) + if err != nil { + s.Logger().Debug("failed to encode release", slog.Any("error", err)) + return err + } + + query, args, err := s.statementBuilder. + Update(sqlReleaseTableName). + Set(sqlReleaseTableBodyColumn, body). + Set(sqlReleaseTableNameColumn, rls.Name). + Set(sqlReleaseTableVersionColumn, int(rls.Version)). + Set(sqlReleaseTableStatusColumn, rls.Info.Status.String()). + Set(sqlReleaseTableOwnerColumn, sqlReleaseDefaultOwner). + Set(sqlReleaseTableModifiedAtColumn, int(time.Now().Unix())). + Where(sq.Eq{sqlReleaseTableKeyColumn: key}). + Where(sq.Eq{sqlReleaseTableNamespaceColumn: namespace}). + ToSql() + + if err != nil { + s.Logger().Debug("failed to build update query", slog.Any("error", err)) + return err + } + + if _, err := s.db.Exec(query, args...); err != nil { + s.Logger().Debug("failed to update release in SQL database", slog.String("key", key), slog.Any("error", err)) + return err + } + + return nil +} + +// Delete deletes a release or returns ErrReleaseNotFound. +func (s *SQL) Delete(key string) (release.Releaser, error) { + transaction, err := s.db.Beginx() + if err != nil { + s.Logger().Debug("failed to start SQL transaction", slog.Any("error", err)) + return nil, fmt.Errorf("error beginning transaction: %v", err) + } + + selectQuery, args, err := s.statementBuilder. + Select(sqlReleaseTableBodyColumn). + From(sqlReleaseTableName). + Where(sq.Eq{sqlReleaseTableKeyColumn: key}). + Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). + ToSql() + if err != nil { + s.Logger().Debug("failed to build select query", slog.Any("error", err)) + return nil, err + } + + var record SQLReleaseWrapper + err = transaction.Get(&record, selectQuery, args...) + if err != nil { + s.Logger().Debug("release not found", slog.String("key", key), slog.Any("error", err)) + return nil, ErrReleaseNotFound + } + + release, err := decodeRelease(record.Body) + if err != nil { + s.Logger().Debug("failed to decode release", slog.String("key", key), slog.Any("error", err)) + transaction.Rollback() + return nil, err + } + defer transaction.Commit() + + deleteQuery, args, err := s.statementBuilder. + Delete(sqlReleaseTableName). + Where(sq.Eq{sqlReleaseTableKeyColumn: key}). + Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). + ToSql() + if err != nil { + s.Logger().Debug("failed to build delete query", slog.Any("error", err)) + return nil, err + } + + _, err = transaction.Exec(deleteQuery, args...) + if err != nil { + s.Logger().Debug("failed perform delete query", slog.Any("error", err)) + return release, err + } + + if release.Labels, err = s.getReleaseCustomLabels(key, s.namespace); err != nil { + s.Logger().Debug( + "failed to get release custom labels", + slog.String("namespace", s.namespace), + slog.String("key", key), + slog.Any("error", err)) + return nil, err + } + + deleteCustomLabelsQuery, args, err := s.statementBuilder. + Delete(sqlCustomLabelsTableName). + Where(sq.Eq{sqlCustomLabelsTableReleaseKeyColumn: key}). + Where(sq.Eq{sqlCustomLabelsTableReleaseNamespaceColumn: s.namespace}). + ToSql() + + if err != nil { + s.Logger().Debug("failed to build delete Labels query", slog.Any("error", err)) + return nil, err + } + _, err = transaction.Exec(deleteCustomLabelsQuery, args...) + return release, err +} + +// Get release custom labels from database +func (s *SQL) getReleaseCustomLabels(key string, _ string) (map[string]string, error) { + query, args, err := s.statementBuilder. + Select(sqlCustomLabelsTableKeyColumn, sqlCustomLabelsTableValueColumn). + From(sqlCustomLabelsTableName). + Where(sq.Eq{sqlCustomLabelsTableReleaseKeyColumn: key, + sqlCustomLabelsTableReleaseNamespaceColumn: s.namespace}). + ToSql() + if err != nil { + return nil, err + } + + var labelsList = []SQLReleaseCustomLabelWrapper{} + if err := s.db.Select(&labelsList, query, args...); err != nil { + return nil, err + } + + labelsMap := make(map[string]string) + for _, i := range labelsList { + labelsMap[i.Key] = i.Value + } + + return filterSystemLabels(labelsMap), nil +} + +// Rebuild system labels from release object +func getReleaseSystemLabels(rls *rspb.Release) map[string]string { + return map[string]string{ + "name": rls.Name, + "owner": sqlReleaseDefaultOwner, + "status": rls.Info.Status.String(), + "version": strconv.Itoa(rls.Version), + } +} diff --git a/vendor/helm.sh/helm/v4/pkg/storage/driver/util.go b/vendor/helm.sh/helm/v4/pkg/storage/driver/util.go new file mode 100644 index 00000000000..ca8e23cc2b3 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/storage/driver/util.go @@ -0,0 +1,118 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package driver // import "helm.sh/helm/v4/pkg/storage/driver" + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "encoding/json" + "io" + "slices" + + rspb "helm.sh/helm/v4/pkg/release/v1" +) + +var b64 = base64.StdEncoding + +var magicGzip = []byte{0x1f, 0x8b, 0x08} + +var systemLabels = []string{"name", "owner", "status", "version", "createdAt", "modifiedAt"} + +// encodeRelease encodes a release returning a base64 encoded +// gzipped string representation, or error. +func encodeRelease(rls *rspb.Release) (string, error) { + b, err := json.Marshal(rls) + if err != nil { + return "", err + } + var buf bytes.Buffer + w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression) + if err != nil { + return "", err + } + if _, err = w.Write(b); err != nil { + return "", err + } + w.Close() + + return b64.EncodeToString(buf.Bytes()), nil +} + +// decodeRelease decodes the bytes of data into a release +// type. Data must contain a base64 encoded gzipped string of a +// valid release, otherwise an error is returned. +func decodeRelease(data string) (*rspb.Release, error) { + // base64 decode string + b, err := b64.DecodeString(data) + if err != nil { + return nil, err + } + + // For backwards compatibility with releases that were stored before + // compression was introduced we skip decompression if the + // gzip magic header is not found + if len(b) > 3 && bytes.Equal(b[0:3], magicGzip) { + r, err := gzip.NewReader(bytes.NewReader(b)) + if err != nil { + return nil, err + } + defer r.Close() + b2, err := io.ReadAll(r) + if err != nil { + return nil, err + } + b = b2 + } + + var rls rspb.Release + // unmarshal release object bytes + if err := json.Unmarshal(b, &rls); err != nil { + return nil, err + } + return &rls, nil +} + +// Checks if label is system +func isSystemLabel(key string) bool { + return slices.Contains(GetSystemLabels(), key) +} + +// Removes system labels from labels map +func filterSystemLabels(lbs map[string]string) map[string]string { + result := make(map[string]string) + for k, v := range lbs { + if !isSystemLabel(k) { + result[k] = v + } + } + return result +} + +// Checks if labels array contains system labels +func ContainsSystemLabels(lbs map[string]string) bool { + for k := range lbs { + if isSystemLabel(k) { + return true + } + } + return false +} + +func GetSystemLabels() []string { + return systemLabels +} diff --git a/vendor/helm.sh/helm/v4/pkg/storage/storage.go b/vendor/helm.sh/helm/v4/pkg/storage/storage.go new file mode 100644 index 00000000000..d6c41635b34 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/storage/storage.go @@ -0,0 +1,350 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package storage // import "helm.sh/helm/v4/pkg/storage" + +import ( + "errors" + "fmt" + "log/slog" + "strings" + + "helm.sh/helm/v4/internal/logging" + "helm.sh/helm/v4/pkg/release" + "helm.sh/helm/v4/pkg/release/common" + rspb "helm.sh/helm/v4/pkg/release/v1" + relutil "helm.sh/helm/v4/pkg/release/v1/util" + "helm.sh/helm/v4/pkg/storage/driver" +) + +// HelmStorageType is the type field of the Kubernetes storage object which stores the Helm release +// version. It is modified slightly replacing the '/': sh.helm/release.v1 +// Note: The version 'v1' is incremented if the release object metadata is +// modified between major releases. +// This constant is used as a prefix for the Kubernetes storage object name. +const HelmStorageType = "sh.helm.release.v1" + +// Storage represents a storage engine for a Release. +type Storage struct { + driver.Driver + + // MaxHistory specifies the maximum number of historical releases that will + // be retained, including the most recent release. Values of 0 or less are + // ignored (meaning no limits are imposed). + MaxHistory int + + // Embed a LogHolder to provide logger functionality + logging.LogHolder +} + +// Get retrieves the release from storage. An error is returned +// if the storage driver failed to fetch the release, or the +// release identified by the key, version pair does not exist. +func (s *Storage) Get(name string, version int) (release.Releaser, error) { + s.Logger().Debug("getting release", "key", makeKey(name, version)) + return s.Driver.Get(makeKey(name, version)) +} + +// Create creates a new storage entry holding the release. An +// error is returned if the storage driver fails to store the +// release, or a release with an identical key already exists. +func (s *Storage) Create(rls release.Releaser) error { + rac, err := release.NewAccessor(rls) + if err != nil { + return err + } + s.Logger().Debug("creating release", "key", makeKey(rac.Name(), rac.Version())) + if s.MaxHistory > 0 { + // Want to make space for one more release. + if err := s.removeLeastRecent(rac.Name(), s.MaxHistory-1); err != nil && + !errors.Is(err, driver.ErrReleaseNotFound) { + return err + } + } + return s.Driver.Create(makeKey(rac.Name(), rac.Version()), rls) +} + +// Update updates the release in storage. An error is returned if the +// storage backend fails to update the release or if the release +// does not exist. +func (s *Storage) Update(rls release.Releaser) error { + rac, err := release.NewAccessor(rls) + if err != nil { + return err + } + s.Logger().Debug("updating release", "key", makeKey(rac.Name(), rac.Version())) + return s.Driver.Update(makeKey(rac.Name(), rac.Version()), rls) +} + +// Delete deletes the release from storage. An error is returned if +// the storage backend fails to delete the release or if the release +// does not exist. +func (s *Storage) Delete(name string, version int) (release.Releaser, error) { + s.Logger().Debug("deleting release", "key", makeKey(name, version)) + return s.Driver.Delete(makeKey(name, version)) +} + +// ListReleases returns all releases from storage. An error is returned if the +// storage backend fails to retrieve the releases. +func (s *Storage) ListReleases() ([]release.Releaser, error) { + s.Logger().Debug("listing all releases in storage") + return s.List(func(_ release.Releaser) bool { return true }) +} + +// releaserToV1Release is a helper function to convert a v1 release passed by interface +// into the type object. +func releaserToV1Release(rel release.Releaser) (*rspb.Release, error) { + switch r := rel.(type) { + case rspb.Release: + return &r, nil + case *rspb.Release: + return r, nil + case nil: + return nil, nil + default: + return nil, fmt.Errorf("unsupported release type: %T", rel) + } +} + +// ListUninstalled returns all releases with Status == UNINSTALLED. An error is returned +// if the storage backend fails to retrieve the releases. +func (s *Storage) ListUninstalled() ([]release.Releaser, error) { + s.Logger().Debug("listing uninstalled releases in storage") + return s.List(func(rls release.Releaser) bool { + rel, err := releaserToV1Release(rls) + if err != nil { + // This will only happen if calling code does not pass the proper types. This is + // a problem with the application and not user data. + s.Logger().Error("unable to convert release to typed release", slog.Any("error", err)) + panic(fmt.Sprintf("unable to convert release to typed release: %s", err)) + } + return relutil.StatusFilter(common.StatusUninstalled).Check(rel) + }) +} + +// ListDeployed returns all releases with Status == DEPLOYED. An error is returned +// if the storage backend fails to retrieve the releases. +func (s *Storage) ListDeployed() ([]release.Releaser, error) { + s.Logger().Debug("listing all deployed releases in storage") + return s.List(func(rls release.Releaser) bool { + rel, err := releaserToV1Release(rls) + if err != nil { + // This will only happen if calling code does not pass the proper types. This is + // a problem with the application and not user data. + s.Logger().Error("unable to convert release to typed release", slog.Any("error", err)) + panic(fmt.Sprintf("unable to convert release to typed release: %s", err)) + } + return relutil.StatusFilter(common.StatusDeployed).Check(rel) + }) +} + +// Deployed returns the last deployed release with the provided release name, or +// returns driver.NewErrNoDeployedReleases if not found. +func (s *Storage) Deployed(name string) (release.Releaser, error) { + ls, err := s.DeployedAll(name) + if err != nil { + return nil, err + } + + if len(ls) == 0 { + return nil, driver.NewErrNoDeployedReleases(name) + } + + rls, err := releaseListToV1List(ls) + if err != nil { + return nil, err + } + + // If executed concurrently, Helm's database gets corrupted + // and multiple releases are DEPLOYED. Take the latest. + relutil.Reverse(rls, relutil.SortByRevision) + + return rls[0], nil +} + +func releaseListToV1List(ls []release.Releaser) ([]*rspb.Release, error) { + rls := make([]*rspb.Release, 0, len(ls)) + for _, val := range ls { + rel, err := releaserToV1Release(val) + if err != nil { + return nil, err + } + rls = append(rls, rel) + } + + return rls, nil +} + +// DeployedAll returns all deployed releases with the provided name, or +// returns driver.NewErrNoDeployedReleases if not found. +func (s *Storage) DeployedAll(name string) ([]release.Releaser, error) { + s.Logger().Debug("getting deployed releases", "name", name) + + ls, err := s.Query(map[string]string{ + "name": name, + "owner": "helm", + "status": "deployed", + }) + if err == nil { + return ls, nil + } + if strings.Contains(err.Error(), "not found") { + return nil, driver.NewErrNoDeployedReleases(name) + } + return nil, err +} + +// History returns the revision history for the release with the provided name, or +// returns driver.ErrReleaseNotFound if no such release name exists. +func (s *Storage) History(name string) ([]release.Releaser, error) { + s.Logger().Debug("getting release history", "name", name) + + return s.Query(map[string]string{"name": name, "owner": "helm"}) +} + +// removeLeastRecent removes items from history until the length number of releases +// does not exceed max. +// +// We allow max to be set explicitly so that calling functions can "make space" +// for the new records they are going to write. +func (s *Storage) removeLeastRecent(name string, maximum int) error { + if maximum < 0 { + return nil + } + h, err := s.History(name) + if err != nil { + return err + } + if len(h) <= maximum { + return nil + } + rls, err := releaseListToV1List(h) + if err != nil { + return err + } + + // We want oldest to newest + relutil.SortByRevision(rls) + + lastDeployed, err := s.Deployed(name) + if err != nil && !errors.Is(err, driver.ErrNoDeployedReleases) { + return err + } + + var toDelete []release.Releaser + for _, rel := range rls { + // once we have enough releases to delete to reach the maximum, stop + if len(rls)-len(toDelete) == maximum { + break + } + if lastDeployed != nil { + ldac, err := release.NewAccessor(lastDeployed) + if err != nil { + return err + } + if rel.Version != ldac.Version() { + toDelete = append(toDelete, rel) + } + } else { + toDelete = append(toDelete, rel) + } + } + + // Delete as many as possible. In the case of API throughput limitations, + // multiple invocations of this function will eventually delete them all. + errs := []error{} + for _, rel := range toDelete { + rac, err := release.NewAccessor(rel) + if err != nil { + errs = append(errs, err) + continue + } + err = s.deleteReleaseVersion(name, rac.Version()) + if err != nil { + errs = append(errs, err) + } + } + + s.Logger().Debug("pruned records", "count", len(toDelete), "release", name, "errors", len(errs)) + switch c := len(errs); c { + case 0: + return nil + case 1: + return errs[0] + default: + return fmt.Errorf("encountered %d deletion errors. First is: %w", c, errs[0]) + } +} + +func (s *Storage) deleteReleaseVersion(name string, version int) error { + key := makeKey(name, version) + _, err := s.Delete(name, version) + if err != nil { + s.Logger().Debug("error pruning release", slog.String("key", key), slog.Any("error", err)) + return err + } + return nil +} + +// Last fetches the last revision of the named release. +func (s *Storage) Last(name string) (release.Releaser, error) { + s.Logger().Debug("getting last revision", "name", name) + h, err := s.History(name) + if err != nil { + return nil, err + } + if len(h) == 0 { + return nil, fmt.Errorf("no revision for release %q", name) + } + rls, err := releaseListToV1List(h) + if err != nil { + return nil, err + } + + relutil.Reverse(rls, relutil.SortByRevision) + return rls[0], nil +} + +// makeKey concatenates the Kubernetes storage object type, a release name and version +// into a string with format:```..v```. +// The storage type is prepended to keep name uniqueness between different +// release storage types. An example of clash when not using the type: +// https://github.com/helm/helm/issues/6435. +// This key is used to uniquely identify storage objects. +func makeKey(rlsname string, version int) string { + return fmt.Sprintf("%s.%s.v%d", HelmStorageType, rlsname, version) +} + +// Init initializes a new storage backend with the driver d. +// If d is nil, the default in-memory driver is used. +func Init(d driver.Driver) *Storage { + // default driver is in memory + if d == nil { + d = driver.NewMemory() + } + s := &Storage{ + Driver: d, + } + + // Get logger from driver if it implements the LoggerSetterGetter interface + if ls, ok := d.(logging.LoggerSetterGetter); ok { + ls.SetLogger(s.Logger().Handler()) + } else { + // If the driver does not implement the LoggerSetterGetter interface, set the default logger + s.SetLogger(slog.Default().Handler()) + } + return s +} diff --git a/vendor/helm.sh/helm/v4/pkg/uploader/chart_uploader.go b/vendor/helm.sh/helm/v4/pkg/uploader/chart_uploader.go new file mode 100644 index 00000000000..b3d612e3821 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/uploader/chart_uploader.go @@ -0,0 +1,56 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package uploader + +import ( + "fmt" + "io" + "net/url" + + "helm.sh/helm/v4/pkg/pusher" + "helm.sh/helm/v4/pkg/registry" +) + +// ChartUploader handles uploading a chart. +type ChartUploader struct { + // Out is the location to write warning and info messages. + Out io.Writer + // Pusher collection for the operation + Pushers pusher.Providers + // Options provide parameters to be passed along to the Pusher being initialized. + Options []pusher.Option + // RegistryClient is a client for interacting with registries. + RegistryClient *registry.Client +} + +// UploadTo uploads a chart. Depending on the settings, it may also upload a provenance file. +func (c *ChartUploader) UploadTo(ref, remote string) error { + u, err := url.Parse(remote) + if err != nil { + return fmt.Errorf("invalid chart URL format: %s", remote) + } + + if u.Scheme == "" { + return fmt.Errorf("scheme prefix missing from remote (e.g. \"%s://\")", registry.OCIScheme) + } + + p, err := c.Pushers.ByScheme(u.Scheme) + if err != nil { + return err + } + + return p.Push(ref, u.String(), c.Options...) +} diff --git a/vendor/helm.sh/helm/v4/pkg/uploader/doc.go b/vendor/helm.sh/helm/v4/pkg/uploader/doc.go new file mode 100644 index 00000000000..112ddbf2ce0 --- /dev/null +++ b/vendor/helm.sh/helm/v4/pkg/uploader/doc.go @@ -0,0 +1,21 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package uploader provides a library for uploading charts. + +This package contains tools for uploading charts to registries. +*/ +package uploader diff --git a/vendor/k8s.io/client-go/util/csaupgrade/OWNERS b/vendor/k8s.io/client-go/util/csaupgrade/OWNERS new file mode 100644 index 00000000000..733ae5da25c --- /dev/null +++ b/vendor/k8s.io/client-go/util/csaupgrade/OWNERS @@ -0,0 +1,10 @@ +# See the OWNERS docs at https://go.k8s.io/owners +approvers: + - apelisse + - alexzielenski +reviewers: + - apelisse + - alexzielenski + - KnVerey +labels: + - sig/api-machinery diff --git a/vendor/k8s.io/client-go/util/csaupgrade/options.go b/vendor/k8s.io/client-go/util/csaupgrade/options.go new file mode 100644 index 00000000000..490b92753a8 --- /dev/null +++ b/vendor/k8s.io/client-go/util/csaupgrade/options.go @@ -0,0 +1,30 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package csaupgrade + +type Option func(*options) + +// Subresource set the subresource to upgrade from CSA to SSA. +func Subresource(s string) Option { + return func(opts *options) { + opts.subresource = s + } +} + +type options struct { + subresource string +} diff --git a/vendor/k8s.io/client-go/util/csaupgrade/upgrade.go b/vendor/k8s.io/client-go/util/csaupgrade/upgrade.go new file mode 100644 index 00000000000..6e2b5df4809 --- /dev/null +++ b/vendor/k8s.io/client-go/util/csaupgrade/upgrade.go @@ -0,0 +1,334 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package csaupgrade + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "reflect" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" +) + +// Finds all managed fields owners of the given operation type which owns all of +// the fields in the given set +// +// If there is an error decoding one of the fieldsets for any reason, it is ignored +// and assumed not to match the query. +func FindFieldsOwners( + managedFields []metav1.ManagedFieldsEntry, + operation metav1.ManagedFieldsOperationType, + fields *fieldpath.Set, +) []metav1.ManagedFieldsEntry { + var result []metav1.ManagedFieldsEntry + for _, entry := range managedFields { + if entry.Operation != operation { + continue + } + + fieldSet, err := decodeManagedFieldsEntrySet(entry) + if err != nil { + continue + } + + if fields.Difference(&fieldSet).Empty() { + result = append(result, entry) + } + } + return result +} + +// Upgrades the Manager information for fields managed with client-side-apply (CSA) +// Prepares fields owned by `csaManager` for 'Update' operations for use now +// with the given `ssaManager` for `Apply` operations. +// +// This transformation should be performed on an object if it has been previously +// managed using client-side-apply to prepare it for future use with +// server-side-apply. +// +// Caveats: +// 1. This operation is not reversible. Information about which fields the client +// owned will be lost in this operation. +// 2. Supports being performed either before or after initial server-side apply. +// 3. Client-side apply tends to own more fields (including fields that are defaulted), +// this will possibly remove this defaults, they will be re-defaulted, that's fine. +// 4. Care must be taken to not overwrite the managed fields on the server if they +// have changed before sending a patch. +// +// obj - Target of the operation which has been managed with CSA in the past +// csaManagerNames - Names of FieldManagers to merge into ssaManagerName +// ssaManagerName - Name of FieldManager to be used for `Apply` operations +func UpgradeManagedFields( + obj runtime.Object, + csaManagerNames sets.Set[string], + ssaManagerName string, + opts ...Option, +) error { + o := options{} + for _, opt := range opts { + opt(&o) + } + + accessor, err := meta.Accessor(obj) + if err != nil { + return err + } + + filteredManagers := accessor.GetManagedFields() + + for csaManagerName := range csaManagerNames { + filteredManagers, err = upgradedManagedFields( + filteredManagers, csaManagerName, ssaManagerName, o) + + if err != nil { + return err + } + } + + // Commit changes to object + accessor.SetManagedFields(filteredManagers) + return nil +} + +// Calculates a minimal JSON Patch to send to upgrade managed fields +// See `UpgradeManagedFields` for more information. +// +// obj - Target of the operation which has been managed with CSA in the past +// csaManagerNames - Names of FieldManagers to merge into ssaManagerName +// ssaManagerName - Name of FieldManager to be used for `Apply` operations +// +// Returns non-nil error if there was an error, a JSON patch, or nil bytes if +// there is no work to be done. +func UpgradeManagedFieldsPatch( + obj runtime.Object, + csaManagerNames sets.Set[string], + ssaManagerName string, + opts ...Option, +) ([]byte, error) { + o := options{} + for _, opt := range opts { + opt(&o) + } + + accessor, err := meta.Accessor(obj) + if err != nil { + return nil, err + } + + managedFields := accessor.GetManagedFields() + filteredManagers := accessor.GetManagedFields() + for csaManagerName := range csaManagerNames { + filteredManagers, err = upgradedManagedFields( + filteredManagers, csaManagerName, ssaManagerName, o) + if err != nil { + return nil, err + } + } + + if reflect.DeepEqual(managedFields, filteredManagers) { + // If the managed fields have not changed from the transformed version, + // there is no patch to perform + return nil, nil + } + + // Create a patch with a diff between old and new objects. + // Just include all managed fields since that is only thing that will change + // + // Also include test for RV to avoid race condition + jsonPatch := []map[string]interface{}{ + { + "op": "replace", + "path": "/metadata/managedFields", + "value": filteredManagers, + }, + { + // Use "replace" instead of "test" operation so that etcd rejects with + // 409 conflict instead of apiserver with an invalid request + "op": "replace", + "path": "/metadata/resourceVersion", + "value": accessor.GetResourceVersion(), + }, + } + + return json.Marshal(jsonPatch) +} + +// Returns a copy of the provided managed fields that has been migrated from +// client-side-apply to server-side-apply, or an error if there was an issue +func upgradedManagedFields( + managedFields []metav1.ManagedFieldsEntry, + csaManagerName string, + ssaManagerName string, + opts options, +) ([]metav1.ManagedFieldsEntry, error) { + if managedFields == nil { + return nil, nil + } + + // Create managed fields clone since we modify the values + managedFieldsCopy := make([]metav1.ManagedFieldsEntry, len(managedFields)) + if copy(managedFieldsCopy, managedFields) != len(managedFields) { + return nil, errors.New("failed to copy managed fields") + } + managedFields = managedFieldsCopy + + // Locate SSA manager + replaceIndex, managerExists := findFirstIndex(managedFields, + func(entry metav1.ManagedFieldsEntry) bool { + return entry.Manager == ssaManagerName && + entry.Operation == metav1.ManagedFieldsOperationApply && + entry.Subresource == opts.subresource + }) + + if !managerExists { + // SSA manager does not exist. Find the most recent matching CSA manager, + // convert it to an SSA manager. + // + // (find first index, since managed fields are sorted so that most recent is + // first in the list) + replaceIndex, managerExists = findFirstIndex(managedFields, + func(entry metav1.ManagedFieldsEntry) bool { + return entry.Manager == csaManagerName && + entry.Operation == metav1.ManagedFieldsOperationUpdate && + entry.Subresource == opts.subresource + }) + + if !managerExists { + // There are no CSA managers that need to be converted. Nothing to do + // Return early + return managedFields, nil + } + + // Convert CSA manager into SSA manager + managedFields[replaceIndex].Operation = metav1.ManagedFieldsOperationApply + managedFields[replaceIndex].Manager = ssaManagerName + } + err := unionManagerIntoIndex(managedFields, replaceIndex, csaManagerName, opts) + if err != nil { + return nil, err + } + + // Create version of managed fields which has no CSA managers with the given name + filteredManagers := filter(managedFields, func(entry metav1.ManagedFieldsEntry) bool { + return !(entry.Manager == csaManagerName && + entry.Operation == metav1.ManagedFieldsOperationUpdate && + entry.Subresource == opts.subresource) + }) + + return filteredManagers, nil +} + +// Locates an Update manager entry named `csaManagerName` with the same APIVersion +// as the manager at the targetIndex. Unions both manager's fields together +// into the manager specified by `targetIndex`. No other managers are modified. +func unionManagerIntoIndex( + entries []metav1.ManagedFieldsEntry, + targetIndex int, + csaManagerName string, + opts options, +) error { + ssaManager := entries[targetIndex] + + // find Update manager of same APIVersion, union ssa fields with it. + // discard all other Update managers of the same name + csaManagerIndex, csaManagerExists := findFirstIndex(entries, + func(entry metav1.ManagedFieldsEntry) bool { + return entry.Manager == csaManagerName && + entry.Operation == metav1.ManagedFieldsOperationUpdate && + entry.Subresource == opts.subresource && + entry.APIVersion == ssaManager.APIVersion + }) + + targetFieldSet, err := decodeManagedFieldsEntrySet(ssaManager) + if err != nil { + return fmt.Errorf("failed to convert fields to set: %w", err) + } + + combinedFieldSet := &targetFieldSet + + // Union the csa manager with the existing SSA manager. Do nothing if + // there was no good candidate found + if csaManagerExists { + csaManager := entries[csaManagerIndex] + + csaFieldSet, err := decodeManagedFieldsEntrySet(csaManager) + if err != nil { + return fmt.Errorf("failed to convert fields to set: %w", err) + } + + combinedFieldSet = combinedFieldSet.Union(&csaFieldSet) + } + + // Encode the fields back to the serialized format + err = encodeManagedFieldsEntrySet(&entries[targetIndex], *combinedFieldSet) + if err != nil { + return fmt.Errorf("failed to encode field set: %w", err) + } + + return nil +} + +func findFirstIndex[T any]( + collection []T, + predicate func(T) bool, +) (int, bool) { + for idx, entry := range collection { + if predicate(entry) { + return idx, true + } + } + + return -1, false +} + +func filter[T any]( + collection []T, + predicate func(T) bool, +) []T { + result := make([]T, 0, len(collection)) + + for _, value := range collection { + if predicate(value) { + result = append(result, value) + } + } + + if len(result) == 0 { + return nil + } + + return result +} + +// Included from fieldmanager.internal to avoid dependency cycle +// FieldsToSet creates a set paths from an input trie of fields +func decodeManagedFieldsEntrySet(f metav1.ManagedFieldsEntry) (s fieldpath.Set, err error) { + err = s.FromJSON(bytes.NewReader(f.FieldsV1.Raw)) + return s, err +} + +// SetToFields creates a trie of fields from an input set of paths +func encodeManagedFieldsEntrySet(f *metav1.ManagedFieldsEntry, s fieldpath.Set) (err error) { + f.FieldsV1.Raw, err = s.ToJSON() + return err +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 8a4fb77d719..ede99d36d0b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -40,8 +40,8 @@ github.com/Microsoft/go-winio/internal/fs github.com/Microsoft/go-winio/internal/socket github.com/Microsoft/go-winio/internal/stringbuffer github.com/Microsoft/go-winio/pkg/guid -# github.com/ProtonMail/go-crypto v1.1.6 -## explicit; go 1.17 +# github.com/ProtonMail/go-crypto v1.3.0 +## explicit; go 1.22.0 github.com/ProtonMail/go-crypto/bitcurves github.com/ProtonMail/go-crypto/brainpool github.com/ProtonMail/go-crypto/eax @@ -50,6 +50,7 @@ github.com/ProtonMail/go-crypto/ocb github.com/ProtonMail/go-crypto/openpgp github.com/ProtonMail/go-crypto/openpgp/aes/keywrap github.com/ProtonMail/go-crypto/openpgp/armor +github.com/ProtonMail/go-crypto/openpgp/clearsign github.com/ProtonMail/go-crypto/openpgp/ecdh github.com/ProtonMail/go-crypto/openpgp/ecdsa github.com/ProtonMail/go-crypto/openpgp/ed25519 @@ -104,7 +105,7 @@ github.com/cloudevents/sdk-go/v2/event/datacodec/xml github.com/cloudevents/sdk-go/v2/protocol github.com/cloudevents/sdk-go/v2/protocol/http github.com/cloudevents/sdk-go/v2/types -# github.com/cloudflare/circl v1.6.1 +# github.com/cloudflare/circl v1.6.3 ## explicit; go 1.22.0 github.com/cloudflare/circl/dh/x25519 github.com/cloudflare/circl/dh/x448 @@ -235,6 +236,9 @@ github.com/docker/go-connections/tlsconfig # github.com/docker/go-metrics v0.0.1 ## explicit; go 1.11 github.com/docker/go-metrics +# github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a +## explicit; go 1.21 +github.com/dylibso/observe-sdk/go # github.com/emicklei/go-restful/v3 v3.13.0 ## explicit; go 1.13 github.com/emicklei/go-restful/v3 @@ -257,12 +261,26 @@ github.com/evanphx/json-patch/v5/internal/json # github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f ## explicit; go 1.15 github.com/exponent-io/jsonpath +# github.com/extism/go-sdk v1.7.1 +## explicit; go 1.22.0 +github.com/extism/go-sdk # github.com/fatih/color v1.18.0 ## explicit; go 1.17 github.com/fatih/color # github.com/felixge/httpsnoop v1.0.4 ## explicit; go 1.13 github.com/felixge/httpsnoop +# github.com/fluxcd/cli-utils v0.37.2-flux.1 +## explicit; go 1.25.0 +github.com/fluxcd/cli-utils/pkg/kstatus/polling/aggregator +github.com/fluxcd/cli-utils/pkg/kstatus/polling/clusterreader +github.com/fluxcd/cli-utils/pkg/kstatus/polling/collector +github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine +github.com/fluxcd/cli-utils/pkg/kstatus/polling/event +github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders +github.com/fluxcd/cli-utils/pkg/kstatus/status +github.com/fluxcd/cli-utils/pkg/kstatus/watcher +github.com/fluxcd/cli-utils/pkg/object # github.com/fsnotify/fsnotify v1.9.0 ## explicit; go 1.17 github.com/fsnotify/fsnotify @@ -270,7 +288,7 @@ github.com/fsnotify/fsnotify/internal # github.com/fxamacker/cbor/v2 v2.9.0 ## explicit; go 1.20 github.com/fxamacker/cbor/v2 -# github.com/go-errors/errors v1.4.2 +# github.com/go-errors/errors v1.5.1 ## explicit; go 1.14 github.com/go-errors/errors # github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 @@ -409,6 +427,9 @@ github.com/gobwas/glob/syntax/ast github.com/gobwas/glob/syntax/lexer github.com/gobwas/glob/util/runes github.com/gobwas/glob/util/strings +# github.com/gofrs/flock v0.13.0 +## explicit; go 1.24.0 +github.com/gofrs/flock # github.com/gogo/protobuf v1.3.2 ## explicit; go 1.15 github.com/gogo/protobuf/proto @@ -537,6 +558,9 @@ github.com/helm/chart-testing/v3/pkg/util # github.com/huandu/xstrings v1.5.0 ## explicit; go 1.12 github.com/huandu/xstrings +# github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca +## explicit; go 1.13 +github.com/ianlancetaylor/demangle # github.com/inconshreveable/mousetrap v1.1.0 ## explicit; go 1.18 github.com/inconshreveable/mousetrap @@ -820,6 +844,49 @@ github.com/stretchr/testify/require # github.com/subosito/gotenv v1.6.0 ## explicit; go 1.18 github.com/subosito/gotenv +# github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 +## explicit; go 1.18 +github.com/tetratelabs/wabin/binary +github.com/tetratelabs/wabin/ieee754 +github.com/tetratelabs/wabin/leb128 +github.com/tetratelabs/wabin/wasm +# github.com/tetratelabs/wazero v1.11.0 +## explicit; go 1.24.0 +github.com/tetratelabs/wazero +github.com/tetratelabs/wazero/api +github.com/tetratelabs/wazero/experimental +github.com/tetratelabs/wazero/experimental/sys +github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1 +github.com/tetratelabs/wazero/internal/descriptor +github.com/tetratelabs/wazero/internal/engine/interpreter +github.com/tetratelabs/wazero/internal/engine/wazevo +github.com/tetratelabs/wazero/internal/engine/wazevo/backend +github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64 +github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64 +github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc +github.com/tetratelabs/wazero/internal/engine/wazevo/frontend +github.com/tetratelabs/wazero/internal/engine/wazevo/ssa +github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi +github.com/tetratelabs/wazero/internal/expctxkeys +github.com/tetratelabs/wazero/internal/filecache +github.com/tetratelabs/wazero/internal/fsapi +github.com/tetratelabs/wazero/internal/ieee754 +github.com/tetratelabs/wazero/internal/internalapi +github.com/tetratelabs/wazero/internal/leb128 +github.com/tetratelabs/wazero/internal/moremath +github.com/tetratelabs/wazero/internal/platform +github.com/tetratelabs/wazero/internal/sock +github.com/tetratelabs/wazero/internal/sys +github.com/tetratelabs/wazero/internal/sysfs +github.com/tetratelabs/wazero/internal/u32 +github.com/tetratelabs/wazero/internal/u64 +github.com/tetratelabs/wazero/internal/version +github.com/tetratelabs/wazero/internal/wasip1 +github.com/tetratelabs/wazero/internal/wasm +github.com/tetratelabs/wazero/internal/wasm/binary +github.com/tetratelabs/wazero/internal/wasmdebug +github.com/tetratelabs/wazero/internal/wasmruntime +github.com/tetratelabs/wazero/sys # github.com/x448/float16 v0.8.4 ## explicit; go 1.11 github.com/x448/float16 @@ -874,6 +941,11 @@ go.opentelemetry.io/otel/trace go.opentelemetry.io/otel/trace/embedded go.opentelemetry.io/otel/trace/internal/telemetry go.opentelemetry.io/otel/trace/noop +# go.opentelemetry.io/proto/otlp v1.10.0 +## explicit; go 1.24.0 +go.opentelemetry.io/proto/otlp/common/v1 +go.opentelemetry.io/proto/otlp/resource/v1 +go.opentelemetry.io/proto/otlp/trace/v1 # go.podman.io/image/v5 v5.40.0 ## explicit; go 1.25.6 go.podman.io/image/v5/docker/reference @@ -995,7 +1067,7 @@ golang.org/x/text/unicode/norm # golang.org/x/time v0.14.0 ## explicit; go 1.24.0 golang.org/x/time/rate -# gomodules.xyz/jsonpatch/v2 v2.4.0 +# gomodules.xyz/jsonpatch/v2 v2.5.0 ## explicit; go 1.20 gomodules.xyz/jsonpatch/v2 # google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 @@ -1133,8 +1205,8 @@ gopkg.in/yaml.v2 # gopkg.in/yaml.v3 v3.0.1 ## explicit gopkg.in/yaml.v3 -# helm.sh/helm/v3 v3.20.1 -## explicit; go 1.25.0 +# helm.sh/helm/v3 v3.17.1 +## explicit; go 1.23.0 helm.sh/helm/v3/internal/fileutil helm.sh/helm/v3/internal/resolver helm.sh/helm/v3/internal/sympath @@ -1174,6 +1246,56 @@ helm.sh/helm/v3/pkg/strvals helm.sh/helm/v3/pkg/time helm.sh/helm/v3/pkg/time/ctime helm.sh/helm/v3/pkg/uploader +# helm.sh/helm/v4 v4.1.4 +## explicit; go 1.25.0 +helm.sh/helm/v4/internal/chart/v3 +helm.sh/helm/v4/internal/chart/v3/loader +helm.sh/helm/v4/internal/copystructure +helm.sh/helm/v4/internal/fileutil +helm.sh/helm/v4/internal/logging +helm.sh/helm/v4/internal/plugin +helm.sh/helm/v4/internal/plugin/schema +helm.sh/helm/v4/internal/resolver +helm.sh/helm/v4/internal/statusreaders +helm.sh/helm/v4/internal/sympath +helm.sh/helm/v4/internal/third_party/dep/fs +helm.sh/helm/v4/internal/third_party/k8s.io/kubernetes/deployment/util +helm.sh/helm/v4/internal/tlsutil +helm.sh/helm/v4/internal/urlutil +helm.sh/helm/v4/internal/version +helm.sh/helm/v4/pkg/action +helm.sh/helm/v4/pkg/chart +helm.sh/helm/v4/pkg/chart/common +helm.sh/helm/v4/pkg/chart/common/util +helm.sh/helm/v4/pkg/chart/loader +helm.sh/helm/v4/pkg/chart/loader/archive +helm.sh/helm/v4/pkg/chart/v2 +helm.sh/helm/v4/pkg/chart/v2/lint +helm.sh/helm/v4/pkg/chart/v2/lint/rules +helm.sh/helm/v4/pkg/chart/v2/lint/support +helm.sh/helm/v4/pkg/chart/v2/loader +helm.sh/helm/v4/pkg/chart/v2/util +helm.sh/helm/v4/pkg/cli +helm.sh/helm/v4/pkg/downloader +helm.sh/helm/v4/pkg/engine +helm.sh/helm/v4/pkg/getter +helm.sh/helm/v4/pkg/helmpath +helm.sh/helm/v4/pkg/helmpath/xdg +helm.sh/helm/v4/pkg/ignore +helm.sh/helm/v4/pkg/kube +helm.sh/helm/v4/pkg/kube/fake +helm.sh/helm/v4/pkg/postrenderer +helm.sh/helm/v4/pkg/provenance +helm.sh/helm/v4/pkg/pusher +helm.sh/helm/v4/pkg/registry +helm.sh/helm/v4/pkg/release +helm.sh/helm/v4/pkg/release/common +helm.sh/helm/v4/pkg/release/v1 +helm.sh/helm/v4/pkg/release/v1/util +helm.sh/helm/v4/pkg/repo/v1 +helm.sh/helm/v4/pkg/storage +helm.sh/helm/v4/pkg/storage/driver +helm.sh/helm/v4/pkg/uploader # k8s.io/api v0.35.4 ## explicit; go 1.25.0 k8s.io/api/admission/v1 @@ -1664,6 +1786,7 @@ k8s.io/client-go/util/apply k8s.io/client-go/util/cert k8s.io/client-go/util/connrotation k8s.io/client-go/util/consistencydetector +k8s.io/client-go/util/csaupgrade k8s.io/client-go/util/flowcontrol k8s.io/client-go/util/homedir k8s.io/client-go/util/jsonpath @@ -1733,6 +1856,11 @@ oras.land/oras-go/pkg/auth/docker oras.land/oras-go/pkg/content oras.land/oras-go/pkg/context oras.land/oras-go/pkg/oras +oras.land/oras-go/pkg/registry +oras.land/oras-go/pkg/registry/remote +oras.land/oras-go/pkg/registry/remote/auth +oras.land/oras-go/pkg/registry/remote/internal/errutil +oras.land/oras-go/pkg/registry/remote/internal/syncutil oras.land/oras-go/pkg/target # oras.land/oras-go/v2 v2.6.0 ## explicit; go 1.23.0 @@ -1821,8 +1949,8 @@ sigs.k8s.io/controller-runtime/pkg/webhook/internal/metrics ## explicit; go 1.23 sigs.k8s.io/json sigs.k8s.io/json/internal/golang/encoding/json -# sigs.k8s.io/kustomize/api v0.20.1 -## explicit; go 1.22.7 +# sigs.k8s.io/kustomize/api v0.21.1 +## explicit; go 1.24.0 sigs.k8s.io/kustomize/api/filters/annotations sigs.k8s.io/kustomize/api/filters/fieldspec sigs.k8s.io/kustomize/api/filters/filtersutil @@ -1867,8 +1995,8 @@ sigs.k8s.io/kustomize/api/provider sigs.k8s.io/kustomize/api/resmap sigs.k8s.io/kustomize/api/resource sigs.k8s.io/kustomize/api/types -# sigs.k8s.io/kustomize/kyaml v0.20.1 -## explicit; go 1.22.7 +# sigs.k8s.io/kustomize/kyaml v0.21.1 +## explicit; go 1.24.0 sigs.k8s.io/kustomize/kyaml/comments sigs.k8s.io/kustomize/kyaml/errors sigs.k8s.io/kustomize/kyaml/ext diff --git a/vendor/oras.land/oras-go/pkg/registry/reference.go b/vendor/oras.land/oras-go/pkg/registry/reference.go new file mode 100644 index 00000000000..299651cd10b --- /dev/null +++ b/vendor/oras.land/oras-go/pkg/registry/reference.go @@ -0,0 +1,177 @@ +/* +Copyright The ORAS Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package registry + +import ( + "fmt" + "net/url" + "regexp" + "strings" + + "github.com/opencontainers/go-digest" + errdef "oras.land/oras-go/pkg/content" +) + +// regular expressions for components. +var ( + // repositoryRegexp is adapted from the distribution implementation. + // The repository name set under OCI distribution spec is a subset of the + // the docker spec. For maximum compability, the docker spec is verified at + // the client side. Further check is left to the server side. + // References: + // - https://github.com/distribution/distribution/blob/v2.7.1/reference/regexp.go#L53 + // - https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pulling-manifests + repositoryRegexp = regexp.MustCompile(`^[a-z0-9]+(?:(?:[._]|__|[-]*)[a-z0-9]+)*(?:/[a-z0-9]+(?:(?:[._]|__|[-]*)[a-z0-9]+)*)*$`) + + // tagRegexp checks the tag name. + // The docker and OCI spec have the same regular expression. + // Reference: https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pulling-manifests + tagRegexp = regexp.MustCompile(`^[\w][\w.-]{0,127}$`) +) + +// Reference references to a descriptor in the registry. +type Reference struct { + // Registry is the name of the registry. + // It is usually the domain name of the registry optionally with a port. + Registry string + + // Repository is the name of the repository. + Repository string + + // Reference is the reference of the object in the repository. + // A reference can be a tag or a digest. + Reference string +} + +// ParseReference parses a string into a artifact reference. +// If the reference contains both the tag and the digest, the tag will be +// dropped. +// Digest is recognized only if the corresponding algorithm is available. +func ParseReference(raw string) (Reference, error) { + parts := strings.SplitN(raw, "/", 2) + if len(parts) == 1 { + return Reference{}, fmt.Errorf("%w: missing repository", errdef.ErrInvalidReference) + } + registry, path := parts[0], parts[1] + var repository string + var reference string + if index := strings.Index(path, "@"); index != -1 { + // digest found + repository = path[:index] + reference = path[index+1:] + + // drop tag since the digest is present. + if index := strings.Index(repository, ":"); index != -1 { + repository = repository[:index] + } + } else if index := strings.Index(path, ":"); index != -1 { + // tag found + repository = path[:index] + reference = path[index+1:] + } else { + // empty reference + repository = path + } + res := Reference{ + Registry: registry, + Repository: repository, + Reference: reference, + } + if err := res.Validate(); err != nil { + return Reference{}, err + } + return res, nil +} + +// Validate validates the entire reference. +func (r Reference) Validate() error { + err := r.ValidateRegistry() + if err != nil { + return err + } + err = r.ValidateRepository() + if err != nil { + return err + } + return r.ValidateReference() +} + +// ValidateRegistry validates the registry. +func (r Reference) ValidateRegistry() error { + uri, err := url.ParseRequestURI("dummy://" + r.Registry) + if err != nil || uri.Host != r.Registry { + return fmt.Errorf("%w: invalid registry", errdef.ErrInvalidReference) + } + return nil +} + +// ValidateRepository validates the repository. +func (r Reference) ValidateRepository() error { + if !repositoryRegexp.MatchString(r.Repository) { + return fmt.Errorf("%w: invalid repository", errdef.ErrInvalidReference) + } + return nil +} + +// ValidateReference validates the reference. +func (r Reference) ValidateReference() error { + if r.Reference == "" { + return nil + } + if _, err := r.Digest(); err == nil { + return nil + } + if !tagRegexp.MatchString(r.Reference) { + return fmt.Errorf("%w: invalid tag", errdef.ErrInvalidReference) + } + return nil +} + +// Host returns the host name of the registry. +func (r Reference) Host() string { + if r.Registry == "docker.io" { + return "registry-1.docker.io" + } + return r.Registry +} + +// ReferenceOrDefault returns the reference or the default reference if empty. +func (r Reference) ReferenceOrDefault() string { + if r.Reference == "" { + return "latest" + } + return r.Reference +} + +// Digest returns the reference as a digest. +func (r Reference) Digest() (digest.Digest, error) { + return digest.Parse(r.Reference) +} + +// String implements `fmt.Stringer` and returns the reference string. +// The resulted string is meaningful only if the reference is valid. +func (r Reference) String() string { + if r.Repository == "" { + return r.Registry + } + ref := r.Registry + "/" + r.Repository + if r.Reference == "" { + return ref + } + if d, err := r.Digest(); err == nil { + return ref + "@" + d.String() + } + return ref + ":" + r.Reference +} diff --git a/vendor/oras.land/oras-go/pkg/registry/remote/auth/cache.go b/vendor/oras.land/oras-go/pkg/registry/remote/auth/cache.go new file mode 100644 index 00000000000..07058837360 --- /dev/null +++ b/vendor/oras.land/oras-go/pkg/registry/remote/auth/cache.go @@ -0,0 +1,158 @@ +/* +Copyright The ORAS Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package auth + +import ( + "context" + "strings" + "sync" + + errdef "oras.land/oras-go/pkg/content" + "oras.land/oras-go/pkg/registry/remote/internal/syncutil" +) + +// DefaultCache is the sharable cache used by DefaultClient. +var DefaultCache Cache = NewCache() + +// Cache caches the auth-scheme and auth-token for the "Authorization" header in +// accessing the remote registry. +// Precisely, the header is `Authorization: auth-scheme auth-token`. +// The `auth-token` is a generic term as `token68` in RFC 7235 section 2.1. +type Cache interface { + // GetScheme returns the auth-scheme part cached for the given registry. + // A single registry is assumed to have a consistent scheme. + // If a registry has different schemes per path, the auth client is still + // workable. However, the cache may not be effective as the cache cannot + // correctly guess the scheme. + GetScheme(ctx context.Context, registry string) (Scheme, error) + + // GetToken returns the auth-token part cached for the given registry of a + // given scheme. + // The underlying implementation MAY cache the token for all schemes for the + // given registry. + GetToken(ctx context.Context, registry string, scheme Scheme, key string) (string, error) + + // Set fetches the token using the given fetch function and caches the token + // for the given scheme with the given key for the given registry. + // The return values of the fetch function is returned by this function. + // The underlying implementation MAY combine the fetch operation if the Set + // function is invoked multiple times at the same time. + Set(ctx context.Context, registry string, scheme Scheme, key string, fetch func(context.Context) (string, error)) (string, error) +} + +// cacheEntry is a cache entry for a single registry. +type cacheEntry struct { + scheme Scheme + tokens sync.Map // map[string]string +} + +// concurrentCache is a cache suitable for concurrent invocation. +type concurrentCache struct { + status sync.Map // map[string]*syncutil.Once + cache sync.Map // map[string]*cacheEntry +} + +// NewCache creates a new go-routine safe cache instance. +func NewCache() Cache { + return &concurrentCache{} +} + +// GetScheme returns the auth-scheme part cached for the given registry. +func (cc *concurrentCache) GetScheme(ctx context.Context, registry string) (Scheme, error) { + entry, ok := cc.cache.Load(registry) + if !ok { + return SchemeUnknown, errdef.ErrNotFound + } + return entry.(*cacheEntry).scheme, nil +} + +// GetToken returns the auth-token part cached for the given registry of a given +// scheme. +func (cc *concurrentCache) GetToken(ctx context.Context, registry string, scheme Scheme, key string) (string, error) { + entryValue, ok := cc.cache.Load(registry) + if !ok { + return "", errdef.ErrNotFound + } + entry := entryValue.(*cacheEntry) + if entry.scheme != scheme { + return "", errdef.ErrNotFound + } + if token, ok := entry.tokens.Load(key); ok { + return token.(string), nil + } + return "", errdef.ErrNotFound +} + +// Set fetches the token using the given fetch function and caches the token +// for the given scheme with the given key for the given registry. +// Set combines the fetch operation if the Set is invoked multiple times at the +// same time. +func (cc *concurrentCache) Set(ctx context.Context, registry string, scheme Scheme, key string, fetch func(context.Context) (string, error)) (string, error) { + // fetch token + statusKey := strings.Join([]string{ + registry, + scheme.String(), + key, + }, " ") + statusValue, _ := cc.status.LoadOrStore(statusKey, syncutil.NewOnce()) + fetchOnce := statusValue.(*syncutil.Once) + fetchedFirst, result, err := fetchOnce.Do(ctx, func() (interface{}, error) { + return fetch(ctx) + }) + if fetchedFirst { + cc.status.Delete(statusKey) + } + if err != nil { + return "", err + } + token := result.(string) + if !fetchedFirst { + return token, nil + } + + // cache token + newEntry := &cacheEntry{ + scheme: scheme, + } + entryValue, exists := cc.cache.LoadOrStore(registry, newEntry) + entry := entryValue.(*cacheEntry) + if exists && entry.scheme != scheme { + // there is a scheme change, which is not expected in most scenarios. + // force invalidating all previous cache. + entry = newEntry + cc.cache.Store(registry, entry) + } + entry.tokens.Store(key, token) + + return token, nil +} + +// noCache is a cache implementation that does not do cache at all. +type noCache struct{} + +// GetScheme always returns not found error as it has no cache. +func (noCache) GetScheme(ctx context.Context, registry string) (Scheme, error) { + return SchemeUnknown, errdef.ErrNotFound +} + +// GetToken always returns not found error as it has no cache. +func (noCache) GetToken(ctx context.Context, registry string, scheme Scheme, key string) (string, error) { + return "", errdef.ErrNotFound +} + +// Set calls fetch directly without caching. +func (noCache) Set(ctx context.Context, registry string, scheme Scheme, key string, fetch func(context.Context) (string, error)) (string, error) { + return fetch(ctx) +} diff --git a/vendor/oras.land/oras-go/pkg/registry/remote/auth/challenge.go b/vendor/oras.land/oras-go/pkg/registry/remote/auth/challenge.go new file mode 100644 index 00000000000..e2d28a11ecf --- /dev/null +++ b/vendor/oras.land/oras-go/pkg/registry/remote/auth/challenge.go @@ -0,0 +1,166 @@ +/* +Copyright The ORAS Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package auth + +import ( + "strconv" + "strings" +) + +// Scheme define the authentication method. +type Scheme byte + +const ( + // SchemeUnknown represents unknown or unsupported schemes + SchemeUnknown Scheme = iota + + // SchemeBasic represents the "Basic" HTTP authentication scheme. + // Reference: https://tools.ietf.org/html/rfc7617 + SchemeBasic + + // SchemeBearer represents the Bearer token in OAuth 2.0. + // Reference: https://tools.ietf.org/html/rfc6750 + SchemeBearer +) + +// parseScheme parse the authentication scheme from the given string +// case-insensitively. +func parseScheme(scheme string) Scheme { + switch { + case strings.EqualFold(scheme, "basic"): + return SchemeBasic + case strings.EqualFold(scheme, "bearer"): + return SchemeBearer + } + return SchemeUnknown +} + +// String return the string for the scheme. +func (s Scheme) String() string { + switch s { + case SchemeBasic: + return "Basic" + case SchemeBearer: + return "Bearer" + } + return "Unknown" +} + +// parseChallenge parses the "WWW-Authenticate" header returned by the remote +// registry, and extracts parameters if scheme is Bearer. +// References: +// - https://docs.docker.com/registry/spec/auth/token/#how-to-authenticate +// - https://tools.ietf.org/html/rfc7235#section-2.1 +func parseChallenge(header string) (scheme Scheme, params map[string]string) { + // as defined in RFC 7235 section 2.1, we have + // challenge = auth-scheme [ 1*SP ( token68 / #auth-param ) ] + // auth-scheme = token + // auth-param = token BWS "=" BWS ( token / quoted-string ) + // + // since we focus parameters only on Bearer, we have + // challenge = auth-scheme [ 1*SP #auth-param ] + schemeString, rest := parseToken(header) + scheme = parseScheme(schemeString) + + // fast path for non bearer challenge + if scheme != SchemeBearer { + return + } + + // parse params for bearer auth. + // combining RFC 7235 section 2.1 with RFC 7230 section 7, we have + // #auth-param => auth-param *( OWS "," OWS auth-param ) + var key, value string + for { + key, rest = parseToken(skipSpace(rest)) + if key == "" { + return + } + + rest = skipSpace(rest) + if rest == "" || rest[0] != '=' { + return + } + rest = skipSpace(rest[1:]) + if rest == "" { + return + } + + if rest[0] == '"' { + prefix, err := strconv.QuotedPrefix(rest) + if err != nil { + return + } + value, err = strconv.Unquote(prefix) + if err != nil { + return + } + rest = rest[len(prefix):] + } else { + value, rest = parseToken(rest) + if value == "" { + return + } + } + if params == nil { + params = map[string]string{ + key: value, + } + } else { + params[key] = value + } + + rest = skipSpace(rest) + if rest == "" || rest[0] != ',' { + return + } + rest = rest[1:] + } +} + +// isNotTokenChar reports whether rune is not a `tchar` defined in RFC 7230 +// section 3.2.6. +func isNotTokenChar(r rune) bool { + // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" + // / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" + // / DIGIT / ALPHA + // ; any VCHAR, except delimiters + return (r < 'A' || r > 'Z') && (r < 'a' || r > 'z') && + (r < '0' || r > '9') && !strings.ContainsRune("!#$%&'*+-.^_`|~", r) +} + +// parseToken finds the next token from the given string. If no token found, +// an empty token is returned and the whole of the input is returned in rest. +// Note: Since token = 1*tchar, empty string is not a valid token. +func parseToken(s string) (token, rest string) { + if i := strings.IndexFunc(s, isNotTokenChar); i != -1 { + return s[:i], s[i:] + } + return s, "" +} + +// skipSpace skips "bad" whitespace (BWS) defined in RFC 7230 section 3.2.3. +func skipSpace(s string) string { + // OWS = *( SP / HTAB ) + // ; optional whitespace + // BWS = OWS + // ; "bad" whitespace + if i := strings.IndexFunc(s, func(r rune) bool { + return r != ' ' && r != '\t' + }); i != -1 { + return s[i:] + } + return s +} diff --git a/vendor/oras.land/oras-go/pkg/registry/remote/auth/client.go b/vendor/oras.land/oras-go/pkg/registry/remote/auth/client.go new file mode 100644 index 00000000000..ef1c9479dc3 --- /dev/null +++ b/vendor/oras.land/oras-go/pkg/registry/remote/auth/client.go @@ -0,0 +1,367 @@ +/* +Copyright The ORAS Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package auth + +import ( + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "oras.land/oras-go/pkg/registry/remote/internal/errutil" +) + +// DefaultClient is the default auth-decorated client. +var DefaultClient = &Client{ + Header: http.Header{ + "User-Agent": {"oras-go"}, + }, + Cache: DefaultCache, +} + +// maxResponseBytes specifies the default limit on how many response bytes are +// allowed in the server's response from authorization service servers. +// A typical response message from authorization service servers is around 1 to +// 4 KiB. Since the size of a token must be smaller than the HTTP header size +// limit, which is usually 16 KiB. As specified by the distribution, the +// response may contain 2 identical tokens, that is, 16 x 2 = 32 KiB. +// Hence, 128 KiB should be sufficient. +// References: https://docs.docker.com/registry/spec/auth/token/ +var maxResponseBytes int64 = 128 * 1024 // 128 KiB + +// defaultClientID specifies the default client ID used in OAuth2. +// See also ClientID. +var defaultClientID = "oras-go" + +// Client is an auth-decorated HTTP client. +// Its zero value is a usable client that uses http.DefaultClient with no cache. +type Client struct { + // Client is the underlying HTTP client used to access the remote + // server. + // If nil, http.DefaultClient is used. + Client *http.Client + + // Header contains the custom headers to be added to each request. + Header http.Header + + // Credential specifies the function for resolving the credential for the + // given registry (i.e. host:port). + // `EmptyCredential` is a valid return value and should not be considered as + // an error. + // If nil, the credential is always resolved to `EmptyCredential`. + Credential func(context.Context, string) (Credential, error) + + // Cache caches credentials for direct accessing the remote registry. + // If nil, no cache is used. + Cache Cache + + // ClientID used in fetching OAuth2 token as a required field. + // If empty, a default client ID is used. + // Reference: https://docs.docker.com/registry/spec/auth/oauth/#getting-a-token + ClientID string + + // ForceAttemptOAuth2 controls whether to follow OAuth2 with password grant + // instead the distribution spec when authenticating using username and + // password. + // References: + // - https://docs.docker.com/registry/spec/auth/jwt/ + // - https://docs.docker.com/registry/spec/auth/oauth/ + ForceAttemptOAuth2 bool +} + +// client returns an HTTP client used to access the remote registry. +// http.DefaultClient is return if the client is not configured. +func (c *Client) client() *http.Client { + if c.Client == nil { + return http.DefaultClient + } + return c.Client +} + +// send adds headers to the request and sends the request to the remote server. +func (c *Client) send(req *http.Request) (*http.Response, error) { + for key, values := range c.Header { + req.Header[key] = append(req.Header[key], values...) + } + return c.client().Do(req) +} + +// credential resolves the credential for the given registry. +func (c *Client) credential(ctx context.Context, reg string) (Credential, error) { + if c.Credential == nil { + return EmptyCredential, nil + } + return c.Credential(ctx, reg) +} + +// cache resolves the cache. +// noCache is return if the cache is not configured. +func (c *Client) cache() Cache { + if c.Cache == nil { + return noCache{} + } + return c.Cache +} + +// SetUserAgent sets the user agent for all out-going requests. +func (c *Client) SetUserAgent(userAgent string) { + if c.Header == nil { + c.Header = http.Header{} + } + c.Header.Set("User-Agent", userAgent) +} + +// Do sends the request to the remote server with resolving authentication +// attempted. +// On authentication failure due to bad credential, +// - Do returns error if it fails to fetch token for bearer auth. +// - Do returns the registry response without error for basic auth. +func (c *Client) Do(originalReq *http.Request) (*http.Response, error) { + ctx := originalReq.Context() + req := originalReq.Clone(ctx) + + // attempt cached auth token + var attemptedKey string + cache := c.cache() + registry := originalReq.Host + scheme, err := cache.GetScheme(ctx, registry) + if err == nil { + switch scheme { + case SchemeBasic: + token, err := cache.GetToken(ctx, registry, SchemeBasic, "") + if err == nil { + req.Header.Set("Authorization", "Basic "+token) + } + case SchemeBearer: + scopes := GetScopes(ctx) + attemptedKey = strings.Join(scopes, " ") + token, err := cache.GetToken(ctx, registry, SchemeBearer, attemptedKey) + if err == nil { + req.Header.Set("Authorization", "Bearer "+token) + } + } + } + + resp, err := c.send(req) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusUnauthorized { + return resp, nil + } + + // attempt again with credentials for recognized schemes + challenge := resp.Header.Get("Www-Authenticate") + scheme, params := parseChallenge(challenge) + switch scheme { + case SchemeBasic: + resp.Body.Close() + + token, err := cache.Set(ctx, registry, SchemeBasic, "", func(ctx context.Context) (string, error) { + return c.fetchBasicAuth(ctx, registry) + }) + if err != nil { + return nil, fmt.Errorf("%s %q: %w", resp.Request.Method, resp.Request.URL, err) + } + + req = originalReq.Clone(ctx) + req.Header.Set("Authorization", "Basic "+token) + case SchemeBearer: + resp.Body.Close() + + // merge hinted scopes with challenged scopes + scopes := GetScopes(ctx) + if scope := params["scope"]; scope != "" { + scopes = append(scopes, strings.Split(scope, " ")...) + scopes = CleanScopes(scopes) + } + key := strings.Join(scopes, " ") + + // attempt the cache again if there is a scope change + if key != attemptedKey { + if token, err := cache.GetToken(ctx, registry, SchemeBearer, key); err == nil { + req = originalReq.Clone(ctx) + req.Header.Set("Authorization", "Bearer "+token) + + resp, err := c.send(req) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusUnauthorized { + return resp, nil + } + resp.Body.Close() + } + } + + // attempt with credentials + realm := params["realm"] + service := params["service"] + token, err := cache.Set(ctx, registry, SchemeBearer, key, func(ctx context.Context) (string, error) { + return c.fetchBearerToken(ctx, registry, realm, service, scopes) + }) + if err != nil { + return nil, fmt.Errorf("%s %q: %w", resp.Request.Method, resp.Request.URL, err) + } + + req = originalReq.Clone(ctx) + req.Header.Set("Authorization", "Bearer "+token) + default: + return resp, nil + } + + return c.send(req) +} + +// fetchBasicAuth fetches a basic auth token for the basic challenge. +func (c *Client) fetchBasicAuth(ctx context.Context, registry string) (string, error) { + cred, err := c.credential(ctx, registry) + if err != nil { + return "", fmt.Errorf("failed to resolve credential: %w", err) + } + if cred == EmptyCredential { + return "", errors.New("credential required for basic auth") + } + if cred.Username == "" || cred.Password == "" { + return "", errors.New("missing username or password for basic auth") + } + auth := cred.Username + ":" + cred.Password + return base64.StdEncoding.EncodeToString([]byte(auth)), nil +} + +// fetchBearerToken fetches an access token for the bearer challenge. +func (c *Client) fetchBearerToken(ctx context.Context, registry, realm, service string, scopes []string) (string, error) { + cred, err := c.credential(ctx, registry) + if err != nil { + return "", err + } + if cred.AccessToken != "" { + return cred.AccessToken, nil + } + if cred == EmptyCredential || (cred.RefreshToken == "" && !c.ForceAttemptOAuth2) { + return c.fetchDistributionToken(ctx, realm, service, scopes, cred.Username, cred.Password) + } + return c.fetchOAuth2Token(ctx, realm, service, scopes, cred) +} + +// fetchDistributionToken fetches an access token as defined by the distribution +// specification. +// It fetches anonymous tokens if no credential is provided. +// References: +// - https://docs.docker.com/registry/spec/auth/jwt/ +// - https://docs.docker.com/registry/spec/auth/token/ +func (c *Client) fetchDistributionToken(ctx context.Context, realm, service string, scopes []string, username, password string) (string, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, realm, nil) + if err != nil { + return "", err + } + if username != "" || password != "" { + req.SetBasicAuth(username, password) + } + q := req.URL.Query() + if service != "" { + q.Set("service", service) + } + for _, scope := range scopes { + q.Add("scope", scope) + } + req.URL.RawQuery = q.Encode() + + resp, err := c.send(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", errutil.ParseErrorResponse(resp) + } + + // As specified in https://docs.docker.com/registry/spec/auth/token/ section + // "Token Response Fields", the token is either in `token` or + // `access_token`. If both present, they are identical. + var result struct { + Token string `json:"token"` + AccessToken string `json:"access_token"` + } + lr := io.LimitReader(resp.Body, maxResponseBytes) + if err := json.NewDecoder(lr).Decode(&result); err != nil { + return "", fmt.Errorf("%s %q: failed to decode response: %w", resp.Request.Method, resp.Request.URL, err) + } + if result.AccessToken != "" { + return result.AccessToken, nil + } + if result.Token != "" { + return result.Token, nil + } + return "", fmt.Errorf("%s %q: empty token returned", resp.Request.Method, resp.Request.URL) +} + +// fetchOAuth2Token fetches an OAuth2 access token. +// Reference: https://docs.docker.com/registry/spec/auth/oauth/ +func (c *Client) fetchOAuth2Token(ctx context.Context, realm, service string, scopes []string, cred Credential) (string, error) { + form := url.Values{} + if cred.RefreshToken != "" { + form.Set("grant_type", "refresh_token") + form.Set("refresh_token", cred.RefreshToken) + } else if cred.Username != "" && cred.Password != "" { + form.Set("grant_type", "password") + form.Set("username", cred.Username) + form.Set("password", cred.Password) + } else { + return "", errors.New("missing username or password for bearer auth") + } + form.Set("service", service) + clientID := c.ClientID + if clientID == "" { + clientID = defaultClientID + } + form.Set("client_id", clientID) + if len(scopes) != 0 { + form.Set("scope", strings.Join(scopes, " ")) + } + body := strings.NewReader(form.Encode()) + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, realm, body) + if err != nil { + return "", err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + resp, err := c.send(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", errutil.ParseErrorResponse(resp) + } + + var result struct { + AccessToken string `json:"access_token"` + } + lr := io.LimitReader(resp.Body, maxResponseBytes) + if err := json.NewDecoder(lr).Decode(&result); err != nil { + return "", fmt.Errorf("%s %q: failed to decode response: %w", resp.Request.Method, resp.Request.URL, err) + } + if result.AccessToken != "" { + return result.AccessToken, nil + } + return "", fmt.Errorf("%s %q: empty token returned", resp.Request.Method, resp.Request.URL) +} diff --git a/vendor/oras.land/oras-go/pkg/registry/remote/auth/credential.go b/vendor/oras.land/oras-go/pkg/registry/remote/auth/credential.go new file mode 100644 index 00000000000..2cd9f774bf4 --- /dev/null +++ b/vendor/oras.land/oras-go/pkg/registry/remote/auth/credential.go @@ -0,0 +1,39 @@ +/* +Copyright The ORAS Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package auth + +// EmptyCredential represents an empty credential. +var EmptyCredential Credential + +// Credential contains authentication credentials used to access remote +// registries. +type Credential struct { + // Username is the name of the user for the remote registry. + Username string + + // Password is the secret associated with the username. + Password string + + // RefreshToken is a bearer token to be sent to the authorization service + // for fetching access tokens. + // A refresh token is often referred as an identity token. + // Reference: https://docs.docker.com/registry/spec/auth/oauth/ + RefreshToken string + + // AccessToken is a bearer token to be sent to the registry. + // An access token is often referred as a registry token. + // Reference: https://docs.docker.com/registry/spec/auth/token/ + AccessToken string +} diff --git a/vendor/oras.land/oras-go/pkg/registry/remote/auth/scope.go b/vendor/oras.land/oras-go/pkg/registry/remote/auth/scope.go new file mode 100644 index 00000000000..3b865dd60f9 --- /dev/null +++ b/vendor/oras.land/oras-go/pkg/registry/remote/auth/scope.go @@ -0,0 +1,231 @@ +/* +Copyright The ORAS Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package auth + +import ( + "context" + "sort" + "strings" +) + +// Actions used in scopes. +// Reference: https://docs.docker.com/registry/spec/auth/scope/ +const ( + // ActionPull represents generic read access for resources of the repository + // type. + ActionPull = "pull" + + // ActionPush represents generic write access for resources of the + // repository type. + ActionPush = "push" + + // ActionDelete represents the delete permission for resources of the + // repository type. + ActionDelete = "delete" +) + +// ScopeRegistryCatalog is the scope for registry catalog access. +const ScopeRegistryCatalog = "registry:catalog:*" + +// ScopeRepository returns a repository scope with given actions. +// Reference: https://docs.docker.com/registry/spec/auth/scope/ +func ScopeRepository(repository string, actions ...string) string { + actions = cleanActions(actions) + if repository == "" || len(actions) == 0 { + return "" + } + return strings.Join([]string{ + "repository", + repository, + strings.Join(actions, ","), + }, ":") +} + +// scopesContextKey is the context key for scopes. +type scopesContextKey struct{} + +// WithScopes returns a context with scopes added. Scopes are de-duplicated. +// Scopes are used as hints for the auth client to fetch bearer tokens with +// larger scopes. +// For example, uploading blob to the repository "hello-world" does HEAD request +// first then POST and PUT. The HEAD request will return a challenge for scope +// `repository:hello-world:pull`, and the auth client will fetch a token for +// that challenge. Later, the POST request will return a challenge for scope +// `repository:hello-world:push`, and the auth client will fetch a token for +// that challenge again. By invoking `WithScopes()` with the scope +// `repository:hello-world:pull,push`, the auth client with cache is hinted to +// fetch a token via a single token fetch request for all the HEAD, POST, PUT +// requests. +// Passing an empty list of scopes will virtually remove the scope hints in the +// context. +// Reference: https://docs.docker.com/registry/spec/auth/scope/ +func WithScopes(ctx context.Context, scopes ...string) context.Context { + scopes = CleanScopes(scopes) + return context.WithValue(ctx, scopesContextKey{}, scopes) +} + +// AppendScopes appends additional scopes to the existing scopes in the context +// and returns a new context. The resulted scopes are de-duplicated. +// The append operation does modify the existing scope in the context passed in. +func AppendScopes(ctx context.Context, scopes ...string) context.Context { + if len(scopes) == 0 { + return ctx + } + return WithScopes(ctx, append(GetScopes(ctx), scopes...)...) +} + +// GetScopes returns the scopes in the context. +func GetScopes(ctx context.Context) []string { + if scopes, ok := ctx.Value(scopesContextKey{}).([]string); ok { + return append([]string(nil), scopes...) + } + return nil +} + +// CleanScopes merges and sort the actions in ascending order if the scopes have +// the same resource type and name. The final scopes are sorted in ascending +// order. In other words, the scopes passed in are de-duplicated and sorted. +// Therefore, the output of this function is deterministic. +// If there is a wildcard `*` in the action, other actions in the same resource +// type and name are ignored. +func CleanScopes(scopes []string) []string { + // fast paths + switch len(scopes) { + case 0: + return nil + case 1: + scope := scopes[0] + i := strings.LastIndex(scope, ":") + if i == -1 { + return []string{scope} + } + actionList := strings.Split(scope[i+1:], ",") + actionList = cleanActions(actionList) + if len(actionList) == 0 { + return nil + } + actions := strings.Join(actionList, ",") + scope = scope[:i+1] + actions + return []string{scope} + } + + // slow path + var result []string + + // merge recognizable scopes + resourceTypes := make(map[string]map[string]map[string]struct{}) + for _, scope := range scopes { + // extract resource type + i := strings.Index(scope, ":") + if i == -1 { + result = append(result, scope) + continue + } + resourceType := scope[:i] + + // extract resource name and actions + rest := scope[i+1:] + i = strings.LastIndex(rest, ":") + if i == -1 { + result = append(result, scope) + continue + } + resourceName := rest[:i] + actions := rest[i+1:] + if actions == "" { + // drop scope since no action found + continue + } + + // add to the intermediate map for de-duplication + namedActions := resourceTypes[resourceType] + if namedActions == nil { + namedActions = make(map[string]map[string]struct{}) + resourceTypes[resourceType] = namedActions + } + actionSet := namedActions[resourceName] + if actionSet == nil { + actionSet = make(map[string]struct{}) + namedActions[resourceName] = actionSet + } + for _, action := range strings.Split(actions, ",") { + if action != "" { + actionSet[action] = struct{}{} + } + } + } + + // reconstruct scopes + for resourceType, namedActions := range resourceTypes { + for resourceName, actionSet := range namedActions { + if len(actionSet) == 0 { + continue + } + var actions []string + for action := range actionSet { + if action == "*" { + actions = []string{"*"} + break + } + actions = append(actions, action) + } + sort.Strings(actions) + scope := resourceType + ":" + resourceName + ":" + strings.Join(actions, ",") + result = append(result, scope) + } + } + + // sort and return + sort.Strings(result) + return result +} + +// cleanActions removes the duplicated actions and sort in ascending order. +// If there is a wildcard `*` in the action, other actions are ignored. +func cleanActions(actions []string) []string { + // fast paths + switch len(actions) { + case 0: + return nil + case 1: + if actions[0] == "" { + return nil + } + return actions + } + + // slow path + sort.Strings(actions) + n := 0 + for i := 0; i < len(actions); i++ { + if actions[i] == "*" { + return []string{"*"} + } + if actions[i] != actions[n] { + n++ + if n != i { + actions[n] = actions[i] + } + } + } + n++ + if actions[0] == "" { + if n == 1 { + return nil + } + return actions[1:n] + } + return actions[:n] +} diff --git a/vendor/oras.land/oras-go/pkg/registry/remote/internal/errutil/errors.go b/vendor/oras.land/oras-go/pkg/registry/remote/internal/errutil/errors.go new file mode 100644 index 00000000000..201ecce89ff --- /dev/null +++ b/vendor/oras.land/oras-go/pkg/registry/remote/internal/errutil/errors.go @@ -0,0 +1,83 @@ +/* +Copyright The ORAS Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package errutil + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "unicode" +) + +// maxErrorBytes specifies the default limit on how many response bytes are +// allowed in the server's error response. +// A typical error message is around 200 bytes. Hence, 8 KiB should be +// sufficient. +var maxErrorBytes int64 = 8 * 1024 // 8 KiB + +// requestError contains a single error. +type requestError struct { + Code string `json:"code"` + Message string `json:"message"` +} + +// Error returns a error string describing the error. +func (e requestError) Error() string { + code := strings.Map(func(r rune) rune { + if r == '_' { + return ' ' + } + return unicode.ToLower(r) + }, e.Code) + if e.Message == "" { + return code + } + return fmt.Sprintf("%s: %s", code, e.Message) +} + +// requestErrors is a bundle of requestError. +type requestErrors []requestError + +// Error returns a error string describing the error. +func (errs requestErrors) Error() string { + switch len(errs) { + case 0: + return "" + case 1: + return errs[0].Error() + } + var errmsgs []string + for _, err := range errs { + errmsgs = append(errmsgs, err.Error()) + } + return strings.Join(errmsgs, "; ") +} + +// ParseErrorResponse parses the error returned by the remote registry. +func ParseErrorResponse(resp *http.Response) error { + var errmsg string + var body struct { + Errors requestErrors `json:"errors"` + } + lr := io.LimitReader(resp.Body, maxErrorBytes) + if err := json.NewDecoder(lr).Decode(&body); err == nil && len(body.Errors) > 0 { + errmsg = body.Errors.Error() + } else { + errmsg = http.StatusText(resp.StatusCode) + } + return fmt.Errorf("%s %q: unexpected status code %d: %s", resp.Request.Method, resp.Request.URL, resp.StatusCode, errmsg) +} diff --git a/vendor/oras.land/oras-go/pkg/registry/remote/internal/syncutil/once.go b/vendor/oras.land/oras-go/pkg/registry/remote/internal/syncutil/once.go new file mode 100644 index 00000000000..a685360b6e0 --- /dev/null +++ b/vendor/oras.land/oras-go/pkg/registry/remote/internal/syncutil/once.go @@ -0,0 +1,69 @@ +/* +Copyright The ORAS Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package syncutil + +import "context" + +// Once is an object that will perform exactly one action. +// Unlike sync.Once, this Once allowes the action to have return values. +type Once struct { + result interface{} + err error + status chan bool +} + +// NewOnce creates a new Once instance. +func NewOnce() *Once { + status := make(chan bool, 1) + status <- true + return &Once{ + status: status, + } +} + +// Do calls the function f if and only if Do is being called first time or all +// previous function calls are cancelled, deadline exceeded, or panicking. +// When `once.Do(ctx, f)` is called multiple times, the return value of the +// first call of the function f is stored, and is directly returned for other +// calls. +// Besides the return value of the function f, including the error, Do returns +// true if the function f passed is called first and is not cancelled, deadline +// exceeded, or panicking. Otherwise, returns false. +func (o *Once) Do(ctx context.Context, f func() (interface{}, error)) (bool, interface{}, error) { + defer func() { + if r := recover(); r != nil { + o.status <- true + panic(r) + } + }() + for { + select { + case inProgress := <-o.status: + if !inProgress { + return false, o.result, o.err + } + result, err := f() + if err == context.Canceled || err == context.DeadlineExceeded { + o.status <- true + return false, nil, err + } + o.result, o.err = result, err + close(o.status) + return true, result, err + case <-ctx.Done(): + return false, nil, ctx.Err() + } + } +} diff --git a/vendor/oras.land/oras-go/pkg/registry/remote/repository.go b/vendor/oras.land/oras-go/pkg/registry/remote/repository.go new file mode 100644 index 00000000000..0fbd37b6638 --- /dev/null +++ b/vendor/oras.land/oras-go/pkg/registry/remote/repository.go @@ -0,0 +1,171 @@ +/* +Copyright The ORAS Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package remote + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strconv" + + errdef "oras.land/oras-go/pkg/content" + "oras.land/oras-go/pkg/registry" + "oras.land/oras-go/pkg/registry/remote/auth" + "oras.land/oras-go/pkg/registry/remote/internal/errutil" +) + +// Client is an interface for a HTTP client. +type Client interface { + // Do sends an HTTP request and returns an HTTP response. + // + // Unlike http.RoundTripper, Client can attempt to interpret the response + // and handle higher-level protocol details such as redirects and + // authentication. + // + // Like http.RoundTripper, Client should not modify the request, and must + // always close the request body. + Do(*http.Request) (*http.Response, error) +} + +// Repository is an HTTP client to a remote repository. +type Repository struct { + // Client is the underlying HTTP client used to access the remote registry. + // If nil, auth.DefaultClient is used. + Client Client + + // Reference references the remote repository. + Reference registry.Reference + + // PlainHTTP signals the transport to access the remote repository via HTTP + // instead of HTTPS. + PlainHTTP bool + + // ManifestMediaTypes is used in `Accept` header for resolving manifests from + // references. It is also used in identifying manifests and blobs from + // descriptors. + // If an empty list is present, default manifest media types are used. + ManifestMediaTypes []string + + // TagListPageSize specifies the page size when invoking the tag list API. + // If zero, the page size is determined by the remote registry. + // Reference: https://docs.docker.com/registry/spec/api/#tags + TagListPageSize int + + // ReferrerListPageSize specifies the page size when invoking the Referrers + // API. + // If zero, the page size is determined by the remote registry. + // Reference: https://github.com/oras-project/artifacts-spec/blob/main/manifest-referrers-api.md + ReferrerListPageSize int + + // MaxMetadataBytes specifies a limit on how many response bytes are allowed + // in the server's response to the metadata APIs, such as catalog list, tag + // list, and referrers list. + // If zero, a default (currently 4MiB) is used. + MaxMetadataBytes int64 +} + +// NewRepository creates a client to the remote repository identified by a +// reference. +// Example: localhost:5000/hello-world +func NewRepository(reference string) (*Repository, error) { + ref, err := registry.ParseReference(reference) + if err != nil { + return nil, err + } + return &Repository{ + Reference: ref, + }, nil +} + +// client returns an HTTP client used to access the remote repository. +// A default HTTP client is return if the client is not configured. +func (r *Repository) client() Client { + if r.Client == nil { + return auth.DefaultClient + } + return r.Client +} + +// parseReference validates the reference. +// Both simplified or fully qualified references are accepted as input. +// A fully qualified reference is returned on success. +func (r *Repository) parseReference(reference string) (registry.Reference, error) { + ref, err := registry.ParseReference(reference) + if err != nil { + ref = registry.Reference{ + Registry: r.Reference.Registry, + Repository: r.Reference.Repository, + Reference: reference, + } + if err = ref.ValidateReference(); err != nil { + return registry.Reference{}, err + } + return ref, nil + } + if ref.Registry == r.Reference.Registry && ref.Repository == r.Reference.Repository { + return ref, nil + } + return registry.Reference{}, fmt.Errorf("%w %q: expect %q", errdef.ErrInvalidReference, ref, r.Reference) +} + +// Tags lists the tags available in the repository. +func (r *Repository) Tags(ctx context.Context, fn func(tags []string) error) error { + ctx = withScopeHint(ctx, r.Reference, auth.ActionPull) + url := buildRepositoryTagListURL(r.PlainHTTP, r.Reference) + var err error + for err == nil { + url, err = r.tags(ctx, fn, url) + } + if err != errNoLink { + return err + } + return nil +} + +// tags returns a single page of tag list with the next link. +func (r *Repository) tags(ctx context.Context, fn func(tags []string) error, url string) (string, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return "", err + } + if r.TagListPageSize > 0 { + q := req.URL.Query() + q.Set("n", strconv.Itoa(r.TagListPageSize)) + req.URL.RawQuery = q.Encode() + } + + resp, err := r.client().Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", errutil.ParseErrorResponse(resp) + } + var page struct { + Tags []string `json:"tags"` + } + lr := limitReader(resp.Body, r.MaxMetadataBytes) + if err := json.NewDecoder(lr).Decode(&page); err != nil { + return "", fmt.Errorf("%s %q: failed to decode response: %w", resp.Request.Method, resp.Request.URL, err) + } + if err := fn(page.Tags); err != nil { + return "", err + } + + return parseLink(resp) +} diff --git a/vendor/oras.land/oras-go/pkg/registry/remote/url.go b/vendor/oras.land/oras-go/pkg/registry/remote/url.go new file mode 100644 index 00000000000..f01752020f0 --- /dev/null +++ b/vendor/oras.land/oras-go/pkg/registry/remote/url.go @@ -0,0 +1,42 @@ +/* +Copyright The ORAS Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package remote + +import ( + "fmt" + + "oras.land/oras-go/pkg/registry" +) + +// buildScheme returns HTTP scheme used to access the remote registry. +func buildScheme(plainHTTP bool) string { + if plainHTTP { + return "http" + } + return "https" +} + +// buildRepositoryBaseURL builds the base endpoint of the remote repository. +// Format: :///v2/ +func buildRepositoryBaseURL(plainHTTP bool, ref registry.Reference) string { + return fmt.Sprintf("%s://%s/v2/%s", buildScheme(plainHTTP), ref.Host(), ref.Repository) +} + +// buildRepositoryTagListURL builds the URL for accessing the tag list API. +// Format: :///v2//tags/list +// Reference: https://docs.docker.com/registry/spec/api/#tags +func buildRepositoryTagListURL(plainHTTP bool, ref registry.Reference) string { + return buildRepositoryBaseURL(plainHTTP, ref) + "/tags/list" +} diff --git a/vendor/oras.land/oras-go/pkg/registry/remote/utils.go b/vendor/oras.land/oras-go/pkg/registry/remote/utils.go new file mode 100644 index 00000000000..eaa84f0f40b --- /dev/null +++ b/vendor/oras.land/oras-go/pkg/registry/remote/utils.go @@ -0,0 +1,72 @@ +/* +Copyright The ORAS Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package remote + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "strings" + + "oras.land/oras-go/pkg/registry" + "oras.land/oras-go/pkg/registry/remote/auth" +) + +// defaultMaxMetadataBytes specifies the default limit on how many response +// bytes are allowed in the server's response to the metadata APIs. +// See also: Repository.MaxMetadataBytes +var defaultMaxMetadataBytes int64 = 4 * 1024 * 1024 // 4 MiB + +// errNoLink is returned by parseLink() when no Link header is present. +var errNoLink = errors.New("no Link header in response") + +// parseLink returns the URL of the response's "Link" header, if present. +func parseLink(resp *http.Response) (string, error) { + link := resp.Header.Get("Link") + if link == "" { + return "", errNoLink + } + if link[0] != '<' { + return "", fmt.Errorf("invalid next link %q: missing '<'", link) + } + if i := strings.IndexByte(link, '>'); i == -1 { + return "", fmt.Errorf("invalid next link %q: missing '>'", link) + } else { + link = link[1:i] + } + + linkURL, err := resp.Request.URL.Parse(link) + if err != nil { + return "", err + } + return linkURL.String(), nil +} + +// limitReader returns a Reader that reads from r but stops with EOF after n +// bytes. If n is zero, defaultMaxMetadataBytes is used. +func limitReader(r io.Reader, n int64) io.Reader { + if n == 0 { + n = defaultMaxMetadataBytes + } + return io.LimitReader(r, n) +} + +// withScopeHint adds a hinted scope to the context. +func withScopeHint(ctx context.Context, ref registry.Reference, actions ...string) context.Context { + scope := auth.ScopeRepository(ref.Repository, actions...) + return auth.AppendScopes(ctx, scope) +} diff --git a/vendor/oras.land/oras-go/pkg/registry/repository.go b/vendor/oras.land/oras-go/pkg/registry/repository.go new file mode 100644 index 00000000000..bcffbe8d2c0 --- /dev/null +++ b/vendor/oras.land/oras-go/pkg/registry/repository.go @@ -0,0 +1,57 @@ +/* +Copyright The ORAS Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package registry + +import ( + "context" +) + +// Repository is an ORAS target and an union of the blob and the manifest CASs. +// As specified by https://docs.docker.com/registry/spec/api/, it is natural to +// assume that content.Resolver interface only works for manifests. Tagging a +// blob may be resulted in an `ErrUnsupported` error. However, this interface +// does not restrict tagging blobs. +// Since a repository is an union of the blob and the manifest CASs, all +// operations defined in the `BlobStore` are executed depending on the media +// type of the given descriptor accordingly. +// Furthurmore, this interface also provides the ability to enforce the +// separation of the blob and the manifests CASs. +type Repository interface { + + // Tags lists the tags available in the repository. + // Since the returned tag list may be paginated by the underlying + // implementation, a function should be passed in to process the paginated + // tag list. + // Note: When implemented by a remote registry, the tags API is called. + // However, not all registries supports pagination or conforms the + // specification. + // References: + // - https://github.com/opencontainers/distribution-spec/blob/main/spec.md#content-discovery + // - https://docs.docker.com/registry/spec/api/#tags + // See also `Tags()` in this package. + Tags(ctx context.Context, fn func(tags []string) error) error +} + +// Tags lists the tags available in the repository. +func Tags(ctx context.Context, repo Repository) ([]string, error) { + var res []string + if err := repo.Tags(ctx, func(tags []string) error { + res = append(res, tags...) + return nil + }); err != nil { + return nil, err + } + return res, nil +} diff --git a/vendor/sigs.k8s.io/kustomize/api/filters/nameref/nameref.go b/vendor/sigs.k8s.io/kustomize/api/filters/nameref/nameref.go index ff83420cb18..c25cf82823a 100644 --- a/vendor/sigs.k8s.io/kustomize/api/filters/nameref/nameref.go +++ b/vendor/sigs.k8s.io/kustomize/api/filters/nameref/nameref.go @@ -205,16 +205,14 @@ func getRoleRefGvk(n *resource.Resource) (*resid.Gvk, error) { return nil, err } if apiGroup.IsNil() { - return nil, fmt.Errorf( - "apiGroup cannot be found in roleRef %s", roleRef.MustString()) + return nil, fmt.Errorf("apiGroup cannot be found in roleRef %s", roleRef.MustString()) } kind, err := roleRef.Pipe(yaml.Lookup("kind")) if err != nil { return nil, err } if kind.IsNil() { - return nil, fmt.Errorf( - "kind cannot be found in roleRef %s", roleRef.MustString()) + return nil, fmt.Errorf("kind cannot be found in roleRef %s", roleRef.MustString()) } return &resid.Gvk{ Group: apiGroup.YNode().Value, diff --git a/vendor/sigs.k8s.io/kustomize/api/filters/replacement/replacement.go b/vendor/sigs.k8s.io/kustomize/api/filters/replacement/replacement.go index 56e70d87562..8afc646d26e 100644 --- a/vendor/sigs.k8s.io/kustomize/api/filters/replacement/replacement.go +++ b/vendor/sigs.k8s.io/kustomize/api/filters/replacement/replacement.go @@ -4,6 +4,7 @@ package replacement import ( + "encoding/json" "fmt" "strings" @@ -11,7 +12,6 @@ import ( "sigs.k8s.io/kustomize/api/resource" "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/kyaml/errors" - "sigs.k8s.io/kustomize/kyaml/resid" kyaml_utils "sigs.k8s.io/kustomize/kyaml/utils" "sigs.k8s.io/kustomize/kyaml/yaml" ) @@ -117,6 +117,10 @@ func applyReplacement(nodes []*yaml.RNode, value *yaml.RNode, targetSelectors [] if len(selector.FieldPaths) == 0 { selector.FieldPaths = []string{types.DefaultReplacementFieldPath} } + tsr, err := types.NewTargetSelectorRegex(selector) + if err != nil { + return nil, fmt.Errorf("error creating target selector: %w", err) + } for _, possibleTarget := range nodes { ids, err := utils.MakeResIds(possibleTarget) if err != nil { @@ -132,9 +136,13 @@ func applyReplacement(nodes []*yaml.RNode, value *yaml.RNode, targetSelectors [] continue } + if tsr.RejectsAny(ids) { + continue + } + // filter targets by matching resource IDs for _, id := range ids { - if id.IsSelectedBy(selector.Select.ResId) && !containsRejectId(selector.Reject, ids) { + if tsr.Selects(id) { err := copyValueToTarget(possibleTarget, value, selector) if err != nil { return nil, err @@ -175,43 +183,37 @@ func matchesAnnoAndLabelSelector(n *yaml.RNode, selector *types.Selector) (bool, return annoMatch && labelMatch, nil } -func containsRejectId(rejects []*types.Selector, ids []resid.ResId) bool { - for _, r := range rejects { - if r.ResId.IsEmpty() { - continue - } - for _, id := range ids { - if id.IsSelectedBy(r.ResId) { - return true - } - } - } - return false -} - func copyValueToTarget(target *yaml.RNode, value *yaml.RNode, selector *types.TargetSelector) error { for _, fp := range selector.FieldPaths { createKind := yaml.Kind(0) // do not create if selector.Options != nil && selector.Options.Create { createKind = value.YNode().Kind } + + // Check if this fieldPath contains structured data access + if err := setValueInStructuredData(target, value, fp, createKind); err == nil { + // Successfully handled as structured data + continue + } + + // Fall back to normal path handling targetFieldList, err := target.Pipe(&yaml.PathMatcher{ Path: kyaml_utils.SmarterPathSplitter(fp, "."), Create: createKind}) if err != nil { - return errors.WrapPrefixf(err, fieldRetrievalError(fp, createKind != 0)) //nolint:govet + return errors.WrapPrefixf(err, "%s", fieldRetrievalError(fp, createKind != 0)) } targetFields, err := targetFieldList.Elements() if err != nil { - return errors.WrapPrefixf(err, fieldRetrievalError(fp, createKind != 0)) //nolint:govet + return errors.WrapPrefixf(err, "%s", fieldRetrievalError(fp, createKind != 0)) } if len(targetFields) == 0 { - return errors.Errorf(fieldRetrievalError(fp, createKind != 0)) //nolint:govet + return errors.Errorf("%s", fieldRetrievalError(fp, createKind != 0)) } for _, t := range targetFields { if err := setFieldValue(selector.Options, t, value); err != nil { - return err + return fmt.Errorf("%w", err) } } } @@ -254,3 +256,146 @@ func setFieldValue(options *types.FieldOptions, targetField *yaml.RNode, value * return nil } + +// setValueInStructuredData handles setting values within structured data (JSON/YAML) in scalar fields +func setValueInStructuredData(target *yaml.RNode, value *yaml.RNode, fieldPath string, createKind yaml.Kind) error { + pathParts := kyaml_utils.SmarterPathSplitter(fieldPath, ".") + if len(pathParts) < 2 { + return fmt.Errorf("not a structured data path") + } + + // Find the potential scalar field that might contain structured data + var scalarFieldPath []string + var structuredDataPath []string + var foundScalar = false + + // Try to find where the scalar field ends and structured data begins + for i := 1; i <= len(pathParts); i++ { + potentialScalarPath := pathParts[:i] + scalarField, err := target.Pipe(yaml.Lookup(potentialScalarPath...)) + if err != nil { + continue + } + if scalarField != nil && scalarField.YNode().Kind == yaml.ScalarNode && i < len(pathParts) { + // Try to parse the scalar value as structured data + scalarValue := scalarField.YNode().Value + var parsedNode yaml.Node + if err := yaml.Unmarshal([]byte(scalarValue), &parsedNode); err == nil { + // Successfully parsed - this is structured data + scalarFieldPath = potentialScalarPath + structuredDataPath = pathParts[i:] + foundScalar = true + break + } + } + } + + if !foundScalar { + return fmt.Errorf("no structured data found in path") + } + + // Get the scalar field containing structured data + scalarField, err := target.Pipe(yaml.Lookup(scalarFieldPath...)) + if err != nil { + return fmt.Errorf("%w", err) + } + + // Parse the structured data + scalarValue := scalarField.YNode().Value + var parsedNode yaml.Node + if err := yaml.Unmarshal([]byte(scalarValue), &parsedNode); err != nil { + return fmt.Errorf("%w", err) + } + + structuredData := yaml.NewRNode(&parsedNode) + + // Navigate to the target location within the structured data + targetInStructured, err := structuredData.Pipe(&yaml.PathMatcher{ + Path: structuredDataPath, + Create: createKind, + }) + if err != nil { + return fmt.Errorf("%w", err) + } + + targetFields, err := targetInStructured.Elements() + if err != nil { + return fmt.Errorf("%w", err) + } + + if len(targetFields) == 0 { + return fmt.Errorf("unable to find field in structured data") + } + + // Set the value in the structured data + for _, t := range targetFields { + if t.YNode().Kind == yaml.ScalarNode { + t.YNode().Value = value.YNode().Value + } else { + t.SetYNode(value.YNode()) + } + } + + // Serialize the modified structured data back to the scalar field + // Try to detect if original was JSON or YAML and preserve formatting + serializedData, err := serializeStructuredData(structuredData, scalarValue) + if err != nil { + return fmt.Errorf("%w", err) + } + + // Update the original scalar field + scalarField.YNode().Value = serializedData + + return nil +} + +// serializeStructuredData handles the serialization of structured data back to string format +// preserving the original format (JSON vs YAML) and style (pretty vs compact) +func serializeStructuredData(structuredData *yaml.RNode, originalValue string) (string, error) { + firstChar := rune(strings.TrimSpace(originalValue)[0]) + if firstChar == '{' || firstChar == '[' { + return serializeAsJSON(structuredData, originalValue) + } + + // Fallback to YAML format + return serializeAsYAML(structuredData) +} + +// serializeAsJSON converts structured data back to JSON format +func serializeAsJSON(structuredData *yaml.RNode, originalValue string) (string, error) { + modifiedData, err := structuredData.String() + if err != nil { + return "", fmt.Errorf("failed to serialize structured data: %w", err) + } + + // Parse the YAML output as JSON + var jsonData interface{} + if err := yaml.Unmarshal([]byte(modifiedData), &jsonData); err != nil { + return "", fmt.Errorf("failed to unmarshal YAML data: %w", err) + } + + // Check if original was pretty-printed by looking for newlines and indentation + if strings.Contains(originalValue, "\n") && strings.Contains(originalValue, " ") { + // Pretty-print the JSON to match original formatting + if prettyJSON, err := json.MarshalIndent(jsonData, "", " "); err == nil { + return string(prettyJSON), nil + } + } + + // Compact JSON + if compactJSON, err := json.Marshal(jsonData); err == nil { + return string(compactJSON), nil + } + + return "", fmt.Errorf("failed to marshal JSON data") +} + +// serializeAsYAML converts structured data back to YAML format +func serializeAsYAML(structuredData *yaml.RNode) (string, error) { + modifiedData, err := structuredData.String() + if err != nil { + return "", fmt.Errorf("failed to serialize YAML data: %w", err) + } + + return strings.TrimSpace(modifiedData), nil +} diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/AnnotationsTransformer.go b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/AnnotationsTransformer.go index 0910c472b8f..d3b3fc8c202 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/AnnotationsTransformer.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/AnnotationsTransformer.go @@ -1,6 +1,4 @@ // Code generated by pluginator on AnnotationsTransformer; DO NOT EDIT. -// pluginator {(devel) unknown } - package builtins import ( diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ConfigMapGenerator.go b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ConfigMapGenerator.go index dc18bce858a..180a6c2c11d 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ConfigMapGenerator.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ConfigMapGenerator.go @@ -1,6 +1,4 @@ // Code generated by pluginator on ConfigMapGenerator; DO NOT EDIT. -// pluginator {(devel) unknown } - package builtins import ( diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/HashTransformer.go b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/HashTransformer.go index ec23312651a..08dd37d0633 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/HashTransformer.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/HashTransformer.go @@ -1,6 +1,4 @@ // Code generated by pluginator on HashTransformer; DO NOT EDIT. -// pluginator {(devel) unknown } - package builtins import ( diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/HelmChartInflationGenerator.go b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/HelmChartInflationGenerator.go index 5811e73eca8..aed01081a20 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/HelmChartInflationGenerator.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/HelmChartInflationGenerator.go @@ -1,6 +1,4 @@ // Code generated by pluginator on HelmChartInflationGenerator; DO NOT EDIT. -// pluginator {(devel) unknown } - package builtins import ( @@ -13,6 +11,7 @@ import ( "slices" "strings" + "sigs.k8s.io/kustomize/api/konfig" "sigs.k8s.io/kustomize/api/resmap" "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/kyaml/errors" @@ -178,12 +177,11 @@ func (p *HelmChartInflationGeneratorPlugin) runHelmCommand( } if err != nil { helm := p.h.GeneralConfig().HelmConfig.Command - //nolint:govet err = errors.WrapPrefixf( fmt.Errorf( "unable to run: '%s %s' with env=%s (is '%s' installed?): %w", helm, strings.Join(args, " "), env, helm, err), - errorOutput, + "%s", errorOutput, ) } return stdout.Bytes(), err @@ -297,6 +295,9 @@ func (p *HelmChartInflationGeneratorPlugin) Generate() (rm resmap.ResMap, err er rm, resMapErr := p.h.ResmapFactory().NewResMapFromBytes(stdout) if resMapErr == nil { + if err := p.markHelmGeneratedResources(rm); err != nil { + return nil, err + } return rm, nil } // try to remove the contents before first "---" because @@ -312,6 +313,9 @@ func (p *HelmChartInflationGeneratorPlugin) Generate() (rm resmap.ResMap, err er if err != nil { return nil, fmt.Errorf("could not parse rnode slice into resource map: %w", err) } + if err := p.markHelmGeneratedResources(rm); err != nil { + return nil, err + } return rm, nil } return nil, fmt.Errorf("could not parse bytes into resource map: %w", resMapErr) @@ -354,9 +358,18 @@ func (p *HelmChartInflationGeneratorPlugin) chartExistsLocally() (string, bool) return path, s.IsDir() } -// checkHelmVersion will return an error if the helm version is not V3 +func (p *HelmChartInflationGeneratorPlugin) markHelmGeneratedResources(rm resmap.ResMap) error { + for _, r := range rm.Resources() { + if err := r.RNode.PipeE(kyaml.SetAnnotation(konfig.HelmGeneratedAnnotation, "true")); err != nil { + return fmt.Errorf("failed to set helm annotation: %w", err) + } + } + return nil +} + +// checkHelmVersion will return an error if the helm version is not V3 or V4 func (p *HelmChartInflationGeneratorPlugin) checkHelmVersion() error { - stdout, err := p.runHelmCommand([]string{"version", "-c", "--short"}) + stdout, err := p.runHelmCommand([]string{"version", "--short"}) if err != nil { return err } @@ -372,8 +385,8 @@ func (p *HelmChartInflationGeneratorPlugin) checkHelmVersion() error { v = v[1:] } majorVersion := strings.Split(v, ".")[0] - if majorVersion != "3" { - return fmt.Errorf("this plugin requires helm V3 but got v%s", v) + if majorVersion != "3" && majorVersion != "4" { + return fmt.Errorf("this plugin requires helm V3 or V4 but got v%s", v) } return nil } diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/IAMPolicyGenerator.go b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/IAMPolicyGenerator.go index cfb1fa81b6f..314e60c7c4f 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/IAMPolicyGenerator.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/IAMPolicyGenerator.go @@ -1,6 +1,4 @@ // Code generated by pluginator on IAMPolicyGenerator; DO NOT EDIT. -// pluginator {(devel) unknown } - package builtins import ( diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ImageTagTransformer.go b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ImageTagTransformer.go index ffde73a7a13..c5284cb70d7 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ImageTagTransformer.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ImageTagTransformer.go @@ -1,6 +1,4 @@ // Code generated by pluginator on ImageTagTransformer; DO NOT EDIT. -// pluginator {(devel) unknown } - package builtins import ( diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/LabelTransformer.go b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/LabelTransformer.go index c45731b54bf..8ebbd19f788 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/LabelTransformer.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/LabelTransformer.go @@ -1,6 +1,4 @@ // Code generated by pluginator on LabelTransformer; DO NOT EDIT. -// pluginator {(devel) unknown } - package builtins import ( diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/NamespaceTransformer.go b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/NamespaceTransformer.go index d839fb97518..3b98195b3f2 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/NamespaceTransformer.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/NamespaceTransformer.go @@ -1,12 +1,11 @@ // Code generated by pluginator on NamespaceTransformer; DO NOT EDIT. -// pluginator {(devel) unknown } - package builtins import ( "fmt" "sigs.k8s.io/kustomize/api/filters/namespace" + "sigs.k8s.io/kustomize/api/konfig" "sigs.k8s.io/kustomize/api/resmap" "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/kyaml/errors" @@ -53,6 +52,10 @@ func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error { // Don't mutate empty objects? continue } + if annotations := r.GetAnnotations(konfig.HelmGeneratedAnnotation); annotations[konfig.HelmGeneratedAnnotation] == "true" { + // Don't apply namespace on Helm generated manifest. Helm should take care of it. + continue + } r.StorePreviousId() if err := r.ApplyFilter(namespace.Filter{ Namespace: p.Namespace, diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/PatchJson6902Transformer.go b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/PatchJson6902Transformer.go index 04625e5109b..348863263fb 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/PatchJson6902Transformer.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/PatchJson6902Transformer.go @@ -1,6 +1,4 @@ // Code generated by pluginator on PatchJson6902Transformer; DO NOT EDIT. -// pluginator {(devel) unknown } - package builtins import ( diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/PatchStrategicMergeTransformer.go b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/PatchStrategicMergeTransformer.go index d68f2425ea8..29f8f2b9d7d 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/PatchStrategicMergeTransformer.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/PatchStrategicMergeTransformer.go @@ -1,6 +1,4 @@ // Code generated by pluginator on PatchStrategicMergeTransformer; DO NOT EDIT. -// pluginator {(devel) unknown } - package builtins import ( diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/PatchTransformer.go b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/PatchTransformer.go index 05d96f23ca2..442291d11c9 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/PatchTransformer.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/PatchTransformer.go @@ -1,6 +1,4 @@ // Code generated by pluginator on PatchTransformer; DO NOT EDIT. -// pluginator {(devel) unknown } - package builtins import ( @@ -24,10 +22,10 @@ type PatchTransformerPlugin struct { patchText string // patchSource is patch source message patchSource string - Path string `json:"path,omitempty" yaml:"path,omitempty"` - Patch string `json:"patch,omitempty" yaml:"patch,omitempty"` - Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"` - Options map[string]bool `json:"options,omitempty" yaml:"options,omitempty"` + Path string `json:"path,omitempty" yaml:"path,omitempty"` + Patch string `json:"patch,omitempty" yaml:"patch,omitempty"` + Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"` + Options *types.PatchArgs `json:"options,omitempty" yaml:"options,omitempty"` } func (p *PatchTransformerPlugin) Config(h *resmap.PluginHelpers, c []byte) error { @@ -70,10 +68,14 @@ func (p *PatchTransformerPlugin) Config(h *resmap.PluginHelpers, c []byte) error if errSM == nil { p.smPatches = patchesSM for _, loadedPatch := range p.smPatches { - if p.Options["allowNameChange"] { + if p.Options == nil { + continue + } + + if p.Options.AllowNameChange { loadedPatch.AllowNameChange() } - if p.Options["allowKindChange"] { + if p.Options.AllowKindChange { loadedPatch.AllowKindChange() } } @@ -87,7 +89,10 @@ func (p *PatchTransformerPlugin) Transform(m resmap.ResMap) error { if p.smPatches != nil { return p.transformStrategicMerge(m) } - return p.transformJson6902(m) + if p.jsonPatches != nil { + return p.transformJson6902(m) + } + return nil } // transformStrategicMerge applies each loaded strategic merge patch diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/PrefixTransformer.go b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/PrefixTransformer.go index 33700bb4b18..fc26de228f4 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/PrefixTransformer.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/PrefixTransformer.go @@ -1,6 +1,4 @@ // Code generated by pluginator on PrefixTransformer; DO NOT EDIT. -// pluginator {(devel) unknown } - package builtins import ( diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ReplacementTransformer.go b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ReplacementTransformer.go index ef0c9321282..795af7b87bc 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ReplacementTransformer.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ReplacementTransformer.go @@ -1,6 +1,4 @@ // Code generated by pluginator on ReplacementTransformer; DO NOT EDIT. -// pluginator {(devel) unknown } - package builtins import ( diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ReplicaCountTransformer.go b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ReplicaCountTransformer.go index c87d64251c0..afbd4b105c7 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ReplicaCountTransformer.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ReplicaCountTransformer.go @@ -1,6 +1,4 @@ // Code generated by pluginator on ReplicaCountTransformer; DO NOT EDIT. -// pluginator {(devel) unknown } - package builtins import ( diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/SecretGenerator.go b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/SecretGenerator.go index 2a4ef1c903b..fbb4efa402c 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/SecretGenerator.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/SecretGenerator.go @@ -1,6 +1,4 @@ // Code generated by pluginator on SecretGenerator; DO NOT EDIT. -// pluginator {(devel) unknown } - package builtins import ( diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/SortOrderTransformer.go b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/SortOrderTransformer.go index 90e290719e6..8b88b129f90 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/SortOrderTransformer.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/SortOrderTransformer.go @@ -1,6 +1,4 @@ // Code generated by pluginator on SortOrderTransformer; DO NOT EDIT. -// pluginator {(devel) unknown } - package builtins import ( diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/SuffixTransformer.go b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/SuffixTransformer.go index 31b5b8fea02..2999711ab62 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/SuffixTransformer.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/SuffixTransformer.go @@ -1,6 +1,4 @@ // Code generated by pluginator on SuffixTransformer; DO NOT EDIT. -// pluginator {(devel) unknown } - package builtins import ( diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ValueAddTransformer.go b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ValueAddTransformer.go index 1d70c98c2c8..2bab5b6b667 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ValueAddTransformer.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/builtins/ValueAddTransformer.go @@ -1,6 +1,4 @@ // Code generated by pluginator on ValueAddTransformer; DO NOT EDIT. -// pluginator {(devel) unknown } - package builtins import ( diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/loader/fileloader.go b/vendor/sigs.k8s.io/kustomize/api/internal/loader/fileloader.go index 69b8295eb7a..e4202815c99 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/loader/fileloader.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/loader/fileloader.go @@ -169,7 +169,7 @@ func (fl *FileLoader) New(path string) (ifc.Loader, error) { } root, err := filesys.ConfirmDir(fl.fSys, fl.root.Join(path)) if err != nil { - return nil, errors.WrapPrefixf(err, ErrRtNotDir.Error()) //nolint:govet + return nil, errors.WrapPrefixf(err, "%s", ErrRtNotDir.Error()) } if err = fl.errIfGitContainmentViolation(root); err != nil { return nil, err @@ -311,7 +311,11 @@ func (fl *FileLoader) httpClientGetContent(path string) ([]byte, error) { } else { hc = &http.Client{} } - resp, err := hc.Get(path) + parsedURL, err := url.ParseRequestURI(path) + if err != nil { + return nil, errors.Wrap(err) + } + resp, err := hc.Get(parsedURL.String()) if err != nil { return nil, errors.Wrap(err) } diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/loader/loader.go b/vendor/sigs.k8s.io/kustomize/api/internal/loader/loader.go index 60b254fa7e7..36b246fb4e2 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/loader/loader.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/loader/loader.go @@ -28,7 +28,7 @@ func NewLoader( } root, err := filesys.ConfirmDir(fSys, target) if err != nil { - return nil, errors.WrapPrefixf(err, ErrRtNotDir.Error()) //nolint:govet + return nil, errors.WrapPrefixf(err, "%s", ErrRtNotDir.Error()) } return newLoaderAtConfirmedDir( lr, root, fSys, nil, git.ClonerUsingGitExec), nil diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/plugins/execplugin/execplugin.go b/vendor/sigs.k8s.io/kustomize/api/internal/plugins/execplugin/execplugin.go index f6c1dba3ce6..f6cc0b53f5e 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/plugins/execplugin/execplugin.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/plugins/execplugin/execplugin.go @@ -161,14 +161,13 @@ func (p *ExecPlugin) invokePlugin(input []byte) ([]byte, error) { _, err = f.Write(p.cfg) if err != nil { return nil, errors.WrapPrefixf( - err, "writing plugin config to "+f.Name()) + err, "writing plugin config to %s", f.Name()) } err = f.Close() if err != nil { return nil, errors.WrapPrefixf( - err, "closing plugin config file "+f.Name()) + err, "closing plugin config file %s", f.Name()) } - //nolint:gosec cmd := exec.Command( p.path, append([]string{f.Name()}, p.args...)...) cmd.Env = p.getEnv() @@ -180,10 +179,9 @@ func (p *ExecPlugin) invokePlugin(input []byte) ([]byte, error) { } result, err := cmd.Output() if err != nil { - //nolint:govet return nil, errors.WrapPrefixf( - fmt.Errorf("failure in plugin configured via %s; %w", - f.Name(), err), stdErr.String()) + fmt.Errorf("failure in plugin configured via %s; %w", f.Name(), err), + "%s", stdErr.String()) } return result, os.Remove(f.Name()) } diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/plugins/loader/loader.go b/vendor/sigs.k8s.io/kustomize/api/internal/plugins/loader/loader.go index 2edf8791ff1..afae8940dfb 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/plugins/loader/loader.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/plugins/loader/loader.go @@ -251,7 +251,7 @@ func (l *Loader) loadPlugin(res *resource.Resource) (resmap.Configurable, error) return nil, errors.Errorf("plugin %s with mount path '%s' is not permitted; "+ "mount paths must be relative to the current kustomization directory", res.OrgId(), mount.Src) } - if strings.HasPrefix(filepath.Clean(mount.Src), "../") { + if strings.HasPrefix(filepath.Clean(mount.Src), "..") { return nil, errors.Errorf("plugin %s with mount path '%s' is not permitted; "+ "mount paths must be under the current kustomization directory", res.OrgId(), mount.Src) } diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/target/kusttarget.go b/vendor/sigs.k8s.io/kustomize/api/internal/target/kusttarget.go index 5f1d1095a17..eeb23bf70c2 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/target/kusttarget.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/target/kusttarget.go @@ -29,13 +29,14 @@ import ( // KustTarget encapsulates the entirety of a kustomization build. type KustTarget struct { - kustomization *types.Kustomization - kustFileName string - ldr ifc.Loader - validator ifc.Validator - rFactory *resmap.Factory - pLdr *loader.Loader - origin *resource.Origin + kustomization *types.Kustomization + kustFileName string + ldr ifc.Loader + validator ifc.Validator + rFactory *resmap.Factory + pLdr *loader.Loader + origin *resource.Origin + helmRootNamespace string // namespace inherited from parent kustomization for HelmCharts } // NewKustTarget returns a new instance of KustTarget. @@ -496,6 +497,16 @@ func (kt *KustTarget) accumulateDirectory( } subKt.kustomization.BuildMetadata = kt.kustomization.BuildMetadata subKt.origin = kt.origin + // Propagate namespace to child kustomization's helmRootNamespace for HelmCharts + // This ensures Helm charts in base kustomizations inherit namespace from overlays + // without affecting other transformers like patches + // Fixes https://github.com/kubernetes-sigs/kustomize/issues/6031 + // Fixes https://github.com/kubernetes-sigs/kustomize/issues/6027 + if kt.kustomization.Namespace != "" { + subKt.helmRootNamespace = kt.kustomization.Namespace + } else if kt.helmRootNamespace != "" { + subKt.helmRootNamespace = kt.helmRootNamespace + } var bytes []byte if openApiPath, exists := subKt.Kustomization().OpenAPI["path"]; exists { bytes, err = ldr.Load(openApiPath) diff --git a/vendor/sigs.k8s.io/kustomize/api/internal/target/kusttarget_configplugin.go b/vendor/sigs.k8s.io/kustomize/api/internal/target/kusttarget_configplugin.go index 1ba028a36fe..aa57e1295ca 100644 --- a/vendor/sigs.k8s.io/kustomize/api/internal/target/kusttarget_configplugin.go +++ b/vendor/sigs.k8s.io/kustomize/api/internal/target/kusttarget_configplugin.go @@ -166,6 +166,16 @@ var generatorConfigurators = map[builtinhelpers.BuiltinPluginType]func( for _, chart := range kt.kustomization.HelmCharts { c.HelmGlobals = globals c.HelmChart = chart + // Pass kustomize namespace to helm + // Fixes https://github.com/kubernetes-sigs/kustomize/issues/5566 + // Also propagate parent namespace for multi-level kustomization hierarchies + if c.HelmChart.Namespace == "" { + if kt.kustomization.Namespace != "" { + c.HelmChart.Namespace = kt.kustomization.Namespace + } else if kt.helmRootNamespace != "" { + c.HelmChart.Namespace = kt.helmRootNamespace + } + } p := f() if err = kt.configureBuiltinPlugin(p, c, bpt); err != nil { return nil, err @@ -250,10 +260,10 @@ var transformerConfigurators = map[builtinhelpers.BuiltinPluginType]func( return } var c struct { - Path string `json:"path,omitempty" yaml:"path,omitempty"` - Patch string `json:"patch,omitempty" yaml:"patch,omitempty"` - Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"` - Options map[string]bool `json:"options,omitempty" yaml:"options,omitempty"` + Path string `json:"path,omitempty" yaml:"path,omitempty"` + Patch string `json:"patch,omitempty" yaml:"patch,omitempty"` + Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"` + Options *types.PatchArgs `json:"options,omitempty" yaml:"options,omitempty"` } for _, pc := range kt.kustomization.Patches { c.Target = pc.Target diff --git a/vendor/sigs.k8s.io/kustomize/api/konfig/general.go b/vendor/sigs.k8s.io/kustomize/api/konfig/general.go index 712bfe7894f..c6618838832 100644 --- a/vendor/sigs.k8s.io/kustomize/api/konfig/general.go +++ b/vendor/sigs.k8s.io/kustomize/api/konfig/general.go @@ -46,4 +46,7 @@ const ( // Label key that indicates the resources are validated by a validator ValidatedByLabelKey = "validated-by" + + // Annotation key for marking helm-generated resources to skip namespace transformation + HelmGeneratedAnnotation = ConfigAnnoDomain + "/helm-generated" ) diff --git a/vendor/sigs.k8s.io/kustomize/api/resmap/reswrangler.go b/vendor/sigs.k8s.io/kustomize/api/resmap/reswrangler.go index f6443539f1b..33578a0bbe5 100644 --- a/vendor/sigs.k8s.io/kustomize/api/resmap/reswrangler.go +++ b/vendor/sigs.k8s.io/kustomize/api/resmap/reswrangler.go @@ -597,7 +597,7 @@ func (m *resWrangler) appendReplaceOrMerge(res *resource.Resource) error { default: return fmt.Errorf( - "id %#v exists; behavior must be merge or replace", id) + "id %#v exists; can not use behavior: '%s', behavior must be merge or replace", id, res.Behavior()) } i, err := m.Replace(res) if err != nil { diff --git a/vendor/sigs.k8s.io/kustomize/api/resource/resource.go b/vendor/sigs.k8s.io/kustomize/api/resource/resource.go index 9884a672c56..dc6995799b0 100644 --- a/vendor/sigs.k8s.io/kustomize/api/resource/resource.go +++ b/vendor/sigs.k8s.io/kustomize/api/resource/resource.go @@ -11,6 +11,7 @@ import ( "sigs.k8s.io/kustomize/api/filters/patchstrategicmerge" "sigs.k8s.io/kustomize/api/ifc" "sigs.k8s.io/kustomize/api/internal/utils" + "sigs.k8s.io/kustomize/api/konfig" "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/kyaml/kio" "sigs.k8s.io/kustomize/kyaml/kio/kioutil" @@ -47,6 +48,8 @@ var BuildAnnotations = []string{ kioutil.LegacyPathAnnotation, kioutil.LegacyIndexAnnotation, kioutil.LegacyIdAnnotation, + + konfig.HelmGeneratedAnnotation, } func (r *Resource) ResetRNode(incoming *Resource) { diff --git a/vendor/sigs.k8s.io/kustomize/api/types/patch.go b/vendor/sigs.k8s.io/kustomize/api/types/patch.go index 5310a6e66db..7ad141975c6 100644 --- a/vendor/sigs.k8s.io/kustomize/api/types/patch.go +++ b/vendor/sigs.k8s.io/kustomize/api/types/patch.go @@ -3,8 +3,6 @@ package types -import "reflect" - // Patch represent either a Strategic Merge Patch or a JSON patch // and its targets. // The content of the patch can either be from a file @@ -20,15 +18,17 @@ type Patch struct { Target *Selector `json:"target,omitempty" yaml:"target,omitempty"` // Options is a list of options for the patch - Options map[string]bool `json:"options,omitempty" yaml:"options,omitempty"` + Options *PatchArgs `json:"options,omitempty" yaml:"options,omitempty"` } // Equals return true if p equals o. func (p *Patch) Equals(o Patch) bool { targetEqual := (p.Target == o.Target) || (p.Target != nil && o.Target != nil && *p.Target == *o.Target) + optionsEqual := (p.Options == o.Options) || + (p.Options != nil && o.Options != nil && *p.Options == *o.Options) return p.Path == o.Path && p.Patch == o.Patch && targetEqual && - reflect.DeepEqual(p.Options, o.Options) + optionsEqual } diff --git a/vendor/sigs.k8s.io/kustomize/api/types/patchargs.go b/vendor/sigs.k8s.io/kustomize/api/types/patchargs.go new file mode 100644 index 00000000000..453849fa6b1 --- /dev/null +++ b/vendor/sigs.k8s.io/kustomize/api/types/patchargs.go @@ -0,0 +1,13 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package types + +// PatchArgs represent set of options on resources of a patch. +type PatchArgs struct { + // AllowNameChange allows name changes to the resource. + AllowNameChange bool `json:"allowNameChange,omitempty" yaml:"allowNameChange,omitempty"` + + // AllowKindChange allows kind changes to the resource. + AllowKindChange bool `json:"allowKindChange,omitempty" yaml:"allowKindChange,omitempty"` +} diff --git a/vendor/sigs.k8s.io/kustomize/api/types/replacement.go b/vendor/sigs.k8s.io/kustomize/api/types/replacement.go index b110322e78f..dd8ed5dc4c4 100644 --- a/vendor/sigs.k8s.io/kustomize/api/types/replacement.go +++ b/vendor/sigs.k8s.io/kustomize/api/types/replacement.go @@ -66,6 +66,53 @@ type TargetSelector struct { Options *FieldOptions `json:"options,omitempty" yaml:"options,omitempty"` } +type TargetSelectorRegex struct { + targetSelector *TargetSelector + selectRegex *SelectorRegex + rejectRegex []*SelectorRegex +} + +func NewTargetSelectorRegex(ts *TargetSelector) (*TargetSelectorRegex, error) { + tsr := new(TargetSelectorRegex) + tsr.targetSelector = ts + var err error + + tsr.selectRegex, err = NewSelectorRegex(ts.Select) + if err != nil { + return nil, err + } + + rej := []*SelectorRegex{} + for _, r := range ts.Reject { + rr, err := NewSelectorRegex(r) + if err != nil { + return nil, err + } + rej = append(rej, rr) + } + tsr.rejectRegex = rej + + return tsr, nil +} + +func (tsr *TargetSelectorRegex) Selects(id resid.ResId) bool { + return tsr.selectRegex.MatchGvk(id.Gvk) && tsr.selectRegex.MatchName(id.Name) && tsr.selectRegex.MatchNamespace(id.Namespace) +} + +func (tsr *TargetSelectorRegex) RejectsAny(ids []resid.ResId) bool { + for _, r := range tsr.rejectRegex { + if r.selector.ResId.IsEmpty() { + continue + } + for _, id := range ids { + if r.MatchGvk(id.Gvk) && r.MatchName(id.Name) && r.MatchNamespace(id.Namespace) { + return true + } + } + } + return false +} + // FieldOptions refine the interpretation of FieldPaths. type FieldOptions struct { // Used to split/join the field. diff --git a/vendor/sigs.k8s.io/kustomize/kyaml/filesys/filesystem.go b/vendor/sigs.k8s.io/kustomize/kyaml/filesys/filesystem.go index c29d5ad8a7d..4e1c3d89660 100644 --- a/vendor/sigs.k8s.io/kustomize/kyaml/filesys/filesystem.go +++ b/vendor/sigs.k8s.io/kustomize/kyaml/filesys/filesystem.go @@ -4,7 +4,6 @@ package filesys import ( - "fmt" "path/filepath" "sigs.k8s.io/kustomize/kyaml/errors" @@ -78,8 +77,7 @@ func ConfirmDir(fSys FileSystem, path string) (ConfirmedDir, error) { return "", errors.WrapPrefixf(err, "not a valid directory") } if f != "" { - //nolint:govet - return "", errors.WrapPrefixf(errors.Errorf("file is not directory"), fmt.Sprintf("'%s'", path)) + return "", errors.WrapPrefixf(errors.Errorf("file is not directory"), "'%s'", path) } return d, nil } diff --git a/vendor/sigs.k8s.io/kustomize/kyaml/fn/runtime/exec/exec.go b/vendor/sigs.k8s.io/kustomize/kyaml/fn/runtime/exec/exec.go index 562dab30f79..7425d183e5f 100644 --- a/vendor/sigs.k8s.io/kustomize/kyaml/fn/runtime/exec/exec.go +++ b/vendor/sigs.k8s.io/kustomize/kyaml/fn/runtime/exec/exec.go @@ -37,7 +37,7 @@ func (c *Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) { } func (c *Filter) Run(reader io.Reader, writer io.Writer) error { - cmd := exec.Command(c.Path, c.Args...) //nolint:gosec + cmd := exec.Command(c.Path, c.Args...) cmd.Env = append(os.Environ(), c.Env...) cmd.Stdin = reader cmd.Stdout = writer diff --git a/vendor/sigs.k8s.io/kustomize/kyaml/kio/kioutil/kioutil.go b/vendor/sigs.k8s.io/kustomize/kyaml/kio/kioutil/kioutil.go index 510ecae18f8..818d6ca0065 100644 --- a/vendor/sigs.k8s.io/kustomize/kyaml/kio/kioutil/kioutil.go +++ b/vendor/sigs.k8s.io/kustomize/kyaml/kio/kioutil/kioutil.go @@ -387,7 +387,7 @@ func ConfirmInternalAnnotationUnchanged(r1 *yaml.RNode, r2 *yaml.RNode, exclusio for _, key := range keys { errorString = errorString + key + ", " } - return errors.Errorf(errorString[0 : len(errorString)-2]) + return errors.Errorf("%s", errorString[0:len(errorString)-2]) } return nil diff --git a/vendor/sigs.k8s.io/kustomize/kyaml/kio/pkgio_reader.go b/vendor/sigs.k8s.io/kustomize/kyaml/kio/pkgio_reader.go index 609a791f390..d7eeda7932c 100644 --- a/vendor/sigs.k8s.io/kustomize/kyaml/kio/pkgio_reader.go +++ b/vendor/sigs.k8s.io/kustomize/kyaml/kio/pkgio_reader.go @@ -268,7 +268,7 @@ func (r LocalPackageReader) Read() ([]*yaml.RNode, error) { // to another location. relPath, err := filepath.Rel(pathRelativeTo, path) if err != nil { - return errors.WrapPrefixf(err, pathRelativeTo) + return errors.WrapPrefixf(err, "%s", pathRelativeTo) } if match, err := r.shouldSkipFile(path, relPath, ignoreFilesMatcher); err != nil { return err @@ -280,7 +280,7 @@ func (r LocalPackageReader) Read() ([]*yaml.RNode, error) { r.initReaderAnnotations(relPath, info) nodes, err := r.readFile(path, info) if err != nil { - return errors.WrapPrefixf(err, path) + return errors.WrapPrefixf(err, "%s", path) } operand = append(operand, nodes...) return nil diff --git a/vendor/sigs.k8s.io/kustomize/kyaml/runfn/runfn.go b/vendor/sigs.k8s.io/kustomize/kyaml/runfn/runfn.go index bf8863c5727..4fd175cd888 100644 --- a/vendor/sigs.k8s.io/kustomize/kyaml/runfn/runfn.go +++ b/vendor/sigs.k8s.io/kustomize/kyaml/runfn/runfn.go @@ -230,7 +230,7 @@ func (r RunFns) runFunctions( } } if len(errs) > 0 { - return fmt.Errorf(strings.Join(errs, "\n---\n")) + return fmt.Errorf("%s", strings.Join(errs, "\n---\n")) } return nil } diff --git a/vendor/sigs.k8s.io/kustomize/kyaml/yaml/fns.go b/vendor/sigs.k8s.io/kustomize/kyaml/yaml/fns.go index 740a28ed021..9c26055ed7c 100644 --- a/vendor/sigs.k8s.io/kustomize/kyaml/yaml/fns.go +++ b/vendor/sigs.k8s.io/kustomize/kyaml/yaml/fns.go @@ -830,6 +830,10 @@ func (e *InvalidNodeKindError) Error() string { return msg } +func (e *InvalidNodeKindError) Unwrap() error { + return errors.Errorf("InvalidNodeKindError") +} + func (e *InvalidNodeKindError) ActualNodeKind() Kind { return e.node.YNode().Kind } diff --git a/vendor/sigs.k8s.io/kustomize/kyaml/yaml/match.go b/vendor/sigs.k8s.io/kustomize/kyaml/yaml/match.go index 28ea03ca6f8..1e70de3c948 100644 --- a/vendor/sigs.k8s.io/kustomize/kyaml/yaml/match.go +++ b/vendor/sigs.k8s.io/kustomize/kyaml/yaml/match.go @@ -14,7 +14,7 @@ import ( ) // PathMatcher returns all RNodes matching the path wrapped in a SequenceNode. -// Lists may have multiple elements matching the path, and each matching element +// Lists may have multiple elements matching the pafunc cleanPath(path []string) []string {g element // is added to the return result. // If Path points to a SequenceNode, the SequenceNode is wrapped in another SequenceNode // If Path does not contain any lists, the result is still wrapped in a SequenceNode of len == 1 @@ -137,10 +137,14 @@ func (p *PathMatcher) visitEveryElem(elem *RNode) error { func (p *PathMatcher) doField(rn *RNode) (*RNode, error) { // lookup the field field, err := rn.Pipe(Get(p.Path[0])) - if err != nil || (!IsCreate(p.Create) && field == nil) { + if err != nil { return nil, err } + if !IsCreate(p.Create) && field == nil { + return nil, nil + } + if IsCreate(p.Create) && field == nil { var nextPart string if len(p.Path) > 1 { @@ -154,6 +158,11 @@ func (p *PathMatcher) doField(rn *RNode) (*RNode, error) { } } + // Check if the field is a scalar and there are remaining path segments + if field != nil && field.YNode().Kind == yaml.ScalarNode && len(p.Path) > 1 { + return p.handleStructuredDataInScalar(field) + } + // recurse on the field, removing the first element of the path pm := &PathMatcher{Path: p.Path[1:], Create: p.Create} p.val, err = pm.filter(field) @@ -253,12 +262,12 @@ func (p *PathMatcher) doSeq(rn *RNode) (*RNode, error) { func (p *PathMatcher) visitPrimitiveElem(elem *RNode) error { r, err := regexp.Compile(p.matchRegex) if err != nil { - return err + return fmt.Errorf("%w", err) } str, err := elem.String() if err != nil { - return err + return fmt.Errorf("%w", err) } str = strings.TrimSpace(str) if !r.MatchString(str) { @@ -272,7 +281,7 @@ func (p *PathMatcher) visitPrimitiveElem(elem *RNode) error { func (p *PathMatcher) visitElem(elem *RNode) error { r, err := regexp.Compile(p.matchRegex) if err != nil { - return err + return fmt.Errorf("%w", err) } // check if this elements field matches the regex @@ -282,7 +291,7 @@ func (p *PathMatcher) visitElem(elem *RNode) error { } str, err := val.Value.String() if err != nil { - return err + return fmt.Errorf("%w", err) } str = strings.TrimSpace(str) if !r.MatchString(str) { @@ -331,3 +340,26 @@ func cleanPath(path []string) []string { } return p } + +// handleStructuredDataInScalar processes a scalar field that contains structured data (JSON/YAML) +// and allows path navigation within that structured data +func (p *PathMatcher) handleStructuredDataInScalar(scalarField *RNode) (*RNode, error) { + scalarValue := scalarField.YNode().Value + var parsedNode yaml.Node + if err := yaml.Unmarshal([]byte(scalarValue), &parsedNode); err != nil { + return nil, fmt.Errorf("%w", err) + } + + // Create a structured field from the parsed data + structuredField := NewRNode(&parsedNode) + + // Process the remaining path on the structured data + pm := &PathMatcher{Path: p.Path[1:], Create: p.Create} + result, err := pm.filter(structuredField) + if err != nil { + return nil, err + } + p.Matches = pm.Matches + + return result, nil +} diff --git a/vendor/sigs.k8s.io/kustomize/kyaml/yaml/rnode.go b/vendor/sigs.k8s.io/kustomize/kyaml/yaml/rnode.go index 0059ec2eb96..43cad97e5d5 100644 --- a/vendor/sigs.k8s.io/kustomize/kyaml/yaml/rnode.go +++ b/vendor/sigs.k8s.io/kustomize/kyaml/yaml/rnode.go @@ -718,10 +718,11 @@ func (rn *RNode) MustString() string { // Content returns Node Content field. func (rn *RNode) Content() []*yaml.Node { - if rn == nil { + yNode := rn.YNode() + if yNode == nil { return nil } - return rn.YNode().Content + return yNode.Content } // Fields returns the list of field names for a MappingNode. @@ -756,7 +757,11 @@ func (rn *RNode) FieldRNodes() ([]*RNode, error) { // Field returns a fieldName, fieldValue pair for MappingNodes. // Returns nil for non-MappingNodes. func (rn *RNode) Field(field string) *MapNode { - if rn.YNode().Kind != yaml.MappingNode { + yNode := rn.YNode() + if yNode == nil { + return nil + } + if yNode.Kind != yaml.MappingNode { return nil } var result *MapNode @@ -892,7 +897,11 @@ func (rn *RNode) ElementValuesList(keys []string) ([][]string, error) { // Element returns the element in the list which contains the field matching the value. // Returns nil for non-SequenceNodes or if no Element matches. func (rn *RNode) Element(key, value string) *RNode { - if rn.YNode().Kind != yaml.SequenceNode { + yNode := rn.YNode() + if yNode == nil { + return nil + } + if yNode.Kind != yaml.SequenceNode { return nil } elem, err := rn.Pipe(MatchElement(key, value)) @@ -906,7 +915,11 @@ func (rn *RNode) Element(key, value string) *RNode { // corresponding values[i]. // Returns nil for non-SequenceNodes or if no Element matches. func (rn *RNode) ElementList(keys []string, values []string) *RNode { - if rn.YNode().Kind != yaml.SequenceNode { + yNode := rn.YNode() + if yNode == nil { + return nil + } + if yNode.Kind != yaml.SequenceNode { return nil } elem, err := rn.Pipe(MatchElementList(keys, values)) @@ -960,12 +973,17 @@ func (rn *RNode) GetAssociativeKey() string { // MarshalJSON creates a byte slice from the RNode. func (rn *RNode) MarshalJSON() ([]byte, error) { + yNode := rn.YNode() + if yNode == nil { + return []byte("null"), nil + } + s, err := rn.String() if err != nil { return nil, err } - if rn.YNode().Kind == SequenceNode { + if yNode.Kind == SequenceNode { var a []interface{} if err := Unmarshal([]byte(s), &a); err != nil { return nil, err @@ -977,6 +995,7 @@ func (rn *RNode) MarshalJSON() ([]byte, error) { if err := Unmarshal([]byte(s), &m); err != nil { return nil, err } + return json.Marshal(m) }