diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index 29039010b4..df29832605 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -189,8 +189,19 @@ protected void deserialize( result.pr("lf_set(" + receiveRef + ", " + value + ");"); } } - case PROTO -> throw new UnsupportedOperationException( - "Protobuf serialization is not supported yet."); + case PROTO -> { + // In C, the type of the action is uint8*, so it will be a token type. + value = action.getName() + "->value"; + var length = action.getName() + "->length"; + var portType = types.getTargetType(ASTUtils.getInferredType(((Port) receivingPort.getVariable()))); + var prefix = portType.toLowerCase().replace("*", ""); + // FIXME: Protobufs does weird things converting cammel case to snake case and vice versa. + // The following "toLowerCase()" call will only work for single-word types. + result.pr(portType + " unpacked = " + prefix + "__unpack(NULL, " + length + ", " + value + ");"); + // FIXME: Should generate and set destructor and copy constructor for this type. + // See: https://www.lf-lang.org/docs/handbook/target-language-details?target=c#dynamically-allocated-data + result.pr("lf_set(" + receiveRef + ", unpacked);"); + } case ROS2 -> { var portType = ASTUtils.getInferredType(((Port) receivingPort.getVariable())); var portTypeStr = types.getTargetType(portType); @@ -408,8 +419,18 @@ protected void serializeAndSend( result.pr(sendingFunction + "(" + commonArgs + ", " + pointerExpression + ");"); } } - case PROTO -> throw new UnsupportedOperationException( - "Protobuf serialization is not supported yet."); + case PROTO -> { + // FIXME: Protobufs does weird things converting camel case to snake case and vice versa. + // The following "toLowerCase()" call will only work for single-word types. + var targetType = types.getTargetType(type).toLowerCase(); + var prefix = targetType.replace("*", ""); + result.pr("size_t _lf_message_length = " + prefix + "__get_packed_size(" + sendRef + "->value);"); + result.pr("uint8_t* buffer = (uint8_t*)malloc(_lf_message_length);"); + result.pr(prefix + "__pack(" + sendRef + "->value, buffer);"); + result.pr(sendingFunction + "(" + commonArgs + ", buffer);"); + } + // throw new UnsupportedOperationException( + // "Protobuf serialization is not supported yet."); case ROS2 -> { var typeStr = types.getTargetType(type); if (CUtil.isTokenType(type, types)) { diff --git a/core/src/main/java/org/lflang/generator/c/CGenerator.java b/core/src/main/java/org/lflang/generator/c/CGenerator.java index dd2fc494d5..e4ed322b55 100644 --- a/core/src/main/java/org/lflang/generator/c/CGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CGenerator.java @@ -34,7 +34,6 @@ import static org.lflang.ast.ASTUtils.toText; import static org.lflang.util.StringUtil.addDoubleQuotes; -import com.google.common.base.Objects; import com.google.common.collect.Iterables; import java.io.File; import java.io.IOException; @@ -44,6 +43,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -1440,7 +1440,7 @@ private void generateStartTimeStep(ReactorInstance instance) { temp.pr("// Add port " + port.getFullName() + " to array of is_present fields."); - if (!Objects.equal(port.getParent(), instance)) { + if (!port.getParent().equals(instance)) { // The port belongs to contained reactor, so we also have // iterate over the instance bank members. temp.startScopedBlock(); @@ -1474,7 +1474,7 @@ private void generateStartTimeStep(ReactorInstance instance) { enclaveInfo.numIsPresentFields += port.getWidth() * port.getParent().getTotalWidth(); - if (!Objects.equal(port.getParent(), instance)) { + if (!port.getParent().equals(instance)) { temp.pr("count++;"); temp.endScopedBlock(); temp.endScopedBlock(); @@ -1602,25 +1602,31 @@ private void generateTimerInitializations(ReactorInstance instance) { *

Run, if possible, the proto-c protocol buffer code generator to produce the required .h and * .c files. * - * @param filename Name of the file to process. + * @param file Path of the .proto file to process. */ - public void processProtoFile(String filename) { + public void processProtoFile(Path file) { + var fileName = file.getFileName().toString(); + var directory = Objects.requireNonNullElse(file.getParent(), ""); var protoc = commandFactory.createCommand( "protoc-c", - List.of("--c_out=" + this.fileConfig.getSrcGenPath(), filename), + List.of( + "--c_out=" + this.fileConfig.getSrcGenPath(), + "--proto_path=" + directory, + fileName), fileConfig.srcPath); if (protoc == null) { messageReporter.nowhere().error("Processing .proto files requires protoc-c >= 1.3.3."); - return; - } - var returnCode = protoc.run(); - if (returnCode == 0) { - var nameSansProto = filename.substring(0, filename.length() - 6); - targetConfig.compileAdditionalSources.add( - fileConfig.getSrcGenPath().resolve(nameSansProto + ".pb-c.c").toString()); } else { - messageReporter.nowhere().error("protoc-c returns error code " + returnCode); + var returnCode = protoc.run(); + if (returnCode == 0) { + messageReporter.nowhere().info("Successfully compiled " + file); + var nameSansProto = fileName.substring(0, fileName.length() - 6); + targetConfig.compileAdditionalSources.add( + fileConfig.getSrcGenPath().resolve(nameSansProto + ".pb-c.c").toString()); + } else { + messageReporter.nowhere().error("protoc-c failed:" + protoc.getErrors()); + } } } @@ -1999,10 +2005,10 @@ protected void setUpGeneralParameters() { } } + /** Iterate over the .proto files specified in the 'proto' target property and compile them. */ protected void handleProtoFiles() { - // Handle .proto files. for (String file : targetConfig.get(ProtobufsProperty.INSTANCE)) { - this.processProtoFile(file); + this.processProtoFile(Path.of(file)); } } @@ -2033,10 +2039,11 @@ protected String generateTopLevelPreambles(Reactor reactor) { .collect(Collectors.toSet()) .forEach(it -> builder.pr(toText(it.getCode()))); for (String file : targetConfig.get(ProtobufsProperty.INSTANCE)) { - var dotIndex = file.lastIndexOf("."); + var fileName = Path.of(file).getFileName().toString(); + var dotIndex = fileName.lastIndexOf("."); var rootFilename = file; if (dotIndex > 0) { - rootFilename = file.substring(0, dotIndex); + rootFilename = fileName.substring(0, dotIndex); } code.pr("#include " + addDoubleQuotes(rootFilename + ".pb-c.h")); builder.pr("#include " + addDoubleQuotes(rootFilename + ".pb-c.h")); diff --git a/core/src/main/java/org/lflang/generator/python/PythonGenerator.java b/core/src/main/java/org/lflang/generator/python/PythonGenerator.java index f043b87acd..018e122275 100644 --- a/core/src/main/java/org/lflang/generator/python/PythonGenerator.java +++ b/core/src/main/java/org/lflang/generator/python/PythonGenerator.java @@ -31,6 +31,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -63,7 +64,6 @@ import org.lflang.target.property.CompilerProperty; import org.lflang.target.property.ProtobufsProperty; import org.lflang.util.FileUtil; -import org.lflang.util.LFCommand; import org.lflang.util.StringUtil; /** @@ -280,7 +280,7 @@ protected String generateTopLevelPreambles(Reactor ignored) { @Override protected void handleProtoFiles() { for (String name : targetConfig.get(ProtobufsProperty.INSTANCE)) { - this.processProtoFile(name); + this.processProtoFile(Path.of(name)); int dotIndex = name.lastIndexOf("."); String rootFilename = dotIndex > 0 ? name.substring(0, dotIndex) : name; pythonPreamble.pr("import " + rootFilename + "_pb2 as " + rootFilename); @@ -288,31 +288,28 @@ protected void handleProtoFiles() { } } - /** - * Process a given .proto file. - * - *

Run, if possible, the proto-c protocol buffer code generator to produce the required .h and - * .c files. - * - * @param filename Name of the file to process. - */ @Override - public void processProtoFile(String filename) { - LFCommand protoc = + public void processProtoFile(Path file) { + var fileName = file.getFileName().toString(); + var directory = Objects.requireNonNullElse(file.getParent(), ""); + var protoc = commandFactory.createCommand( "protoc", - List.of("--python_out=" + fileConfig.getSrcGenPath(), filename), + List.of( + "--python_out=" + this.fileConfig.getSrcGenPath(), + "--proto_path=" + directory, + fileName), fileConfig.srcPath); - if (protoc == null) { - messageReporter.nowhere().error("Processing .proto files requires libprotoc >= 3.6.1"); - return; - } - int returnCode = protoc.run(); - if (returnCode == 0) { - pythonRequiredModules.add("google-api-python-client"); + messageReporter.nowhere().error("Processing .proto files requires protoc-c >= 1.3.3."); } else { - messageReporter.nowhere().error("protoc returns error code " + returnCode); + var returnCode = protoc.run(); + if (returnCode == 0) { + messageReporter.nowhere().info("Successfully compiled " + file); + pythonRequiredModules.add("google-api-python-client"); + } else { + messageReporter.nowhere().error("protoc-c failed:" + protoc.getErrors()); + } } } diff --git a/test/C/src/serialization/ProtoHelloWorld.proto b/test/C/src/serialization/Hello.proto similarity index 63% rename from test/C/src/serialization/ProtoHelloWorld.proto rename to test/C/src/serialization/Hello.proto index 22f7b6dedd..71883f35e0 100644 --- a/test/C/src/serialization/ProtoHelloWorld.proto +++ b/test/C/src/serialization/Hello.proto @@ -2,8 +2,7 @@ syntax = "proto3"; -message ProtoHelloWorld { +message Hello { string name = 1; int32 number = 2; - repeated string sentence = 3; } diff --git a/test/C/src/serialization/PersonProtocolBuffers.lf b/test/C/src/serialization/PersonProtocolBuffers.lf index 193de9193a..f4dc391b9f 100644 --- a/test/C/src/serialization/PersonProtocolBuffers.lf +++ b/test/C/src/serialization/PersonProtocolBuffers.lf @@ -4,18 +4,15 @@ * on the examples at https://github.com/protobuf-c/protobuf-c/wiki/Examples. This example just * packs and unpacks a message. * - * To run this example first install the protocol buffers compiler from - * https://github.com/protocolbuffers/protobuf. It is also available from homebrew on a Mac via + * To run this test, first install the protocol buffers compiler from + * https://github.com/protocolbuffers/protobuf as well as the C plugin which comes from + * https://github.com/protobuf-c/protobuf-c. * - * $ brew install protobuf - * - * Building protobuf from source is slow, so avoid doing that if possible. Next install the C plugin - * for protocol buffers from + * (Building protobuf from source is slow, so avoid doing that if possible.) * - * https://github.com/protobuf-c/protobuf-c - * - * The code generator assumes that executables are installed within the PATH. On a Mac, this is - * typically at /usr/local/bin. + * On Mac, you can install these dependencies via Homebrew: + * $ brew install protobuf + * $ brew install protobuf-c */ target C { protobufs: Person.proto @@ -32,6 +29,7 @@ main reactor { person.email = "eal@berkeley.edu"; // Pack the message into buffer. + person__init(&person); len = person__get_packed_size(&person); buffer = (uint8_t*)malloc(len); person__pack(&person, buffer); diff --git a/test/C/src/serialization/ProtoFederated.lf b/test/C/src/serialization/ProtoFederated.lf new file mode 100644 index 0000000000..40285da7a4 --- /dev/null +++ b/test/C/src/serialization/ProtoFederated.lf @@ -0,0 +1,63 @@ +/** + * This example creates a Protocol Buffer message and passes it to another reactor without packing + * and unpacking. This demonstrates that local communication, within one shared-memory machine, need + * not incur the overhead of packing and unpacking. + * + * To run this example first install the protocol buffers compiler from + * https://github.com/protocolbuffers/protobuf. It is also available from homebrew on a Mac via + * + * $ brew install protobuf + * + * Building protobuf from source is slow, so avoid doing that if possible. Next install the C plugin + * for protocol buffers from + * + * https://github.com/protobuf-c/protobuf-c + * + * The code generator assumes that executables are installed within the PATH. On a Mac, this is + * typically at /usr/local/bin. + */ +target C { + protobufs: [Hello.proto], + timeout: 1 s +} + +reactor SourceProto { + output out: Hello* + + reaction(startup) -> out {= + Hello* value = (Hello*)malloc(sizeof(Hello)); + hello__init(value); + value->name = "Hello World"; + value->number = 42; + lf_set(out, value); + =} +} + +reactor SinkProto { + preamble {= + // FIXME: Ideally, this function would be generated by the tool + // processing the .proto file. + // Destructor to use at the receiving end, which frees the memory used to unpack the message. + void hello_unpacked_destructor(void* hello) { + Hello* cast = (Hello*)hello; + hello__free_unpacked(cast, NULL); + } + // FIXME: Should also provide a copy constructor. + =} + + input in: Hello* + + reaction(startup) in {= + // FIXME: Ideally, this would be automatically generated. + lf_set_destructor(in, hello_unpacked_destructor); + =} + reaction(in) {= + printf("Received: name=\"%s\", number=%d.\n", in->value->name, in->value->number); + =} +} + +federated reactor { + s = new SourceProto() + d = new SinkProto() + s.out -> d.in serializer "proto" +} diff --git a/test/C/src/serialization/ProtoNoPacking.lf b/test/C/src/serialization/ProtoNoPacking.lf index 57b610e337..8a00efeb6a 100644 --- a/test/C/src/serialization/ProtoNoPacking.lf +++ b/test/C/src/serialization/ProtoNoPacking.lf @@ -3,25 +3,22 @@ * and unpacking. This demonstrates that local communication, within one shared-memory machine, need * not incur the overhead of packing and unpacking. * - * To run this example first install the protocol buffers compiler from - * https://github.com/protocolbuffers/protobuf. It is also available from homebrew on a Mac via + * To run this test, first install the protocol buffers compiler from + * https://github.com/protocolbuffers/protobuf as well as the C plugin which comes from + * https://github.com/protobuf-c/protobuf-c. * - * $ brew install protobuf - * - * Building protobuf from source is slow, so avoid doing that if possible. Next install the C plugin - * for protocol buffers from + * (Building protobuf from source is slow, so avoid doing that if possible.) * - * https://github.com/protobuf-c/protobuf-c - * - * The code generator assumes that executables are installed within the PATH. On a Mac, this is - * typically at /usr/local/bin. + * On Mac, you can install these dependencies via Homebrew: + * $ brew install protobuf + * $ brew install protobuf-c */ target C { - protobufs: [ProtoHelloWorld.proto] + protobufs: [Hello.proto] } reactor SourceProto { - output out: ProtoHelloWorld + output out: Hello reaction(startup) -> out {= out->value.name = "Hello World"; @@ -31,7 +28,7 @@ reactor SourceProto { } reactor SinkProto { - input in: ProtoHelloWorld + input in: Hello reaction(in) {= printf("Received: name=\"%s\", number=%d.\n", in->value.name, in->value.number);