Skip to content

Commit

Permalink
feat: import framebased-foreign-function-interface (go part)
Browse files Browse the repository at this point in the history
  • Loading branch information
stergiotis committed Oct 22, 2023
1 parent 6130367 commit 06bce72
Show file tree
Hide file tree
Showing 19 changed files with 2,035 additions and 2 deletions.
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ require (
github.com/stretchr/testify v1.8.4
github.com/urfave/cli/v2 v2.25.7
github.com/zeebo/xxh3 v1.0.2
golang.org/x/text v0.13.0
golang.org/x/tools v0.14.0
)

require (
Expand All @@ -26,7 +28,8 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/mod v0.13.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.6.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
10 changes: 9 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,21 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
77 changes: 77 additions & 0 deletions public/fffi/FFFI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
## Features
* FFFI preserves ...
- ... comments;
- ... prolog code;
- ... epilog code;
- ... receiver variable names and types;
- ... multiple objects in one file.
* Error Handling
- Implicitly: Method `receiver.handleError(_err_)`.
- Explicitly (last result type is an interface containing `error`): Return values are left untouched.
* Reserved Names in go:
- Variable `_err_` is used for implicitly handled errors.
- Receiver name `foreignptr` is used to detect foreign pointers/instances to enable object-oriented interfaces.
* Reserved Names in C++:
- Goto label `skipAfterError` is reserved.
* Object-oriented interfaces
- 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);
- Name the receiver `foreignptr` (needed to detect oo use case);
- 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(...)`).

## Interfaces
FFFI object needs to implement the following interface
```go
type FffiI interface {
handleError(err error)
getFffi() *fffi2.Fffi2
}
```
## Example
Regular go package defining type aliases and objects implementing the `FffiI` interface:
```go
package mypackage

type MyEnumE uint32
type MyBool bool
type MyError interface {
F() uint32
error
}
type MyStruct struct {
fffi *fffi2.Fffi2
}
func (inst *MyStruct) handleError(err error) {
log.Fatal().Err(err).Msg("fffi error")
}
func (inst *MyStruct) getFffi() *fffi2.Fffi2 {
return inst.fffi
}
```
Interface definition language file (e.g. .idl.go):
```go
package mypackage

// MyExportedFunction a comment
func (inst *MyStruct) MyExportedFunctionSimple(a uint32,b uint32) (res uint32) {
_ = `res = myU32AdditionInCpp(a,b)`
}

