Skip to content

Commit 06bce72

Browse files
committed
feat: import framebased-foreign-function-interface (go part)
1 parent 6130367 commit 06bce72

19 files changed

+2035
-2
lines changed

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ require (
1212
github.com/stretchr/testify v1.8.4
1313
github.com/urfave/cli/v2 v2.25.7
1414
github.com/zeebo/xxh3 v1.0.2
15+
golang.org/x/text v0.13.0
16+
golang.org/x/tools v0.14.0
1517
)
1618

1719
require (
@@ -26,7 +28,8 @@ require (
2628
github.com/russross/blackfriday/v2 v2.1.0 // indirect
2729
github.com/x448/float16 v0.8.4 // indirect
2830
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
29-
golang.org/x/sys v0.6.0 // indirect
31+
golang.org/x/mod v0.13.0 // indirect
32+
golang.org/x/sys v0.13.0 // indirect
3033
golang.org/x/term v0.6.0 // indirect
3134
gopkg.in/yaml.v3 v3.0.1 // indirect
3235
)

go.sum

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,21 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr
4747
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
4848
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
4949
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
50+
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
51+
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
52+
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
5053
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
5154
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
5255
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
53-
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
5456
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
57+
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
58+
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
5559
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
5660
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
61+
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
62+
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
63+
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
64+
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
5765
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
5866
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
5967
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

public/fffi/FFFI.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
## Features
2+
* FFFI preserves ...
3+
- ... comments;
4+
- ... prolog code;
5+
- ... epilog code;
6+
- ... receiver variable names and types;
7+
- ... multiple objects in one file.
8+
* Error Handling
9+
- Implicitly: Method `receiver.handleError(_err_)`.
10+
- Explicitly (last result type is an interface containing `error`): Return values are left untouched.
11+
* Reserved Names in go:
12+
- Variable `_err_` is used for implicitly handled errors.
13+
- Receiver name `foreignptr` is used to detect foreign pointers/instances to enable object-oriented interfaces.
14+
* Reserved Names in C++:
15+
- Goto label `skipAfterError` is reserved.
16+
* Object-oriented interfaces
17+
- Define a custom type as an alias over a pointer-wide unsigned integer (consider architecture (e.g. ILP32, LLP64) of foreign code, not go code);
18+
- Name the receiver `foreignptr` (needed to detect oo use case);
19+
- Go method receiver is sent to foreign code and can be cast to a regular foreign code pointer (e.g. `((my_ptr_type*)foreignptr)->myMethod(...)`).
20+
21+
## Interfaces
22+
FFFI object needs to implement the following interface
23+
```go
24+
type FffiI interface {
25+
handleError(err error)
26+
getFffi() *fffi2.Fffi2
27+
}
28+
```
29+
## Example
30+
Regular go package defining type aliases and objects implementing the `FffiI` interface:
31+
```go
32+
package mypackage
33+
34+
type MyEnumE uint32
35+
type MyBool bool
36+
type MyError interface {
37+
F() uint32
38+
error
39+
}
40+
type MyStruct struct {
41+
fffi *fffi2.Fffi2
42+
}
43+
func (inst *MyStruct) handleError(err error) {
44+
log.Fatal().Err(err).Msg("fffi error")
45+
}
46+
func (inst *MyStruct) getFffi() *fffi2.Fffi2 {
47+
return inst.fffi
48+
}
49+
```
50+
Interface definition language file (e.g. .idl.go):
51+
```go
52+
package mypackage
53+
54+
// MyExportedFunction a comment
55+
func (inst *MyStruct) MyExportedFunctionSimple(a uint32,b uint32) (res uint32) {
56+
_ = `res = myU32AdditionInCpp(a,b)`
57+
}
58+
59+
// MyExportedFunction a comment
60+
func (inst *MyStruct) MyExportedFunction(a uint32,b MyEnumE) (success MyBool) {
61+
{ // prolog code
62+
myvar0 := 0
63+
_ = myvar
64+
}
65+
_ = `success = cppFunc(a,b)`
66+
{ // epilog code
67+
mvar1 := 1
68+
_ = myvar1
69+
}
70+
}
71+
func (inst *MyStruct) MyExportedFunction2(a uint32,b MyEnumE) (success MyBool, err MyError) {
72+
_ = `if(!cppFunc(a,b)) {
73+
sendString("my error string");
74+
goto skipAfterError;
75+
}`
76+
}
77+
```

public/fffi/IDEAS.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
## Next Steps
2+
* Deferred function results (pointer return values)
3+
* Channels
4+
- Zero-Copy shared memory channel
5+
- Message Bus (nats? redis pub/sub? kafka? rabbitmq?)
6+
- Wazero (WASM) as a channel?
7+
* Strings
8+
- Learn string dictionary by sampling
9+
- Pre-Extract all constants string from go codebase
10+
* Callbacks
11+
- Add JIT callbacks

public/fffi/compiletime/app.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package compiletime
2+
3+
import (
4+
"errors"
5+
"github.com/stergiotis/boxer/public/config"
6+
"github.com/stergiotis/boxer/public/observability/eh"
7+
"github.com/stergiotis/boxer/public/observability/eh/eb"
8+
"github.com/urfave/cli/v2"
9+
"io"
10+
"os"
11+
"path/filepath"
12+
"syscall"
13+
)
14+
15+
func makeFileReadOnly(path string) error {
16+
s, err := os.Stat(path)
17+
if err != nil {
18+
return eb.Build().Str("path", path).Errorf("unable to stat file: %w", err)
19+
}
20+
err = os.Chmod(path, s.Mode()&^(os.FileMode(syscall.S_IWUSR)|os.FileMode(syscall.S_IWGRP)|os.FileMode(syscall.S_IWOTH)))
21+
if err != nil {
22+
return eb.Build().Str("path", path).Errorf("unable to chmod file: %w", err)
23+
}
24+
return nil
25+
}
26+
func emitToFile(path string, emitter Emitter) (err error) {
27+
_ = os.MkdirAll(filepath.Dir(path), 0o000)
28+
29+
var w io.WriteCloser
30+
err = os.Remove(path)
31+
if err != nil && !errors.Is(err, os.ErrNotExist) {
32+
err = eb.Build().Str("path", path).Errorf("unable to remove output file: %w", err)
33+
return
34+
}
35+
w, err = os.Create(path)
36+
if err != nil {
37+
err = eb.Build().Str("path", path).Errorf("unable to create file: %w", err)
38+
return
39+
}
40+
_, err = emitter.Emit(w)
41+
_ = w.Close()
42+
if err != nil {
43+
err = eb.Build().Str("path", path).Errorf("unable to populate file: %w", err)
44+
return
45+
}
46+
err = makeFileReadOnly(path)
47+
if err != nil {
48+
err = eb.Build().Str("path", path).Errorf("unable to make file read-only: %w", err)
49+
return
50+
}
51+
52+
return
53+
}
54+
func generateBackendCode(idlDriver IDLDriver, cfg *Config, namer *Namer) (err error) {
55+
be := NewCodeTransformerBackendPresenterCpp(namer)
56+
err = idlDriver.DriveBackend(be)
57+
if err != nil {
58+
err = eh.Errorf("unable to generate code: %w", err)
59+
return
60+
}
61+
err = emitToFile(cfg.CppOutputFile, be)
62+
if err != nil {
63+
err = eh.Errorf("unable to generate cpp file: %w", err)
64+
return
65+
}
66+
67+
return
68+
}
69+
func generateFrontendCode(idlDriver IDLDriver, cfg *Config, namer *Namer) (err error) {
70+
fe := NewCodeTransformerFrontendGo(namer)
71+
err = idlDriver.DriveFrontend(fe)
72+
if err != nil {
73+
err = eh.Errorf("unable to generate code: %w", err)
74+
return
75+
}
76+
err = emitToFile(cfg.GoOutputFile, fe)
77+
if err != nil {
78+
err = eh.Errorf("unable to generate go file: %w", err)
79+
return
80+
}
81+
82+
return
83+
}
84+
85+
func mainE(config *Config, namerCfg *NamerConfig) (err error) {
86+
namer := NewNamer(namerCfg)
87+
_ = os.Remove(config.GoOutputFile)
88+
_ = os.Remove(config.CppOutputFile)
89+
var idlDriver *IDLDriverGoFile
90+
idlDriver, err = NewIDLDriverGoFile(config.IdlBuildTag, config.IdlPackagePattern, runtime.FuncProcId(config.FuncProcIdOffset))
91+
if err != nil {
92+
err = eh.Errorf("unable to process IDL file: %w", err)
93+
return
94+
}
95+
err = generateBackendCode(idlDriver, config, namer)
96+
if err != nil {
97+
err = eh.Errorf("unable to generate backend code: %w", err)
98+
return
99+
}
100+
err = generateFrontendCode(idlDriver, config, namer)
101+
if err != nil {
102+
err = eh.Errorf("unable to generate frontend code: %w", err)
103+
return
104+
}
105+
return
106+
}
107+
func NewCommand(cfg *Config, namerCfg *NamerConfig) *cli.Command {
108+
if cfg == nil {
109+
cfg = &Config{
110+
IdlBuildTag: "fffi_idl_code",
111+
IdlPackagePattern: "",
112+
GoOutputFile: "",
113+
CppOutputFile: "",
114+
FuncProcIdOffset: 0,
115+
validated: false,
116+
nValidationMessages: 0,
117+
}
118+
}
119+
if namerCfg == nil {
120+
namerCfg = &NamerConfig{
121+
RuneCppType: "rune_t",
122+
}
123+
}
124+
return &cli.Command{
125+
Name: "generateFffiCode",
126+
Flags: append(cfg.ToCliFlags(config.IdentityNameTransf, config.IdentityNameTransf), namerCfg.ToCliFlags(config.IdentityNameTransf, config.IdentityNameTransf)...),
127+
Action: func(context *cli.Context) error {
128+
nMessages := cfg.FromContext(config.IdentityNameTransf, context)
129+
if nMessages > 0 {
130+
return eb.Build().Int("nMessages", nMessages).Errorf("unable to compose config")
131+
}
132+
nMessages = namerCfg.FromContext(config.IdentityNameTransf, context)
133+
if nMessages > 0 {
134+
return eb.Build().Int("nMessages", nMessages).Errorf("unable to compose namer config")
135+
}
136+
return mainE(cfg, namerCfg)
137+
},
138+
}
139+
}

0 commit comments

Comments
 (0)