From 8f97f3d2e6da7a8f0606f374754b5a095c7bab83 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sun, 25 Oct 2026 18:43:33 +0100 Subject: [PATCH] fix: compile on every GOOS/GOARCH Go supports except plan9 A type-check of every package and test for each of the 47 pairs in `go tool dist list` found 31 that did not compile. 44 now do; android and ios compile their library packages (they only link through cgo). - Store gems (SQLite3, Sequel, ActiveRecord's adapter, Bolt, Bleve, Etcd, Confd) are constrained to the targets modernc.org/sqlite v1.60.1 has a port for (its lib/ directory: darwin amd64/arm64, freebsd 4, linux 8, netbsd/amd64, openbsd amd64/arm64, windows). Elsewhere bindings_store_stub.go, generalised from the wasm stubs, keeps `require` working and raises NotImplementedError " is not supported on " on use ("on wasm" stays as it was). classOfExtBinding's store cases moved to classOfStoreBinding so the other bindings keep their classes on those targets. - Errno: EMULTIHOP and ENOLINK join the per-target names (OpenBSD lacks them), ENOTRECOVERABLE and EOWNERDEAD are undefined on NetBSD and DragonFly; MRI's undefined_error treatment, as for js/wasm. - Process::RLIMIT_*: AS, MEMLOCK, NPROC and RSS come from per-OS tables (OpenBSD has no AS, AIX no MEMLOCK, Solaris/illumos only AS), as MRI defines them #ifdef. Setrlimit stores through a generic setter: the Rlimit fields are int64 on FreeBSD and DragonFly. - flock: x/sys/unix instead of syscall (Solaris has no syscall.Flock); on AIX, which has no flock(2), the fcntl(2) emulation of MRI's missing/flock.c. - Test-only servers: mysql_test.go also needs go-mysql-server's sockstate (Linux, macOS, Windows, FreeBSD); nats_bind_internal_test.go needs nats-server (not on AIX, nor FreeBSD 386/arm). CI gains `compile on every target`, one job per GOOS: go vet for each GOARCH, default and rbgo_core builds. plan9 stays out and the README says why: no errno numbers, Kill, Flock, Getrlimit or Stat_t in its syscall package, and four gem drivers that do not build there. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 39 +++++ README.md | 9 + internal/vm/activerecord_adapter.go | 2 +- internal/vm/batch6_internal_test.go | 2 +- internal/vm/bindings_native.go | 64 +------- internal/vm/bindings_store.go | 90 ++++++++++ internal/vm/bindings_store_stub.go | 190 ++++++++++++++++++++++ internal/vm/bindings_wasm.go | 168 +------------------ internal/vm/bleve.go | 2 +- internal/vm/bleve_bind.go | 2 +- internal/vm/bleve_bind_internal_test.go | 2 +- internal/vm/bleve_test.go | 2 +- internal/vm/bolt.go | 2 +- internal/vm/bolt_bind.go | 2 +- internal/vm/confd.go | 2 +- internal/vm/confd_bind.go | 2 +- internal/vm/confd_internal_test.go | 2 +- internal/vm/errno.go | 17 +- internal/vm/errno_bsd_noowner.go | 25 +++ internal/vm/errno_native.go | 11 +- internal/vm/errno_openbsd.go | 24 +++ internal/vm/errno_wasm.go | 11 +- internal/vm/etcd.go | 2 +- internal/vm/etcd_bind.go | 2 +- internal/vm/etcd_bind_internal_test.go | 2 +- internal/vm/filestat_unix.go | 2 +- internal/vm/mysql_test.go | 8 +- internal/vm/nats_bind_internal_test.go | 2 +- internal/vm/pstore_lock_unix.go | 17 +- internal/vm/rlimit_aix.go | 17 ++ internal/vm/rlimit_full.go | 18 ++ internal/vm/rlimit_openbsd.go | 17 ++ internal/vm/rlimit_solaris.go | 16 ++ internal/vm/sequel.go | 2 +- internal/vm/sequel_bind.go | 2 +- internal/vm/sequel_schema.go | 2 +- internal/vm/sequel_test.go | 2 +- internal/vm/spawn_native.go | 41 +++-- internal/vm/sqlite3.go | 2 +- internal/vm/sqlite3_bind.go | 2 +- internal/vm/sqlite3_bind_internal_test.go | 2 +- internal/vm/sqlite3_test.go | 2 +- internal/vm/sysflock_aix.go | 40 +++++ internal/vm/sysflock_unix.go | 14 ++ 44 files changed, 604 insertions(+), 280 deletions(-) create mode 100644 internal/vm/bindings_store.go create mode 100644 internal/vm/bindings_store_stub.go create mode 100644 internal/vm/errno_bsd_noowner.go create mode 100644 internal/vm/errno_openbsd.go create mode 100644 internal/vm/rlimit_aix.go create mode 100644 internal/vm/rlimit_full.go create mode 100644 internal/vm/rlimit_openbsd.go create mode 100644 internal/vm/rlimit_solaris.go create mode 100644 internal/vm/sysflock_aix.go create mode 100644 internal/vm/sysflock_unix.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8025a52c..1faf4b1f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,6 +132,45 @@ jobs: - name: Test (GOARCH=386) run: GOARCH=386 go test -timeout=60m ./... + # Every GOOS/GOARCH pair Go supports must at least compile rbgo, its tests + # and the rbgo_core build: a target that does not compile is one nobody can + # embed rbgo on, and nothing else here would notice (the lanes above run six + # of them). go vet type-checks every package with its tests for the target. + # android and ios link only through cgo, so there the library packages are + # built (no link) instead. plan9 is excluded: its syscall package has no + # errno numbers and no POSIX process or file-lock calls, which rbgo's IO, + # Errno and Process layers are written against (see README "Platforms"). + every-target: + name: compile on every target (${{ matrix.goos }}) + runs-on: ubuntu-latest + env: + CGO_ENABLED: "0" + strategy: + fail-fast: false + matrix: + goos: [aix, android, darwin, dragonfly, freebsd, illumos, ios, js, linux, netbsd, openbsd, solaris, wasip1, windows] + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 + with: + go-version: 1.27.2 + - name: Vet (or build) every GOARCH of ${{ matrix.goos }}, both builds + shell: bash + run: | + fail=0 + for t in $(go tool dist list | grep "^${{ matrix.goos }}/"); do + arch=${t#*/} + for tags in "" rbgo_core; do + case "${{ matrix.goos }}" in + android|ios) cmd="go build -tags=$tags . ./internal/..." ;; + *) cmd="go vet -tags=$tags ./..." ;; + esac + echo "== $t [$tags]: $cmd" + GOOS=${{ matrix.goos }} GOARCH=$arch $cmd || { echo "::error::$t [$tags] does not compile"; fail=1; } + done + done + exit $fail + # WebAssembly (WASI preview 1) gate: the same interpreter compiled for # GOOS=wasip1 GOARCH=wasm and RUN under a real wasm runtime. The database / # search / KV / heavy-network gem backends are compiled out on wasm (their diff --git a/README.md b/README.md index 898a5af6..da72e063 100644 --- a/README.md +++ b/README.md @@ -775,6 +775,15 @@ Otherwise: | **wasip1/wasm** | built with `CGO_ENABLED=0 GOOS=wasip1 GOARCH=wasm` and **run** under [wazero](https://wazero.io), a pure-Go runtime — verified here: `wazero run rbgo.wasm -e 'puts (1..10).sum'` prints `55` | | **js/wasm (browser)** | `CGO_ENABLED=0 GOOS=js GOARCH=wasm go build ./...` plus `go vet` over `./cmd/wasm ./cmd/rbgo ./internal/vm`, then the playground module is **run** in node by `web/smoke.mjs` (`wasm (js, browser)` in `ci.yml`). Added with #684, which fixed the three `syscall` constants the `js` port does not define | | **rbgo_core** | `go vet` and the `internal/vm` suite with `-tags rbgo_core`, then the core playground module is built for js/wasm, its size bounded, and **run** in node by `web/smoke.mjs --core` (`rbgo_core (core build)` in `ci.yml`). See [the `rbgo_core` build](#the-rbgo_core-build-the-language-and-the-numeric-stack-no-gems) | +| **every other target** | `go vet ./...`, default and `rbgo_core` builds, for **every GOOS/GOARCH pair `go tool dist list` names except plan9** — aix, android, darwin, dragonfly, freebsd, illumos, ios, js, linux (all 13 arches, mips and ppc64 included), netbsd, openbsd, solaris, wasip1, windows (`compile on every target` in `ci.yml`; android and ios link only through cgo, so their library packages are built instead). Compiling is all it checks there | + +What a target lacks, rbgo leaves out the way MRI does: + +- **The store gems** — SQLite3, Sequel, ActiveRecord's adapter, Bolt, Bleve, Etcd and Confd — need modernc.org/sqlite, which has a port for darwin/{amd64,arm64}, freebsd/{386,amd64,arm,arm64}, linux/{386,amd64,arm,arm64,loong64,ppc64le,riscv64,s390x}, netbsd/amd64, openbsd/{amd64,arm64} and windows. Elsewhere (wasm included) `require` still works and the modules exist, and the first real operation raises `NotImplementedError: sqlite3 is not supported on linux/mips64le` (or the gem and target in question). +- **Errno**: a name the system's `` lacks is still a constant, naming `Errno::NOERROR`, as MRI's `undefined_error` does: `EMULTIHOP` and `ENOLINK` on OpenBSD, `ENOTRECOVERABLE` and `EOWNERDEAD` on NetBSD and DragonFly. +- **`Process::RLIMIT_*`** exist only where the system has the resource: no `RLIMIT_AS` on OpenBSD, no `RLIMIT_MEMLOCK` on AIX, only `RLIMIT_AS` of the four optional ones on Solaris and illumos. +- **`File#flock`** and PStore's lock use `flock(2)`; AIX has none, so there it is emulated with `fcntl(2)` record locks, exactly as MRI's `missing/flock.c` does. +- **plan9 does not compile.** Its `syscall` package has no errno numbers (`syscall.EINVAL` is a string error there), no `Kill`, `Flock`, `Getrlimit` or `Stat_t`, and several gem drivers (nats, Kafka's franz-go, Thrift, gopher-lua) do not build for it; a port means a plan9 variant of rbgo's Errno, IO and Process layers. **Not on the pull-request path** — validated on a nightly schedule instead: `riscv64`, `loong64`, `ppc64le`, `s390x` and 32-bit `arm`, under QEMU diff --git a/internal/vm/activerecord_adapter.go b/internal/vm/activerecord_adapter.go index bd0e82c6..639788f5 100644 --- a/internal/vm/activerecord_adapter.go +++ b/internal/vm/activerecord_adapter.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm diff --git a/internal/vm/batch6_internal_test.go b/internal/vm/batch6_internal_test.go index 5c4fa4cb..22fa1243 100644 --- a/internal/vm/batch6_internal_test.go +++ b/internal/vm/batch6_internal_test.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm diff --git a/internal/vm/bindings_native.go b/internal/vm/bindings_native.go index a5b6e70f..cd86eb4f 100644 --- a/internal/vm/bindings_native.go +++ b/internal/vm/bindings_native.go @@ -101,68 +101,10 @@ func (vm *VM) classOfExtBinding(v object.Value) (*RClass, bool) { return vm.consts["OpenStack::Image"].(*RClass), true case *OpenStackIdentity: return vm.consts["OpenStack::Identity"].(*RClass), true - case *SQLite3Database: - return vm.consts["SQLite3::Database"].(*RClass), true - case *SQLite3Statement: - return vm.consts["SQLite3::Statement"].(*RClass), true - case *SequelDBObj: - return x.cls, true - case *SequelDatasetObj: - return x.cls, true - case *SequelSchemaObj: - return x.cls, true - case *BoltDB: - return x.cls, true - case *BoltTx: - return x.cls, true - case *BoltBucket: - return x.cls, true - case *BoltCursor: - return x.cls, true - case *BleveIndex: - return x.cls, true - case *BleveMapping: - return x.cls, true - case *BleveQuery: - return x.cls, true - case *BleveSearchResult: - return x.cls, true - case *BleveHit: - return x.cls, true - case *BleveBatch: - return x.cls, true - case *BleveFacet: - return x.cls, true - case *EtcdClient: - return x.cls, true - case *EtcdKeyValue: - return x.cls, true - case *EtcdGetResult: - return x.cls, true - case *EtcdPutResult: - return x.cls, true - case *EtcdDelResult: - return x.cls, true - case *EtcdLease: - return x.cls, true - case *EtcdEvent: - return x.cls, true - case *EtcdTxn: - return x.cls, true - case *EtcdCmp: - return x.cls, true - case *EtcdOp: - return x.cls, true - case *EtcdTxnResult: - return x.cls, true - case *EtcdLock: - return x.cls, true - case *EtcdMember: - return x.cls, true - case *EtcdStatus: - return x.cls, true } - return nil, false + // SQLite3, Sequel, Bolt, Bleve and Etcd are a separate seam: their drivers + // build on fewer targets than the rest (see bindings_store.go). + return vm.classOfStoreBinding(v) } // valueEqualExtBinding is the native half of the value-equality seam for the diff --git a/internal/vm/bindings_store.go b/internal/vm/bindings_store.go new file mode 100644 index 00000000..79eab216 --- /dev/null +++ b/internal/vm/bindings_store.go @@ -0,0 +1,90 @@ +// Copyright (c) the go-embedded-ruby/ruby authors +// +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) + +package vm + +import "github.com/go-embedded-ruby/ruby/internal/object" + +// The store bindings — SQLite3, Sequel and ActiveRecord's adapter (all three over +// modernc.org/sqlite), Bolt, Bleve and Etcd/Confd — build only where +// modernc.org/sqlite has a port: the targets listed in this file's constraint, +// which are exactly the GOOS_GOARCH pairs of modernc.org/sqlite v1.60.1's lib/ +// directory (its pure-Go translation is generated per target, and +// modernc.org/libc under it likewise). Everywhere else — js/wasm and wasip1, +// linux/mips*, linux/ppc64, AIX, Solaris and illumos, DragonFly, NetBSD and +// OpenBSD on their smaller ports — bindings_store_stub.go installs graceful +// modules whose operations raise NotImplementedError. Bolt, Bleve and Etcd +// share the constraint rather than carrying their own: they are one seam, and +// on AIX, the only further target where they would build without SQLite, +// Bleve's mmap-go and Etcd's fileutil do not. + +// classOfStoreBinding maps the store bindings' value types to their Ruby class, +// the tail of classOfExtBinding. ok is false for any other value. +func (vm *VM) classOfStoreBinding(v object.Value) (*RClass, bool) { + switch x := v.(type) { + case *SQLite3Database: + return vm.consts["SQLite3::Database"].(*RClass), true + case *SQLite3Statement: + return vm.consts["SQLite3::Statement"].(*RClass), true + case *SequelDBObj: + return x.cls, true + case *SequelDatasetObj: + return x.cls, true + case *SequelSchemaObj: + return x.cls, true + case *BoltDB: + return x.cls, true + case *BoltTx: + return x.cls, true + case *BoltBucket: + return x.cls, true + case *BoltCursor: + return x.cls, true + case *BleveIndex: + return x.cls, true + case *BleveMapping: + return x.cls, true + case *BleveQuery: + return x.cls, true + case *BleveSearchResult: + return x.cls, true + case *BleveHit: + return x.cls, true + case *BleveBatch: + return x.cls, true + case *BleveFacet: + return x.cls, true + case *EtcdClient: + return x.cls, true + case *EtcdKeyValue: + return x.cls, true + case *EtcdGetResult: + return x.cls, true + case *EtcdPutResult: + return x.cls, true + case *EtcdDelResult: + return x.cls, true + case *EtcdLease: + return x.cls, true + case *EtcdEvent: + return x.cls, true + case *EtcdTxn: + return x.cls, true + case *EtcdCmp: + return x.cls, true + case *EtcdOp: + return x.cls, true + case *EtcdTxnResult: + return x.cls, true + case *EtcdLock: + return x.cls, true + case *EtcdMember: + return x.cls, true + case *EtcdStatus: + return x.cls, true + } + return nil, false +} diff --git a/internal/vm/bindings_store_stub.go b/internal/vm/bindings_store_stub.go new file mode 100644 index 00000000..d093617f --- /dev/null +++ b/internal/vm/bindings_store_stub.go @@ -0,0 +1,190 @@ +// Copyright (c) the go-embedded-ruby/ruby authors +// +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !rbgo_core && !((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) + +// This file is the counterpart of the store bindings on every target their +// drivers do not build for (see bindings_store.go for the list and the reason): +// js/wasm and wasip1, linux/mips*, linux/ppc64, AIX, Solaris, illumos, +// DragonFly, and NetBSD and OpenBSD outside amd64/arm64. The bindings are +// SQLite3 (modernc.org/sqlite), Sequel and ActiveRecord (executor wired to +// SQLite3), Bolt and Bleve (go.etcd.io/bbolt) and Etcd (go.etcd.io/etcd + +// coreos/go-systemd). Their real implementations live in files carrying the +// complementary constraint, so this file supplies the symbols the shared code +// still references. +// +// The chosen degradation strategy is a graceful module, consistently across every +// guarded gem: `require "sqlite3"` (etc.) still succeeds (the feature name is in +// require.go's providedFeatures), the module and its exception class are defined +// so conditional code and `rescue`s load, and only an actual operation +// (SQLite3::Database.new, Sequel.connect, Bolt::DB.open, Bleve.new, Etcd.new, +// Confd.render, ActiveRecord::Base.establish_connection, …) raises a clean +// NotImplementedError " is not supported on " — the same error kind +// the spawn/xstr wasm stubs raise. No behaviour changes where the drivers build: +// these definitions exist only where they do not. + +package vm + +import ( + "runtime" + "strings" + + activerecord "github.com/go-ruby-activerecord/activerecord" + + "github.com/go-embedded-ruby/ruby/internal/object" +) + +// classOfStoreBinding is the stub half of the store bindings' classOf seam: their +// value types can never be constructed here. +func (vm *VM) classOfStoreBinding(v object.Value) (*RClass, bool) { _, _ = vm, v; return nil, false } + +// unsupportedTarget names this target the way the NotImplementedError messages +// do: "wasm" for both wasm ports (the wording the browser build has always +// used), GOOS/GOARCH otherwise. +func unsupportedTarget() string { + if runtime.GOARCH == "wasm" { + return "wasm" + } + return runtime.GOOS + "/" + runtime.GOARCH +} + +// storeUnsupported returns a native method that raises the standard +// " is not supported on " NotImplementedError. +func storeUnsupported(gem string) NativeFn { + return func(_ *VM, _ object.Value, _ []object.Value, _ *Proc) object.Value { + return raise("NotImplementedError", "%s is not supported on %s", gem, unsupportedTarget()) + } +} + +// simpleName returns the last "::"-segment of a qualified constant name. +func simpleName(qualified string) string { + if i := strings.LastIndex(qualified, ":"); i >= 0 { + return qualified[i+1:] + } + return qualified +} + +// stubBindingModule installs a top-level module `name` plus its exception class +// `errQualified` (< StandardError), registered both as a nested constant and +// under its qualified name, mirroring the native register's error tree so a +// `rescue Gem::Error` still resolves. +func (vm *VM) stubBindingModule(name, errQualified string) *RClass { + mod := newClass(name, nil) + mod.isModule = true + vm.consts[name] = mod + std := vm.consts["StandardError"].(*RClass) + ec := newClass(errQualified, std) + mod.consts[simpleName(errQualified)] = ec + vm.consts[errQualified] = ec + return mod +} + +// stubBindingClass installs a nested class `qualified` under mod (as vm.cObject +// subclass), registered both nested and top-level, like the native trees. +func (vm *VM) stubBindingClass(mod *RClass, qualified string) *RClass { + c := newClass(qualified, vm.cObject) + mod.consts[simpleName(qualified)] = c + vm.consts[qualified] = c + return c +} + +// stubSMethods attaches raising singleton methods (class/module methods) named by +// names to c. +func stubSMethods(c *RClass, gem string, names ...string) { + for _, n := range names { + c.smethods[n] = &Method{name: n, owner: c, native: storeUnsupported(gem)} + } +} + +// registerSQLite3 installs a graceful SQLite3 module: SQLite3::Exception and a +// SQLite3::Database whose .new/.open raise. +func (vm *VM) registerSQLite3() { + mod := vm.stubBindingModule("SQLite3", "SQLite3::Exception") + db := vm.stubBindingClass(mod, "SQLite3::Database") + stubSMethods(db, "sqlite3", "new", "open") +} + +// registerSequel installs a graceful Sequel module: Sequel::Error and the +// Sequel.sqlite / .connect / .mock factory methods, all raising. +func (vm *VM) registerSequel() { + mod := vm.stubBindingModule("Sequel", "Sequel::Error") + stubSMethods(mod, "sequel", "sqlite", "connect", "mock") +} + +// registerBolt installs a graceful Bolt module: Bolt::Error and a Bolt::DB whose +// .open/.new raise. +func (vm *VM) registerBolt() { + mod := vm.stubBindingModule("Bolt", "Bolt::Error") + db := vm.stubBindingClass(mod, "Bolt::DB") + stubSMethods(db, "bolt", "open", "new") +} + +// registerBleve installs a graceful Bleve module: Bleve::Error and the +// Bleve.new / .open / .new_mem_index factory methods, all raising. +func (vm *VM) registerBleve() { + mod := vm.stubBindingModule("Bleve", "Bleve::Error") + stubSMethods(mod, "bleve", "new", "open", "new_mem_index") +} + +// registerEtcd installs graceful Etcd and Etcdv3 modules (Etcd::Error) whose +// .new/.connect raise. +func (vm *VM) registerEtcd() { + mod := vm.stubBindingModule("Etcd", "Etcd::Error") + stubSMethods(mod, "etcd", "new", "connect") + alias := newClass("Etcdv3", nil) + alias.isModule = true + vm.consts["Etcdv3"] = alias + stubSMethods(alias, "etcd", "new", "connect") +} + +// registerConfd installs a graceful Confd module: Confd::Error and Confd.render, +// which raises. +func (vm *VM) registerConfd() { + mod := vm.stubBindingModule("Confd", "Confd::Error") + stubSMethods(mod, "confd", "render") +} + +// arSQLiteAdapter is the stub of the ActiveRecord adapter. ActiveRecord itself +// builds everywhere; only its executor is wired to SQLite3, so the adapter seam +// is stubbed here. The type still satisfies activerecord.Adapter (so the shared +// ActiveRecord code that passes it to the core compiles) and every method +// raises — though none is reached, because arConnect/arRequireAdapter raise +// first. +type arSQLiteAdapter struct{} + +func (a *arSQLiteAdapter) Execute(sql string) ([]activerecord.Row, error) { + raiseARUnsupported() + return nil, nil +} + +func (a *arSQLiteAdapter) ExecuteDML(sql string) (affected int64, lastInsertID int64, err error) { + raiseARUnsupported() + return 0, 0, nil +} + +func (a *arSQLiteAdapter) AdapterName() string { return "sqlite3" } + +// raiseARUnsupported raises ActiveRecord's NotImplementedError for this target. +func raiseARUnsupported() object.Value { + return raise("NotImplementedError", "active_record (sqlite3) is not supported on %s", unsupportedTarget()) +} + +// arConnect raises: there is no SQLite3 driver to open a connection. +func (vm *VM) arConnect(path string) { + _ = path + raiseARUnsupported() +} + +// arRequireAdapter raises (a connection can never be established), so any query +// path degrades to a clean error rather than a nil dereference. +func (vm *VM) arRequireAdapter() *arSQLiteAdapter { + raiseARUnsupported() + return nil +} + +// arRawConnection is the stub of ActiveRecord::Base.connection's SQLite3 tail; +// it raises, matching the establish_connection path. +func (vm *VM) arRawConnection() object.Value { + return raiseARUnsupported() +} diff --git a/internal/vm/bindings_wasm.go b/internal/vm/bindings_wasm.go index eabed086..13f37283 100644 --- a/internal/vm/bindings_wasm.go +++ b/internal/vm/bindings_wasm.go @@ -4,36 +4,19 @@ //go:build wasm && !rbgo_core -// This file is the GOOS=js GOARCH=wasm counterpart of the database / search / KV -// bindings whose Go drivers do not compile for wasm — SQLite3 (modernc.org/libc), -// Sequel and ActiveRecord (executor wired to SQLite3), Bolt and Bleve -// (go.etcd.io/bbolt), and Etcd (go.etcd.io/etcd + coreos/go-systemd). Their real -// implementations live in files carried by the //go:build !(js && wasm) tag, so -// this file supplies the symbols the shared code still references on wasm. -// -// The chosen degradation strategy is a graceful module, consistently across every -// guarded gem: `require "sqlite3"` (etc.) still succeeds (the feature name is in -// require.go's providedFeatures), the module and its exception class are defined -// so conditional code and `rescue`s load, and only an actual operation -// (SQLite3::Database.new, Sequel.connect, Bolt::DB.open, Bleve.new, Etcd.new, -// Confd.render, ActiveRecord::Base.establish_connection, …) raises a clean -// NotImplementedError " is not supported on wasm" — the same error kind -// the existing spawn/xstr wasm stubs raise. No native behaviour changes: these -// definitions exist only on wasm. +// This file is the wasm half of the value seams of the bindings that cannot +// function under GOOS=js / GOOS=wasip1 GOARCH=wasm (bindings_native.go is the +// other half). The store bindings (SQLite3, Sequel, ActiveRecord's adapter, +// Bolt, Bleve, Etcd, Confd) are stubbed by bindings_store_stub.go, which serves +// every target their drivers do not build for, wasm included. package vm -import ( - "strings" - - activerecord "github.com/go-ruby-activerecord/activerecord" - - "github.com/go-embedded-ruby/ruby/internal/object" -) +import "github.com/go-embedded-ruby/ruby/internal/object" // classOfExtBinding is the wasm half of the classOf seam: the guarded value types -// can never be constructed on wasm (their constructors are the stubs below), so -// no value ever needs mapping here. +// can never be constructed on wasm (their constructors are stubs), so no value +// ever needs mapping here. func (vm *VM) classOfExtBinding(v object.Value) (*RClass, bool) { _, _ = vm, v; return nil, false } // valueEqualExtBinding is the wasm half of the value-equality seam: the guarded @@ -44,138 +27,3 @@ func valueEqualExtBinding(a, b object.Value) (bool, bool) { _, _ = a, b; return // hasCustomEqExtBinding is the wasm half of the custom-== seam: the guarded value // types (BSON::ObjectId, …) can never be constructed on wasm. func hasCustomEqExtBinding(v object.Value) bool { _ = v; return false } - -// wasmUnsupported returns a native method that raises the standard -// " is not supported on wasm" NotImplementedError. -func wasmUnsupported(gem string) NativeFn { - return func(_ *VM, _ object.Value, _ []object.Value, _ *Proc) object.Value { - return raise("NotImplementedError", "%s is not supported on wasm", gem) - } -} - -// simpleName returns the last "::"-segment of a qualified constant name. -func simpleName(qualified string) string { - if i := strings.LastIndex(qualified, ":"); i >= 0 { - return qualified[i+1:] - } - return qualified -} - -// stubBindingModule installs a top-level module `name` plus its exception class -// `errQualified` (< StandardError), registered both as a nested constant and -// under its qualified name, mirroring the native register's error tree so a -// `rescue Gem::Error` still resolves. -func (vm *VM) stubBindingModule(name, errQualified string) *RClass { - mod := newClass(name, nil) - mod.isModule = true - vm.consts[name] = mod - std := vm.consts["StandardError"].(*RClass) - ec := newClass(errQualified, std) - mod.consts[simpleName(errQualified)] = ec - vm.consts[errQualified] = ec - return mod -} - -// stubBindingClass installs a nested class `qualified` under mod (as vm.cObject -// subclass), registered both nested and top-level, like the native trees. -func (vm *VM) stubBindingClass(mod *RClass, qualified string) *RClass { - c := newClass(qualified, vm.cObject) - mod.consts[simpleName(qualified)] = c - vm.consts[qualified] = c - return c -} - -// stubSMethods attaches raising singleton methods (class/module methods) named by -// names to c. -func stubSMethods(c *RClass, gem string, names ...string) { - for _, n := range names { - c.smethods[n] = &Method{name: n, owner: c, native: wasmUnsupported(gem)} - } -} - -// registerSQLite3 installs a graceful SQLite3 module: SQLite3::Exception and a -// SQLite3::Database whose .new/.open raise on wasm. -func (vm *VM) registerSQLite3() { - mod := vm.stubBindingModule("SQLite3", "SQLite3::Exception") - db := vm.stubBindingClass(mod, "SQLite3::Database") - stubSMethods(db, "sqlite3", "new", "open") -} - -// registerSequel installs a graceful Sequel module: Sequel::Error and the -// Sequel.sqlite / .connect / .mock factory methods, all raising on wasm. -func (vm *VM) registerSequel() { - mod := vm.stubBindingModule("Sequel", "Sequel::Error") - stubSMethods(mod, "sequel", "sqlite", "connect", "mock") -} - -// registerBolt installs a graceful Bolt module: Bolt::Error and a Bolt::DB whose -// .open/.new raise on wasm. -func (vm *VM) registerBolt() { - mod := vm.stubBindingModule("Bolt", "Bolt::Error") - db := vm.stubBindingClass(mod, "Bolt::DB") - stubSMethods(db, "bolt", "open", "new") -} - -// registerBleve installs a graceful Bleve module: Bleve::Error and the -// Bleve.new / .open / .new_mem_index factory methods, all raising on wasm. -func (vm *VM) registerBleve() { - mod := vm.stubBindingModule("Bleve", "Bleve::Error") - stubSMethods(mod, "bleve", "new", "open", "new_mem_index") -} - -// registerEtcd installs graceful Etcd and Etcdv3 modules (Etcd::Error) whose -// .new/.connect raise on wasm. -func (vm *VM) registerEtcd() { - mod := vm.stubBindingModule("Etcd", "Etcd::Error") - stubSMethods(mod, "etcd", "new", "connect") - alias := newClass("Etcdv3", nil) - alias.isModule = true - vm.consts["Etcdv3"] = alias - stubSMethods(alias, "etcd", "new", "connect") -} - -// registerConfd installs a graceful Confd module: Confd::Error and Confd.render, -// which raises on wasm. -func (vm *VM) registerConfd() { - mod := vm.stubBindingModule("Confd", "Confd::Error") - stubSMethods(mod, "confd", "render") -} - -// arSQLiteAdapter is the wasm stub of the ActiveRecord adapter. ActiveRecord -// itself builds for wasm; only its executor is wired to SQLite3, so the adapter -// seam is stubbed here. The type still satisfies activerecord.Adapter (so the -// shared ActiveRecord code that passes it to the core compiles) and every method -// raises — though none is reached, because arConnect/arRequireAdapter raise -// first. -type arSQLiteAdapter struct{} - -func (a *arSQLiteAdapter) Execute(sql string) ([]activerecord.Row, error) { - raise("NotImplementedError", "active_record (sqlite3) is not supported on wasm") - return nil, nil -} - -func (a *arSQLiteAdapter) ExecuteDML(sql string) (affected int64, lastInsertID int64, err error) { - raise("NotImplementedError", "active_record (sqlite3) is not supported on wasm") - return 0, 0, nil -} - -func (a *arSQLiteAdapter) AdapterName() string { return "sqlite3" } - -// arConnect raises on wasm: there is no SQLite3 driver to open a connection. -func (vm *VM) arConnect(path string) { - _ = path - raise("NotImplementedError", "active_record (sqlite3) is not supported on wasm") -} - -// arRequireAdapter raises on wasm (a connection can never be established), so any -// query path degrades to a clean error rather than a nil dereference. -func (vm *VM) arRequireAdapter() *arSQLiteAdapter { - raise("NotImplementedError", "active_record (sqlite3) is not supported on wasm") - return nil -} - -// arRawConnection is the wasm stub of ActiveRecord::Base.connection's SQLite3 -// tail; it raises, matching the establish_connection path. -func (vm *VM) arRawConnection() object.Value { - return raise("NotImplementedError", "active_record (sqlite3) is not supported on wasm") -} diff --git a/internal/vm/bleve.go b/internal/vm/bleve.go index aa3b81c8..e8902f06 100644 --- a/internal/vm/bleve.go +++ b/internal/vm/bleve.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm diff --git a/internal/vm/bleve_bind.go b/internal/vm/bleve_bind.go index c9a4c42f..6ff29f10 100644 --- a/internal/vm/bleve_bind.go +++ b/internal/vm/bleve_bind.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm diff --git a/internal/vm/bleve_bind_internal_test.go b/internal/vm/bleve_bind_internal_test.go index 1e948468..2733ea86 100644 --- a/internal/vm/bleve_bind_internal_test.go +++ b/internal/vm/bleve_bind_internal_test.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm diff --git a/internal/vm/bleve_test.go b/internal/vm/bleve_test.go index 9743b38e..3f2c8c3c 100644 --- a/internal/vm/bleve_test.go +++ b/internal/vm/bleve_test.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm_test diff --git a/internal/vm/bolt.go b/internal/vm/bolt.go index 3e4c92a6..91d9b3b8 100644 --- a/internal/vm/bolt.go +++ b/internal/vm/bolt.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm diff --git a/internal/vm/bolt_bind.go b/internal/vm/bolt_bind.go index aa034393..15871a01 100644 --- a/internal/vm/bolt_bind.go +++ b/internal/vm/bolt_bind.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm diff --git a/internal/vm/confd.go b/internal/vm/confd.go index 256990d1..196ba3fd 100644 --- a/internal/vm/confd.go +++ b/internal/vm/confd.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm diff --git a/internal/vm/confd_bind.go b/internal/vm/confd_bind.go index f753d820..04621f17 100644 --- a/internal/vm/confd_bind.go +++ b/internal/vm/confd_bind.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm diff --git a/internal/vm/confd_internal_test.go b/internal/vm/confd_internal_test.go index 375871ea..120eb928 100644 --- a/internal/vm/confd_internal_test.go +++ b/internal/vm/confd_internal_test.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm diff --git a/internal/vm/errno.go b/internal/vm/errno.go index 6a073765..2596bff0 100644 --- a/internal/vm/errno.go +++ b/internal/vm/errno.go @@ -55,9 +55,12 @@ import ( // // "Universal" is measured, not assumed: a probe compiled this set for // darwin/{amd64,arm64}, linux/{amd64,386,arm64,riscv64,loong64,ppc64le,s390x}, -// windows/{amd64,arm64}, wasip1/wasm and js/wasm. The names js/wasm alone lacks -// (ENOTRECOVERABLE, EOWNERDEAD, ETXTBSY) are NOT here — they live in the -// per-target files, which is what issue #682 was about. +// windows/{amd64,arm64}, wasip1/wasm and js/wasm, and later for every other +// GOOS Go supports except plan9 (whose syscall package has no errno numbers). The +// names some of those lack (ENOTRECOVERABLE, EOWNERDEAD and ETXTBSY on js/wasm, +// EMULTIHOP and ENOLINK on OpenBSD, ENOTRECOVERABLE and EOWNERDEAD on NetBSD +// and DragonFly) are NOT here — they live in the per-target files, which is what +// issue #682 was about. // // The names outside this set are in errnoUndefinedNames, where rbgo reports 0. // That is a DECLARED conformance gap, not an absence: the same probe found that @@ -102,7 +105,6 @@ var errnoUniversalNumbers = map[string]int64{ "EMFILE": int64(syscall.EMFILE), "EMLINK": int64(syscall.EMLINK), "EMSGSIZE": int64(syscall.EMSGSIZE), - "EMULTIHOP": int64(syscall.EMULTIHOP), "ENAMETOOLONG": int64(syscall.ENAMETOOLONG), "ENETDOWN": int64(syscall.ENETDOWN), "ENETRESET": int64(syscall.ENETRESET), @@ -113,7 +115,6 @@ var errnoUniversalNumbers = map[string]int64{ "ENOENT": int64(syscall.ENOENT), "ENOEXEC": int64(syscall.ENOEXEC), "ENOLCK": int64(syscall.ENOLCK), - "ENOLINK": int64(syscall.ENOLINK), "ENOMEM": int64(syscall.ENOMEM), "ENOMSG": int64(syscall.ENOMSG), "ENOPROTOOPT": int64(syscall.ENOPROTOOPT), @@ -158,7 +159,11 @@ var errnoUniversalNumbers = map[string]int64{ // exactly cover this list with no overlap — so a future name added to one file // and forgotten in the other is a test failure, not a silently shorter // Errno.constants. -var errnoPlatformNames = []string{"ENOTRECOVERABLE", "EOWNERDEAD", "ETXTBSY"} +// +// The BSDs and DragonFly added two more: OpenBSD has no EMULTIHOP or ENOLINK, +// NetBSD and DragonFly no ENOTRECOVERABLE or EOWNERDEAD (errno_openbsd.go, +// errno_bsd_noowner.go). +var errnoPlatformNames = []string{"EMULTIHOP", "ENOLINK", "ENOTRECOVERABLE", "EOWNERDEAD", "ETXTBSY"} // errnoKnownNameCount is the size of MRI's known_errors list (ruby/ruby v3_4_0 // known_errors.inc), which Errno.constants must have on EVERY target. It is diff --git a/internal/vm/errno_bsd_noowner.go b/internal/vm/errno_bsd_noowner.go new file mode 100644 index 00000000..a43cfa8f --- /dev/null +++ b/internal/vm/errno_bsd_noowner.go @@ -0,0 +1,25 @@ +// Copyright (c) the go-embedded-ruby/ruby authors +// +// SPDX-License-Identifier: BSD-3-Clause + +//go:build netbsd || dragonfly + +package vm + +import "syscall" + +// The per-target half of errno.go's bucket rule for NetBSD and DragonFly, whose +// syscall packages have no ENOTRECOVERABLE or EOWNERDEAD. MRI's undefined_error +// makes both constants naming Errno::NOERROR there. + +// errnoPlatformNumbers gives the numbers for the errnoPlatformNames these +// targets define. +var errnoPlatformNumbers = map[string]int64{ + "EMULTIHOP": int64(syscall.EMULTIHOP), + "ENOLINK": int64(syscall.ENOLINK), + "ETXTBSY": int64(syscall.ETXTBSY), +} + +// errnoPlatformUndefined lists the errnoPlatformNames these targets have no +// number for. +var errnoPlatformUndefined = []string{"ENOTRECOVERABLE", "EOWNERDEAD"} diff --git a/internal/vm/errno_native.go b/internal/vm/errno_native.go index 8dacccf4..bbb34047 100644 --- a/internal/vm/errno_native.go +++ b/internal/vm/errno_native.go @@ -2,16 +2,17 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !(js && wasm) +//go:build !(js && wasm) && !openbsd && !netbsd && !dragonfly package vm import "syscall" // This is the per-target half of the errno bucket rule stated in errno.go, for -// every target EXCEPT js/wasm: darwin, linux, windows and wasip1 all define -// ENOTRECOVERABLE, EOWNERDEAD and ETXTBSY, so all three take their real numbers -// here and none of them is an undefined_error name. +// every target EXCEPT js/wasm and the BSDs below: darwin, linux, windows, +// wasip1, freebsd, solaris, illumos and aix all define the five +// errnoPlatformNames, so all five take their real numbers here and none of them +// is an undefined_error name. // // The tag is spelled !(js && wasm) to match the file pair this package already // uses for the js-only split (jsbridge_native.go, servergems_native.go), and @@ -21,6 +22,8 @@ import "syscall" // errnoPlatformNumbers gives the numbers for the errnoPlatformNames this target // defines — all of them here. var errnoPlatformNumbers = map[string]int64{ + "EMULTIHOP": int64(syscall.EMULTIHOP), + "ENOLINK": int64(syscall.ENOLINK), "ENOTRECOVERABLE": int64(syscall.ENOTRECOVERABLE), "EOWNERDEAD": int64(syscall.EOWNERDEAD), "ETXTBSY": int64(syscall.ETXTBSY), diff --git a/internal/vm/errno_openbsd.go b/internal/vm/errno_openbsd.go new file mode 100644 index 00000000..8e314106 --- /dev/null +++ b/internal/vm/errno_openbsd.go @@ -0,0 +1,24 @@ +// Copyright (c) the go-embedded-ruby/ruby authors +// +// SPDX-License-Identifier: BSD-3-Clause + +//go:build openbsd + +package vm + +import "syscall" + +// The per-target half of errno.go's bucket rule for OpenBSD, whose +// (and so Go's syscall package) has no EMULTIHOP or ENOLINK. MRI's +// undefined_error makes both constants naming Errno::NOERROR there. + +// errnoPlatformNumbers gives the numbers for the errnoPlatformNames OpenBSD +// defines. +var errnoPlatformNumbers = map[string]int64{ + "ENOTRECOVERABLE": int64(syscall.ENOTRECOVERABLE), + "EOWNERDEAD": int64(syscall.EOWNERDEAD), + "ETXTBSY": int64(syscall.ETXTBSY), +} + +// errnoPlatformUndefined lists the errnoPlatformNames OpenBSD has no number for. +var errnoPlatformUndefined = []string{"EMULTIHOP", "ENOLINK"} diff --git a/internal/vm/errno_wasm.go b/internal/vm/errno_wasm.go index 7fe37d15..7281784f 100644 --- a/internal/vm/errno_wasm.go +++ b/internal/vm/errno_wasm.go @@ -6,6 +6,8 @@ package vm +import "syscall" + // This is the per-target half of the errno bucket rule stated in errno.go, for // js/wasm. Go's js/wasm syscall package (src/syscall/tables_js.go) carries an // errno enum of its own that omits ENOTRECOVERABLE, EOWNERDEAD and ETXTBSY — @@ -23,9 +25,12 @@ package vm // constant, not a name with a number rbgo invented. // errnoPlatformNumbers gives the numbers for the errnoPlatformNames this target -// defines — none of them. -var errnoPlatformNumbers = map[string]int64{} +// defines: EMULTIHOP and ENOLINK, which every target but OpenBSD has. +var errnoPlatformNumbers = map[string]int64{ + "EMULTIHOP": int64(syscall.EMULTIHOP), + "ENOLINK": int64(syscall.ENOLINK), +} // errnoPlatformUndefined lists the errnoPlatformNames this target has NO number -// for: all three. +// for: the three above. var errnoPlatformUndefined = []string{"ENOTRECOVERABLE", "EOWNERDEAD", "ETXTBSY"} diff --git a/internal/vm/etcd.go b/internal/vm/etcd.go index 81f95cff..7dd0919d 100644 --- a/internal/vm/etcd.go +++ b/internal/vm/etcd.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm diff --git a/internal/vm/etcd_bind.go b/internal/vm/etcd_bind.go index 73ef4117..8fa67ac8 100644 --- a/internal/vm/etcd_bind.go +++ b/internal/vm/etcd_bind.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm diff --git a/internal/vm/etcd_bind_internal_test.go b/internal/vm/etcd_bind_internal_test.go index 43bdd162..85a9d742 100644 --- a/internal/vm/etcd_bind_internal_test.go +++ b/internal/vm/etcd_bind_internal_test.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm diff --git a/internal/vm/filestat_unix.go b/internal/vm/filestat_unix.go index 2ebfb6a2..7d536afd 100644 --- a/internal/vm/filestat_unix.go +++ b/internal/vm/filestat_unix.go @@ -74,7 +74,7 @@ func statSys(fi fs.FileInfo) statFields { // rbgo defines, so the operation is passed through unchanged. var ( lockFdFn = func(path string) (int, error) { return unix.Open(path, unix.O_RDONLY, 0) } - flockFn = func(fd, op int) error { return unix.Flock(fd, op) } + flockFn = func(fd, op int) error { return sysFlock(fd, op) } closeFdFn = func(fd int) error { return unix.Close(fd) } ) diff --git a/internal/vm/mysql_test.go b/internal/vm/mysql_test.go index 4911e04e..ecd0831c 100644 --- a/internal/vm/mysql_test.go +++ b/internal/vm/mysql_test.go @@ -2,13 +2,15 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core && !386 && !arm && !mips && !mipsle +//go:build !wasm && !rbgo_core && !386 && !arm && !mips && !mipsle && (linux || darwin || windows || freebsd) // The in-process server these tests run against, dolthub/go-mysql-server // (v0.20.0, its latest release), does not compile for 32-bit targets: its // sql/types and sql/rowexec packages assign math.MaxUint32 and larger -// constants to int. The mysql2 binding itself builds and runs there; only -// this server-backed test file is limited to 64-bit targets. +// constants to int, and its server package needs internal/sockstate, which +// has no implementation outside Linux, macOS, Windows and FreeBSD. The mysql2 +// binding itself builds and runs everywhere; only this server-backed test file +// is limited to those systems' 64-bit targets. package vm_test diff --git a/internal/vm/nats_bind_internal_test.go b/internal/vm/nats_bind_internal_test.go index ce03c58c..62de8165 100644 --- a/internal/vm/nats_bind_internal_test.go +++ b/internal/vm/nats_bind_internal_test.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && !aix && !(freebsd && (386 || arm)) package vm diff --git a/internal/vm/pstore_lock_unix.go b/internal/vm/pstore_lock_unix.go index c20ed606..6b875382 100644 --- a/internal/vm/pstore_lock_unix.go +++ b/internal/vm/pstore_lock_unix.go @@ -8,12 +8,15 @@ package vm import ( "os" - "syscall" + + "golang.org/x/sys/unix" ) -// flockSyscall is syscall.Flock as a var so a test can force the lock-failure -// branch of flockFile (a valid fd never fails it in practice). -var flockSyscall = syscall.Flock +// flockSyscall is sysFlock as a var so a test can force the lock-failure +// branch of flockFile (a valid fd never fails it in practice). The syscall +// package has no Flock on AIX or Solaris; x/sys/unix has the LOCK_ constants on +// both, and sysFlock the call (emulated on AIX). +var flockSyscall = sysFlock // flockFile opens the store file O_CREAT|O_RDWR and takes a real advisory flock(2) // on it (LOCK_SH for a read-only transaction, LOCK_EX otherwise), returning a @@ -25,16 +28,16 @@ func flockFile(path string, readOnly bool) (func(), error) { if err != nil { return nil, err } - how := syscall.LOCK_EX + how := unix.LOCK_EX if readOnly { - how = syscall.LOCK_SH + how = unix.LOCK_SH } if err := flockSyscall(int(f.Fd()), how); err != nil { f.Close() return nil, err } return func() { - flockSyscall(int(f.Fd()), syscall.LOCK_UN) + flockSyscall(int(f.Fd()), unix.LOCK_UN) f.Close() }, nil } diff --git a/internal/vm/rlimit_aix.go b/internal/vm/rlimit_aix.go new file mode 100644 index 00000000..489ab7ab --- /dev/null +++ b/internal/vm/rlimit_aix.go @@ -0,0 +1,17 @@ +// Copyright (c) the go-embedded-ruby/ruby authors +// +// SPDX-License-Identifier: BSD-3-Clause + +//go:build aix + +package vm + +import "golang.org/x/sys/unix" + +// rlimitHostResources are the optional RLIMIT_ resources (see rlimitResources). +// AIX has no RLIMIT_MEMLOCK. +var rlimitHostResources = map[string]int{ + "AS": unix.RLIMIT_AS, + "NPROC": unix.RLIMIT_NPROC, + "RSS": unix.RLIMIT_RSS, +} diff --git a/internal/vm/rlimit_full.go b/internal/vm/rlimit_full.go new file mode 100644 index 00000000..f08cf294 --- /dev/null +++ b/internal/vm/rlimit_full.go @@ -0,0 +1,18 @@ +// Copyright (c) the go-embedded-ruby/ruby authors +// +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !windows && !wasm && !openbsd && !aix && !solaris + +package vm + +import "golang.org/x/sys/unix" + +// rlimitHostResources are the optional RLIMIT_ resources (see rlimitResources). +// Linux, Darwin, FreeBSD, NetBSD and DragonFly have all four. +var rlimitHostResources = map[string]int{ + "AS": unix.RLIMIT_AS, + "MEMLOCK": unix.RLIMIT_MEMLOCK, + "NPROC": unix.RLIMIT_NPROC, + "RSS": unix.RLIMIT_RSS, +} diff --git a/internal/vm/rlimit_openbsd.go b/internal/vm/rlimit_openbsd.go new file mode 100644 index 00000000..96a5c1ab --- /dev/null +++ b/internal/vm/rlimit_openbsd.go @@ -0,0 +1,17 @@ +// Copyright (c) the go-embedded-ruby/ruby authors +// +// SPDX-License-Identifier: BSD-3-Clause + +//go:build openbsd + +package vm + +import "golang.org/x/sys/unix" + +// rlimitHostResources are the optional RLIMIT_ resources (see rlimitResources). +// OpenBSD has no RLIMIT_AS. +var rlimitHostResources = map[string]int{ + "MEMLOCK": unix.RLIMIT_MEMLOCK, + "NPROC": unix.RLIMIT_NPROC, + "RSS": unix.RLIMIT_RSS, +} diff --git a/internal/vm/rlimit_solaris.go b/internal/vm/rlimit_solaris.go new file mode 100644 index 00000000..772eaa9c --- /dev/null +++ b/internal/vm/rlimit_solaris.go @@ -0,0 +1,16 @@ +// Copyright (c) the go-embedded-ruby/ruby authors +// +// SPDX-License-Identifier: BSD-3-Clause + +//go:build solaris + +package vm + +import "golang.org/x/sys/unix" + +// rlimitHostResources are the optional RLIMIT_ resources (see rlimitResources). +// Solaris and illumos (GOOS=illumos also sets the solaris tag) have RLIMIT_AS +// but not MEMLOCK, NPROC or RSS. +var rlimitHostResources = map[string]int{ + "AS": unix.RLIMIT_AS, +} diff --git a/internal/vm/sequel.go b/internal/vm/sequel.go index 09a7c4e8..0d0a0292 100644 --- a/internal/vm/sequel.go +++ b/internal/vm/sequel.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm diff --git a/internal/vm/sequel_bind.go b/internal/vm/sequel_bind.go index 40e13f63..325b7512 100644 --- a/internal/vm/sequel_bind.go +++ b/internal/vm/sequel_bind.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm diff --git a/internal/vm/sequel_schema.go b/internal/vm/sequel_schema.go index 714ae341..68f4eef9 100644 --- a/internal/vm/sequel_schema.go +++ b/internal/vm/sequel_schema.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm diff --git a/internal/vm/sequel_test.go b/internal/vm/sequel_test.go index 21c142c0..a26bcc60 100644 --- a/internal/vm/sequel_test.go +++ b/internal/vm/sequel_test.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm_test diff --git a/internal/vm/spawn_native.go b/internal/vm/spawn_native.go index ae745e98..c52b09e1 100644 --- a/internal/vm/spawn_native.go +++ b/internal/vm/spawn_native.go @@ -93,17 +93,26 @@ const procPosix = true // process.c's rlimit_resource_name2int accepts. The numbers differ per OS // (RLIMIT_NPROC is 6 on Linux and 7 on Darwin), so they come from the kernel // headers via x/sys/unix rather than being written out here. -var rlimitResources = map[string]int{ - "AS": unix.RLIMIT_AS, - "CORE": unix.RLIMIT_CORE, - "CPU": unix.RLIMIT_CPU, - "DATA": unix.RLIMIT_DATA, - "FSIZE": unix.RLIMIT_FSIZE, - "MEMLOCK": unix.RLIMIT_MEMLOCK, - "NOFILE": unix.RLIMIT_NOFILE, - "NPROC": unix.RLIMIT_NPROC, - "RSS": unix.RLIMIT_RSS, - "STACK": unix.RLIMIT_STACK, +// +// Not every system has every resource. The six below exist everywhere this file +// builds; AS, MEMLOCK, NPROC and RSS come from rlimitHostResources, one file per +// family (rlimit_*.go), and a name the host lacks is simply absent — as in MRI, +// whose process.c defines Process::RLIMIT_ only #ifdef RLIMIT_. +var rlimitResources = mergeRlimitResources(map[string]int{ + "CORE": unix.RLIMIT_CORE, + "CPU": unix.RLIMIT_CPU, + "DATA": unix.RLIMIT_DATA, + "FSIZE": unix.RLIMIT_FSIZE, + "NOFILE": unix.RLIMIT_NOFILE, + "STACK": unix.RLIMIT_STACK, +}, rlimitHostResources) + +// mergeRlimitResources adds the host's optional resources to the common ones. +func mergeRlimitResources(common, host map[string]int) map[string]int { + for name, n := range host { + common[name] = n + } + return common } // rlimitValues is the symbolic limit table process.c's rlimit_resource_value @@ -123,6 +132,9 @@ var prioTargets = map[string]int{ "USER": unix.PRIO_USER, } +// setRlim stores a limit in an Rlimit field of either signedness. +func setRlim[T ~int64 | ~uint64](dst *T, v uint64) { *dst = T(v) } + // The seams themselves. Each is a package var so a whitebox test can drive the // failure branch of the shared code without needing a kernel that fails. var ( @@ -134,7 +146,12 @@ var ( return uint64(rl.Cur), uint64(rl.Max), nil } procSetrlimit = func(res int, cur, max uint64) error { - rl := unix.Rlimit{Cur: cur, Max: max} + // Rlimit's fields are uint64 on most systems and int64 on FreeBSD and + // DragonFly (whose RLIM_INFINITY is math.MaxInt64); setRlim stores into + // whichever this one has. + var rl unix.Rlimit + setRlim(&rl.Cur, cur) + setRlim(&rl.Max, max) return unix.Setrlimit(res, &rl) } procGetpriority = unix.Getpriority diff --git a/internal/vm/sqlite3.go b/internal/vm/sqlite3.go index a8f662e6..29057d7a 100644 --- a/internal/vm/sqlite3.go +++ b/internal/vm/sqlite3.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm diff --git a/internal/vm/sqlite3_bind.go b/internal/vm/sqlite3_bind.go index fe1f0480..c7ea3cc1 100644 --- a/internal/vm/sqlite3_bind.go +++ b/internal/vm/sqlite3_bind.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm diff --git a/internal/vm/sqlite3_bind_internal_test.go b/internal/vm/sqlite3_bind_internal_test.go index 6844eefb..41a32758 100644 --- a/internal/vm/sqlite3_bind_internal_test.go +++ b/internal/vm/sqlite3_bind_internal_test.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm diff --git a/internal/vm/sqlite3_test.go b/internal/vm/sqlite3_test.go index 890e87a6..980195ec 100644 --- a/internal/vm/sqlite3_test.go +++ b/internal/vm/sqlite3_test.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && ((darwin && (amd64 || arm64)) || (freebsd && (386 || amd64 || arm || arm64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) || (netbsd && amd64) || (openbsd && (amd64 || arm64)) || windows) package vm_test diff --git a/internal/vm/sysflock_aix.go b/internal/vm/sysflock_aix.go new file mode 100644 index 00000000..b24c3f67 --- /dev/null +++ b/internal/vm/sysflock_aix.go @@ -0,0 +1,40 @@ +// Copyright (c) the go-embedded-ruby/ruby authors +// +// SPDX-License-Identifier: BSD-3-Clause + +//go:build aix + +package vm + +import ( + "io" + + "golang.org/x/sys/unix" +) + +// sysFlock emulates flock(2), which AIX does not have, with a whole-file +// fcntl(2) record lock, exactly as MRI's missing/flock.c (ruby/ruby v3_4_0, the +// HAVE_FCNTL branch) does: LOCK_SH is F_RDLCK, LOCK_EX is F_WRLCK, LOCK_UN is +// F_UNLCK, over [0, EOF) from SEEK_SET; LOCK_NB selects F_SETLK instead of the +// blocking F_SETLKW; any other operation is EINVAL. +// +// The semantics differ from flock(2) the way MRI's do on this platform: a +// record lock belongs to the process, not to the open file description. +func sysFlock(fd, how int) error { + lk := unix.Flock_t{Whence: io.SeekStart} + switch how &^ unix.LOCK_NB { + case unix.LOCK_SH: + lk.Type = unix.F_RDLCK + case unix.LOCK_EX: + lk.Type = unix.F_WRLCK + case unix.LOCK_UN: + lk.Type = unix.F_UNLCK + default: + return unix.EINVAL + } + cmd := unix.F_SETLKW + if how&unix.LOCK_NB != 0 { + cmd = unix.F_SETLK + } + return unix.FcntlFlock(uintptr(fd), cmd, &lk) +} diff --git a/internal/vm/sysflock_unix.go b/internal/vm/sysflock_unix.go new file mode 100644 index 00000000..84e318ea --- /dev/null +++ b/internal/vm/sysflock_unix.go @@ -0,0 +1,14 @@ +// Copyright (c) the go-embedded-ruby/ruby authors +// +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !windows && !wasm && !aix + +package vm + +import "golang.org/x/sys/unix" + +// sysFlock is flock(2), the lock behind File#flock and PStore's transaction +// lock. AIX has no flock(2); sysflock_aix.go emulates it with fcntl(2) locks, +// as MRI's missing/flock.c does there. +var sysFlock = unix.Flock