Skip to content

Instantly share code, notes, and snippets.

@bendk
Created August 4, 2021 14:41
Show Gist options
  • Save bendk/7a46ffb6cea0f41947bebe663a8ccbab to your computer and use it in GitHub Desktop.
Save bendk/7a46ffb6cea0f41947bebe663a8ccbab to your computer and use it in GitHub Desktop.
# This code comes from taking James' of components and composition and trying to run the other way
# with it. Instead of (maybe in addition to) creating composible components in uniffi_bindgen, what if we
# tried to write them in the target language?
#
# The basic idea would be:
# - Define a ViaFFI interface
# - Map Type enums to objects that implement the interface for that type. How this works will
# depend on the target language:
# - For python, we could setup a dict that maps the canonical type name to an object
# - For Rust, we could map the Type enum to a struct that implements the ViaFFi trait (I'm
# working on that in my current PR)
# - Since we have a well-defined interface and a way to map types to implementations of that
# interface, then it's easy to generate lower/lift/read/write expressions for any type.
#
# It seems like a nice and simple system to me and I think it would help with a few of our current
# issues:
# - Importing types from other UDLs
# - Custom types that adapt other ViaFFI implementations
# Here's some dreamcode for how the bindings might be generated in python
#
# Todo:
# - Improve the way ViaFFI impls are referenced |t seems very easy with this code to try to use a ViaFFIMap value
# before it's been generated.
# - Add system for types to specify modules to import (Like othercrate and json below)
class ViaFFI:
def lift(self, val): pass
def read(self, buf_stream): pass
def lower(self, val): pass
def write(self, val, buf_builder): pass
# Map canonnical_type_name to objects that implement ViaFFI
ViaFFIMap = { }
# Primitives are easy to define
class ViaFFIPrimitive(ViaFFI):
def __init__(self, size, format):
self.size = size
self.format
def read(self, buf_stream):
return buf_stream.unpack_from(self.size, self.format)
...
ViaFFIMap["u8"] = ViaFFIPrimitive(1, ">b")
# Optional can be defined using composition
class ViaFFIOptional(ViaFFI):
def __init__(self, inner)
self.inner = inner
def read(self, buf_stream):
flag = buf.unpack_from(1, ">b")
if flag == 0:
return None
elif flag == 1:
return inner.read(buf_stream)
else:
raise InternalError("Unexpected flag byte for Optional")
... more methods
ViaFFIMap["OptionalString"] = ViaFFIOptional(ViaFFIMap["String"])
# Record could also be defined using composition.
ViaFFIMap["RecordFoo"] = ViaFFIRecord([
('field1', ViaFFIMap['String']),
('field2', ViaFFIMap['i32']),
('field3', ViaFFIMap['OptionalString']),
])
# You could reference types from other crates
ViaFFIMap["ImportedType"] = othercrate.ViaFFIMap["Type"]
ViaFFIMap["OptionalImportedType"] = Optional(ViaFFIMap["ImportedType"])
ViaFFIMap["RecordWithImportedType"] ViaFFIRecord([
('field1', ViaFFIMap['String']),
('field2', ViaFFIMap['ImportedType']),
])
# You could implement custom types. Imagine this code coming from a custom
# template -- maybe from a separate extension crate.
class ViaFFIJsonValue(ViaFFI):
def read(self, buf_stream):
return json.loads(ViaFFIMap["String"].read(buf_stream))
ViaFFIMap["CustomJsonValue"] = ViaFFIJsonValue()
# CustomJsonValue could now be nested in an Option, Record, whatever.
# Functions would look something like this:
def call_constructor(arg1, arg2):
ViaFFIMap["MyInterface"].lift(
_UniFFILib.func_name(
ViaFFIMap["String"].lower(arg1),
ViaFFIMap["MyRecord"].lower(arg2)
))
@bendk
Copy link
Author

bendk commented Aug 5, 2021

* Runtime overhead in the generated code

I've been wondering about this one too. There are a couple other methods I've been wondering about:

  • You could index each type. Then instead of ViaFfiMap you have ViaFfiArray. I think this might be zero cost, since the lookups are going to be in the form of static_array[const_index]
  • You could create a set of classes that only have static methods. This is close to how Rust does it, but it kind of seems weird to define a class for each Type instance -- including types that wrap other types like Option.
  • I think each binding might want to handle this differently. Each language has different features that it's going to want to use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment