-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: import framebased-foreign-function-interface (go part)
- Loading branch information
1 parent
6130367
commit 06bce72
Showing
19 changed files
with
2,035 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}` | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
package 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 GitHub Actions / gotestsum
Check failure on line 90 in public/fffi/compiletime/app.go GitHub Actions / gotestsum
|
||
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) | ||
}, | ||
} | ||
} |
Oops, something went wrong.