mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-07-16 21:10:19 +00:00
feat(ffi): make the scalar-fast-path binding drop loud (#120)
This commit is contained in:
parent
3e57751e3a
commit
ea67fb747a
11
README.md
11
README.md
@ -111,6 +111,17 @@ The default wire format is `cbor`. Override the library default with
|
||||
`declareLibrary("lib", Lib, defaultABIFormat = "c")`, or per annotation with an
|
||||
`"abi = ..."` spec, e.g. `{.ffi: "abi = c".}`.
|
||||
|
||||
An `abi = c` proc whose whole signature is scalar — fixed-width integer, float,
|
||||
or bool params (a `string` return is fine, a `string` param is not) and no
|
||||
structs, handles, or pointers — dispatches through a CBOR-free scalar fast path.
|
||||
Foreign-binding codegen for that shape isn't implemented yet, so
|
||||
under `-d:ffiGenBindings` such a proc would be omitted from the generated
|
||||
bindings — and `genBindings()` fails with an error naming the affected procs.
|
||||
Resolve it by switching the proc to `abi = cbor`, adding a non-scalar param so it
|
||||
takes the CBOR wire shape, or passing `-d:ffiAllowScalarSkip` to accept the
|
||||
omission (the proc still works over the scalar fast path; it's just absent from
|
||||
the generated foreign bindings).
|
||||
|
||||
## Placement of `genBindings()`
|
||||
|
||||
`genBindings()` reads the compile-time registries that the pragmas populate as
|
||||
|
||||
@ -259,12 +259,15 @@ task genbindings_c_echo, "Generate C bindings for the echo example":
|
||||
" -d:ffiSrcPath=../echo.nim" & " -o:/dev/null examples/echo/echo.nim"
|
||||
|
||||
task genbindings_c_abi_echo, "Generate CBOR-free abi=c C bindings for the echo example":
|
||||
# echoVersion is all-scalar under the abi=c default, so it has no foreign
|
||||
# codegen yet and is omitted from the bindings; -d:ffiAllowScalarSkip accepts
|
||||
# that omission instead of failing the build (see genBindings()).
|
||||
exec "nim c " & nimFlagsOrc & " --app:lib --noMain --nimMainPrefix:libecho" &
|
||||
" -d:ffiEchoAbiC -d:ffiGenBindings -d:targetLang=c_abi" &
|
||||
" -d:ffiEchoAbiC -d:ffiGenBindings -d:targetLang=c_abi -d:ffiAllowScalarSkip" &
|
||||
" -d:ffiOutputDir=examples/echo/c_abi_bindings" & " -d:ffiSrcPath=../echo.nim" &
|
||||
" -o:/dev/null examples/echo/echo.nim"
|
||||
exec "nim c " & nimFlagsRefc & " --app:lib --noMain --nimMainPrefix:libecho" &
|
||||
" -d:ffiEchoAbiC -d:ffiGenBindings -d:targetLang=c_abi" &
|
||||
" -d:ffiEchoAbiC -d:ffiGenBindings -d:targetLang=c_abi -d:ffiAllowScalarSkip" &
|
||||
" -d:ffiOutputDir=examples/echo/c_abi_bindings" & " -d:ffiSrcPath=../echo.nim" &
|
||||
" -o:/dev/null examples/echo/echo.nim"
|
||||
|
||||
|
||||
@ -172,3 +172,8 @@ const ffiOutputDir* {.strdefine.} = ""
|
||||
# Nim source path (relative to outputDir) embedded in generated build files;
|
||||
# set with -d:ffiSrcPath=../relative/path.nim
|
||||
const ffiSrcPath* {.strdefine.} = ""
|
||||
|
||||
# When set to true, scalar-only `abi = c` procs (which have no foreign-binding codegen
|
||||
# yet) are silently omitted from the generated bindings instead of failing the
|
||||
# build. Off by default so the drop is loud; see genBindings().
|
||||
const ffiAllowScalarSkip* {.booldefine.} = false
|
||||
|
||||
@ -1841,6 +1841,32 @@ macro ffiEvent*(args: varargs[untyped]): untyped =
|
||||
echo generated.repr
|
||||
return generated
|
||||
|
||||
proc reportScalarFastPathDrops(procs: seq[FFIProcMeta]) {.compileTime.} =
|
||||
## Scalar-fast-path procs have no foreign-binding codegen yet, so they can't
|
||||
## ride the generated bindings. Fail loudly, naming them, unless
|
||||
## `-d:ffiAllowScalarSkip` opts into the silent omission (then just hint).
|
||||
var skipped: seq[string] = @[]
|
||||
for p in procs:
|
||||
if p.scalarFastPath:
|
||||
skipped.add(p.procName)
|
||||
if skipped.len == 0:
|
||||
return
|
||||
if ffiAllowScalarSkip:
|
||||
for name in skipped:
|
||||
hint(
|
||||
"genBindings: omitting scalar-fast-path proc '" & name &
|
||||
"' from the bindings (-d:ffiAllowScalarSkip)"
|
||||
)
|
||||
return
|
||||
error(
|
||||
"genBindings: no foreign-binding codegen for scalar-fast-path `abi = c` " &
|
||||
"procs yet, so these would be silently omitted from the generated " & "bindings: " &
|
||||
skipped.join(", ") & ".\n" & "Fix by one of:\n" &
|
||||
" - switch the proc to `abi = cbor`, or\n" &
|
||||
" - add a non-scalar param (e.g. a struct or handle) so it takes the " &
|
||||
"CBOR wire shape, or\n" & " - pass -d:ffiAllowScalarSkip to accept the omission."
|
||||
)
|
||||
|
||||
macro genBindings*(
|
||||
outputDir: static[string] = ffiOutputDir, nimSrcRelPath: static[string] = ffiSrcPath
|
||||
): untyped =
|
||||
@ -1880,16 +1906,8 @@ macro genBindings*(
|
||||
)
|
||||
let lang = string_helpers.toLower(targetLang)
|
||||
let libName = deriveLibName(ffiProcRegistry)
|
||||
# Scalar-fast-path procs have no foreign-dispatch codegen yet (their C
|
||||
# export doesn't take the CBOR `(reqCbor, reqCborLen)` shape); drop them so
|
||||
# the generators don't emit a broken CBOR caller for them.
|
||||
let genProcs = bindableProcs(ffiProcRegistry)
|
||||
for p in ffiProcRegistry:
|
||||
if p.scalarFastPath:
|
||||
hint(
|
||||
"genBindings: skipping scalar-fast-path proc '" & p.procName &
|
||||
"' (no foreign-binding codegen for the scalar shape yet)"
|
||||
)
|
||||
reportScalarFastPathDrops(ffiProcRegistry)
|
||||
case lang
|
||||
of "rust":
|
||||
generateRustCrate(
|
||||
|
||||
26
tests/unit/fixtures/scalar_skip_fixture.nim
Normal file
26
tests/unit/fixtures/scalar_skip_fixture.nim
Normal file
@ -0,0 +1,26 @@
|
||||
## Compile fixture for the scalar-fast-path drop error (see
|
||||
## tests/unit/test_scalar_skip_gen.nim). Under `-d:ffiGenBindings` the scalar
|
||||
## `abi = c` proc below has no foreign-binding codegen, so genBindings() must
|
||||
## fail — unless `-d:ffiAllowScalarSkip` is passed, which downgrades it to a hint.
|
||||
|
||||
import ffi, chronos
|
||||
|
||||
type SkipLib = object
|
||||
base: int
|
||||
|
||||
declareLibrary("scalarskip", SkipLib)
|
||||
|
||||
type SkipConfig {.ffi.} = object
|
||||
base: int
|
||||
|
||||
proc scalarskip_create*(cfg: SkipConfig): Future[Result[SkipLib, string]] {.ffiCtor.} =
|
||||
return ok(SkipLib(base: cfg.base))
|
||||
|
||||
proc scalarskip_add*(
|
||||
lib: SkipLib, a: int, b: int
|
||||
): Future[Result[int, string]] {.ffi: "abi = c".} =
|
||||
## All-scalar signature: dispatches through the CBOR-free fast path and has no
|
||||
## foreign-binding codegen yet.
|
||||
return ok(lib.base + a + b)
|
||||
|
||||
genBindings()
|
||||
44
tests/unit/test_scalar_skip_gen.nim
Normal file
44
tests/unit/test_scalar_skip_gen.nim
Normal file
@ -0,0 +1,44 @@
|
||||
## Drives the scalar-fast-path drop error end to end: compiles
|
||||
## `fixtures/scalar_skip_fixture.nim` (a library with an all-scalar `abi = c`
|
||||
## proc) with `-d:ffiGenBindings` and asserts genBindings() fails loudly, and
|
||||
## that `-d:ffiAllowScalarSkip` downgrades the drop to a clean build.
|
||||
##
|
||||
## The fixture is compiled in a child `nim check` (search paths and compiler
|
||||
## captured at compile time) so its expected failure is observed as a test
|
||||
## assertion, not this file's own compile error.
|
||||
|
||||
import std/[os, osproc, strutils, compilesettings]
|
||||
import unittest2
|
||||
|
||||
const
|
||||
fixture = currentSourcePath().parentDir() / "fixtures" / "scalar_skip_fixture.nim"
|
||||
nimExe = getCurrentCompilerExe()
|
||||
ffiSearchPaths = querySettingSeq(searchPaths)
|
||||
|
||||
proc genFixture(extraDefs: seq[string]): tuple[output: string, exitCode: int] =
|
||||
let outDir = getTempDir() / "ffi_scalar_skip_out"
|
||||
let cacheDir = getTempDir() / "ffi_scalar_skip_cache"
|
||||
createDir(outDir)
|
||||
var cmd = quoteShell(nimExe) & " check --hints:off --warnings:off"
|
||||
for p in ffiSearchPaths:
|
||||
cmd.add(" --path:" & quoteShell(p))
|
||||
cmd.add(" -d:ffiGenBindings -d:targetLang=c")
|
||||
cmd.add(" -d:ffiOutputDir=" & quoteShell(outDir))
|
||||
for d in extraDefs:
|
||||
cmd.add(" " & d)
|
||||
cmd.add(" --nimcache:" & quoteShell(cacheDir))
|
||||
cmd.add(" " & quoteShell(fixture))
|
||||
execCmdEx(cmd)
|
||||
|
||||
suite "scalar-fast-path drop is loud under -d:ffiGenBindings":
|
||||
test "genBindings errors and names the dropped scalar proc":
|
||||
let (output, code) = genFixture(@[])
|
||||
check code != 0
|
||||
check output.contains("scalarskip_add")
|
||||
check output.contains("scalar-fast-path")
|
||||
check output.contains("-d:ffiAllowScalarSkip")
|
||||
|
||||
test "-d:ffiAllowScalarSkip downgrades the drop to a clean build":
|
||||
let (output, code) = genFixture(@["-d:ffiAllowScalarSkip"])
|
||||
check code == 0
|
||||
check not output.contains("Error")
|
||||
Loading…
x
Reference in New Issue
Block a user