// MyExportedFunction a comment
func (inst *MyStruct) MyExportedFunction(a uint32,b MyEnumE) (success MyBool) {
{ // prolog code
myvar0 := 0
_ = myvar
}
_ = `success = cppFunc(a,b)`
{ // epilog code
mvar1 := 1
_ = myvar1
}
}
func (inst *MyStruct) MyExportedFunction2(a uint32,b MyEnumE) (success MyBool, err MyError) {
_ = `if(!cppFunc(a,b)) {
sendString("my error string");
goto skipAfterError;
}`
}
```
11 changes: 11 additions & 0 deletions public/fffi/IDEAS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## Next Steps
* Deferred function results (pointer return values)
* Channels
- Zero-Copy shared memory channel
- Message Bus (nats? redis pub/sub? kafka? rabbitmq?)
- Wazero (WASM) as a channel?
* Strings
- Learn string dictionary by sampling
- Pre-Extract all constants string from go codebase
* Callbacks
- Add JIT callbacks
139 changes: 139 additions & 0 deletions public/fffi/compiletime/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package compiletime

Check failure on line 1 in public/fffi/compiletime/app.go

View workflow job for this annotation

GitHub Actions / lint

: # github.com/stergiotis/boxer/public/fffi/compiletime

import (
"errors"
"github.com/stergiotis/boxer/public/config"
"github.com/stergiotis/boxer/public/observability/eh"
"github.com/stergiotis/boxer/public/observability/eh/eb"
"github.com/urfave/cli/v2"
"io"
"os"
"path/filepath"
"syscall"
)

func makeFileReadOnly(path string) error {
s, err := os.Stat(path)
if err != nil {
return eb.Build().Str("path", path).Errorf("unable to stat file: %w", err)
}
err = os.Chmod(path, s.Mode()&^(os.FileMode(syscall.S_IWUSR)|os.FileMode(syscall.S_IWGRP)|os.FileMode(syscall.S_IWOTH)))
if err != nil {
return eb.Build().Str("path", path).Errorf("unable to chmod file: %w", err)
}
return nil
}
func emitToFile(path string, emitter Emitter) (err error) {
_ = os.MkdirAll(filepath.Dir(path), 0o000)

var w io.WriteCloser
err = os.Remove(path)
if err != nil && !errors.Is(err, os.ErrNotExist) {
err = eb.Build().Str("path", path).Errorf("unable to remove output file: %w", err)
return
}
w, err = os.Create(path)
if err != nil {
err = eb.Build().Str("path", path).Errorf("unable to create file: %w", err)
return
}
_, err = emitter.Emit(w)
_ = w.Close()
if err != nil {
err = eb.Build().Str("path", path).Errorf("unable to populate file: %w", err)
return
}
err = makeFileReadOnly(path)
if err != nil {
err = eb.Build().Str("path", path).Errorf("unable to make file read-only: %w", err)
return
}

return
}
func generateBackendCode(idlDriver IDLDriver, cfg *Config, namer *Namer) (err error) {
be := NewCodeTransformerBackendPresenterCpp(namer)
err = idlDriver.DriveBackend(be)
if err != nil {
err = eh.Errorf("unable to generate code: %w", err)
return
}
err = emitToFile(cfg.CppOutputFile, be)
if err != nil {
err = eh.Errorf("unable to generate cpp file: %w", err)
return
}

return
}
func generateFrontendCode(idlDriver IDLDriver, cfg *Config, namer *Namer) (err error) {
fe := NewCodeTransformerFrontendGo(namer)
err = idlDriver.DriveFrontend(fe)
if err != nil {
err = eh.Errorf("unable to generate code: %w", err)
return
}
err = emitToFile(cfg.GoOutputFile, fe)
if err != nil {
err = eh.Errorf("unable to generate go file: %w", err)
return
}

return
}

func mainE(config *Config, namerCfg *NamerConfig) (err error) {
namer := NewNamer(namerCfg)
_ = os.Remove(config.GoOutputFile)
_ = os.Remove(config.CppOutputFile)
var idlDriver *IDLDriverGoFile
idlDriver, err = NewIDLDriverGoFile(config.IdlBuildTag, config.IdlPackagePattern, runtime.FuncProcId(config.FuncProcIdOffset))

Check failure on line 90 in public/fffi/compiletime/app.go

View workflow job for this annotation

GitHub Actions / gotestsum

undefined: runtime

Check failure on line 90 in public/fffi/compiletime/app.go

View workflow job for this annotation

GitHub Actions / gotestsum

undefined: runtime

Check failure on line 90 in public/fffi/compiletime/app.go

View workflow job for this annotation

GitHub Actions / lint

undefined: runtime (typecheck)
if err != nil {
err = eh.Errorf("unable to process IDL file: %w", err)
return
}
err = generateBackendCode(idlDriver, config, namer)
if err != nil {
err = eh.Errorf("unable to generate backend code: %w", err)
return
}
err = generateFrontendCode(idlDriver, config, namer)
if err != nil {
err = eh.Errorf("unable to generate frontend code: %w", err)
return
}
return
}
func NewCommand(cfg *Config, namerCfg *NamerConfig) *cli.Command {
if cfg == nil {
cfg = &Config{
IdlBuildTag: "fffi_idl_code",
IdlPackagePattern: "",
GoOutputFile: "",
CppOutputFile: "",
FuncProcIdOffset: 0,
validated: false,
nValidationMessages: 0,
}
}
if namerCfg == nil {
namerCfg = &NamerConfig{
RuneCppType: "rune_t",
}
}
return &cli.Command{
Name: "generateFffiCode",
Flags: append(cfg.ToCliFlags(config.IdentityNameTransf, config.IdentityNameTransf), namerCfg.ToCliFlags(config.IdentityNameTransf, config.IdentityNameTransf)...),
Action: func(context *cli.Context) error {
nMessages := cfg.FromContext(config.IdentityNameTransf, context)
if nMessages > 0 {
return eb.Build().Int("nMessages", nMessages).Errorf("unable to compose config")
}
nMessages = namerCfg.FromContext(config.IdentityNameTransf, context)
if nMessages > 0 {
return eb.Build().Int("nMessages", nMessages).Errorf("unable to compose namer config")
}
return mainE(cfg, namerCfg)
},
}
}
Loading

0 comments on commit 06bce72

Please sign in to comment.