p4lang / pna Goto Github PK
View Code? Open in Web Editor NEWPortable NIC Architecture
License: Apache License 2.0
Portable NIC Architecture
License: Apache License 2.0
One way to implement port-to-port traffic in PNA is as shown in this figure: https://github.com/p4lang/pna/blob/main/figs/path-port-to-port-host-loopback.png
In particular, note that in that figure, the packet does not use the 'host loopback' path shown in the figure, but instead goes through one of the hosts and turns around there.
Further note that this host need not be on the other side of some PCIe pins from the NIC, but would more likely be implemented in most NICs by turning the packet around inside of a "host" that is some array of RISC CPU cores plus memory that is inside of the NIC implementation itself.
The answer might depend upon whether such a port-to-port packet should go through message processing as shown in the figure linked above, or we think it should NOT do that. If it should NOT, then the host loopback arrow is the better representation of how the packet is processed.
If we think that such a packet should always go through message processing (twice, once in each direction), then we could consider simplifying PNA slightly by eliminating the 'host loopback' arrow from the spec.
This is sometimes specified as a required hash function for implementing Receive Side Scaling by some NIC customers/driver developers.
There's very likely a hardware-centric reason as to why the restrictions specified in:
https://p4.org/p4-spec/docs/pna-working-draft-html-version.html#sec-extern-restrictions
exist. If someone could educate me on what that is, I would appreciate it.
Of course in s/ware it doesnt make sense to have such restrictions - but to write a "portable P4 program" we'll need to adhere to specified restrictions.
Before publishing, let's set up the fonts to be the same as for the P4_16 specification. I can do this, but I don't want to break people's build while it's being developed.
Mentioned briefly during 2022-Oct-10 meeting.
Champion: Mahesh Shirshyad
If Yes,
The entries in the 'add-on-miss' table are populated by 'data-plane'. So the table entries will not be available(not known) to the control plane. The life(age) time of these entries expected to be short.
How do we intended to use the 'pna_direct_counter' and 'pna_direct_meter' table properties [output statistics] from the control plane for 'add-on-miss' table ?
Is there known use case to for this type of table combination ?
If No,
Shall we add the below statement in the PNA specification,
"Need not support both 'add_on_miss' and 'pna_sirect_counter'/'pna_direct_meter' properties on the same table."
I am trying to use p4c-dpdk to compile the codes in the examples
folder, and found that the extern send_to_vport
and type VportID_t
are used, which are no longer available in the pna.p4.
I suppose they are depreciated?
and similarly not from FROM_HOST to TO_HOST.
Hardware reasons involving flow control / credits/etc. are the primary reason.
Currently in PNA, a counter extern is defined as the following:
enum PNA_CounterType_t {
PACKETS,
BYTES,
PACKETS_AND_BYTES
}
extern Counter<W, S> {
Counter(bit<32> n_counters, PNA_CounterType_t type);
void count(in S index, @optional in bit<32> increment);
}
When type == PNA_CounterType_t.PACKETS_AND_BYTES
, the counter is used to count both packets and bytes. The way it works (as I understand) is that some bits of the counter works as the packets counter, while the rest of the bits work as the bytes counter. The issue here is that we can only specify the total width of the combined counter through W
, but not the 2 counters separately.
Would it be a good idea to enable setting packets and bytes counter widths separately?
Here is a PR to address this same question for the PSA specification in one particular way, which we should review and think about whether we want something similar for PNA, too: p4lang/p4-spec#1026
This was discussed a fair amount around Jan 2021, and there are some notes on at least some ideas on how this could be done from that time in the meeting notes from then.
Prerequisite: Thiss PR or something similar is merged in: #53
Action: Create an example program that demonstrates all of the pieces of a working demonstration of IPsec encryption, and another for IPsec decryption (or a single program that demonstrates both in the same program is also fine).
It does not need to be "production worthy", i.e. it need not include a bunch of other features such as L2 and L3 forwarding, etc., but it should cover all of these aspects of IPsec:
An example is at:
pna/examples/include/crypto-accelerator.p4
Line 100 in 8fa3993
void set_auth_data_offset<T>(in T offset);
The type checking for this will be done in the backend rather than the front-end.
It would be useful for these type T would be a static type (say bit<32>) which will reduce how much type checking needs to be done in the backend.
If we really want to use generics here, it would be useful to explain what types are valid in the header file. E.g say all bit<N>
, int<N>
types, and serializable enums. (yes it might be obvious but sometimes being explicit is makes it less ambiguous as say you have a struct which is just wrapping an bit type).
In PR#92, I said:
Suggest that the text talks about how entries can be explicitly deleted (not via timeout but on seeing e.g. a RST packet), and briefly describe the semantics of
ExpireTimeProfileId_t
.
It would be nice to have an example for the "Tables with add-on-miss capability" section since I am bit confused when reading the section.
I am filing this issue to remind myself to submit a pull request for this which I will be doing before the next PNA meeting on Monday.
This was first discussed in a public PNA meeting on 2022-Jun-13.
The original motivation for the new proposed externs is to have constructs that are in some ways similar to P4 tables, but:
(a) their lookup
method can be called from inside the body of a P4 action. The P4_16 language spec explicitly disallows table_name.apply()
calls within P4 action calls today, and always has since version 1.0.
(b) they simply "return a value" rather than execute an arbitrary P4 action.
Attached is a P4_16 source file pna_extern_defns.p4.txt that defines the extern objects. Something like this would be added to the pna.p4 #include file if this proposal goes forward.
Below is an edited version of a file that I used to explain the new extern's behavior in the first meeting discussing it, plus some notes of discussion we had during that meeting.
The originally proposed name of ExactMap was ExactMatchLookupTable.
Not only does this seem too long, but putting "table" anywhere in its
name makes it more confusing to talk about, since it is not a P4
table (despite the similarities). ExactMap was proposed as a shorter
name that avoids the "table" word confusion.
Here are some examples of how to instantiate and call the originally
proposed extern object originally proposed, as defined in the file
pna_extern_defns.p4:
// Example of ExactMap with type bit<W> for key type and return
// value type.
// instantiation of an instance named `emap1`, in a control before
// the apply { ... } block
ExactMap<bit<8>, bit<16>, _>(
size = 128,
default_value = 42) // default value returned for all other keys
emap1;
bit<16> v;
apply {
// ... other code here ...
v = emap1.lookup({hdr.ipv4.protocol);
// A target _might_ support multiple lookup() operations on
// the same instance per packet, or it might limit the number
// of lookup() operations to 1 per packet, 2 per packet, etc.
// That is a target-specific decision. The most portable P4
// code would limit itself to 1 lookup operation per instance,
// per packet, just as P4 developers do today for P4 table
// apply() calls.
//w = emap1.lookup(hdr.ipv4.srcAddr[7:0]);
// ... more other code here ...
}
For the purposes of understanding the behavior of the code above, it
should be the same as the code below, which does not use the new
extern.
// Code that behaves the same as the above, and has a very similar
// control plane API
bit<16> v;
action emap1_set_value (bit<16> val) { v = val; }
table emap1_table {
key = { hdr.ipv4.protocol : exact; }
actions = { emap1_set_value; }
size = 128;
default_action = emap1_set_value(42);
}
apply {
// ... other code here ...
emap1_table.apply();
// ... more other code here ...
}
The example below is nearly the same as the first one above, but it
uses a struct type for the key type. This is a straightforward way in
existing P4_16 to have "multiple fields" in such an extern as the
lookup key.
Independently from the key, the return value type could also be a
struct type. The example below uses a struct type for the return
value, as well as for the key.
// Example of ExactMap with struct types for key type and return
// value type.
struct MyKeyType1_t {
bit<8> f1;
bit<16> f2;
}
struct MyValType1_t {
bit<1> valid;
bit<16> foo;
}
MyValType1_t v;
ExactMap<MyKeyType1_t, MyValType1_t, _>(
size = 1024,
default_value = {valid = 0, foo = 0}) // default value returned for all other keys
emap2;
apply {
// ...
v = emap2.lookup({hdr.ipv4.protocol, hdr.ipv4.srcAddr[15:0]});
}
One advantage of using struct types for key types is that it gives a
clear choice for what to use for "key field names" in a table-like
control plane API when adding/deleting entries to this extern --
i.e. use the names of the struct members, of the struct that is type
K.
Note that P4Runtime API 1.x does not support struct types as table
keys today. It could be extended, of course, but not there now.
Proposal here would be to use field names f1, f2 for emap2 as key
fields in control plane API of emap2.
TODO: Would tuples have any advantages over structs for emap2
example? One potential disadvantage vs struct is that struct
members have names that control plane API could use for emap2,
but tuple members do not have names.
Vlad: Note that for TernaryMap, the type of a key field int should
have a mask with type bit, probably, not int.
Thomas: This seems fairly target-specific. Why not go to the P4_16
language spec and make a separate method?
e.g. table_name.lookup(key_expr)
, as shown in sample code snippet
below?
// Thomas Calvert: Why not make this look more like an existing P4
// table, but perhaps with different table properties to
// distinguish it?
bit<16> v;
table emap3 {
key = { hdr.ipv4.protocol : exact; }
size = 128;
value_type = bit<16>; // Andy: I am making up possible syntax here on the fly
default_value = 42;
// actions table property is NOT ALLOWED for this variant of
// table, because value_type table property is defined.
}
apply {
// ...
v = emap3.lookup();
// ...
}
Alan Lo: Is there a way to make the above look more like other
P4 table definitions?
Perhaps using the 'implementation' table property, as shown in
code snippet below.
table emap4 {
key = { hdr.ipv4.protocol : exact; }
size = 128;
implementation = ValueReturner; // making up proposed name `ValueReturner` on the fly during discussion here
// TODO what the other table properties would be, and what the
// definition of the ValueReturner extern is.
}
Andy is summarizing in his (Andy's) own words a week after Vlad said
something like the following near the end of the meeting. Any
differences between this and what Vlad actually said are Andy's
fault.
pna_extern_defns.p4.txt
This exists in the v1model.p4 include file, here: https://github.com/p4lang/p4c/blob/main/p4include/v1model.p4#L705-L711
You can find a few examples of its use in this v1model architecture program that runs on BMv2 simple_switch/simple_switch_grpc here: https://github.com/jafingerhut/p4-guide/blob/7b60e42d8aa3e7afe11d630d7f3357bff3064bb9/ptf-tests/registeraccess/registeraccess.p4
Like assert and assume (see #65 ) it is useful for testing and development on software switch environments, and would typically be treated as no-op when compiling to hardware targets.
See the example program attached to this comment: p4lang/p4-spec#1262 (comment)
Radostin asked the question about resubmit operation during 2022-Oct-17 PNA meeting.
Andy's best guess: PNA will support clone and recirculate operations, and some targets may support a "loopback" operation to change the direction of a packet. However, resubmit seems of low utility, given that recirculate exists and lets the developer modify the packet, and user-defined metadata, and preserve both kinds of changes for the next pass of processing.
If we want recirculate to preserve all user-defined metadata that was modified during the pass through parser + mainControl + mainDeparser where the recirculate operation was invoked, we should ensure that this is documented explicitly in the specification.
Raised in this comment on another PR: #91 (comment)
Anyone should feel free to write a PR adding whatever is desired there.
While reading pna.p4 I noticed that drop_packet() extern function is not defined in the spec itself. This has to be an oversight. It is referenced in the spec once: The effects of mirror_packet() calls are independent of calls to drop_packet() and send_to_port().
But never defined what it does and what it can do.
The header file has:
// The following extern functions are "forwarding" functions -- they
// all set the destination of the packet. Calling one of them
// overwrites and replaces the effect of any earlier call to any of
// the functions in this set. Only the last one executed will
// actually take effect for the packet.
// + drop_packet
// + send_to_port
// drop_packet() - Cause the packet to be dropped when it finishes
// completing the main control.
//
// Invoking drop_packet() is supported only within the main control.
extern void drop_packet();
The intent is that the PreControl is executed only for net-to-host packets, not for host-to-net packets.
Its output determines whether the packet should immediately recirculate, immediate meaning that the MainControl and MainDeparser are NOT executed in this pass, but are skipped. If this is not done, then the MainControl and MainDeparser are executed for the packet, with the MainControl determining whether the packet is recirculated.
TBD whether it should be possible for the MainControl to recirculate the packet and use the net-to-host inline extern -- one reasonable proposal would be that such recirculated packets cannot use the net-to-host inline extern.
There are only a couple that remain in version 0.7, but they should be removed.
So far PNA assumes the data plane supports a timer, but a timer is not defined as an extern. If not defined, how do implementations know which control blocks are allowed to use the timer extern? Or the timer can be used anywhere in the P4 program?
Currently if we have a table T1 that uses this combination of features:
add_on_miss = true;
in table propertiesidle_timeout_with_auto_delete = true;
in table propertiesadd_entry
used to add entries to the table in the data planeThen T1 seems to be a very useful kind of table. The data plane can add entries without the control plane having to do so, presumably at very high rate in the data plane, and the control plane need not be bothered to add such table entries.
Similarly, the data plane will auto-delete entries that have expired, without the control plane having to bother to do so. Presumably high quality PNA implementations would support as high a rate for auto-deleting expired entries as it supports for adding new entries in the data plane, at least long term if not over arbitrary duration time intervals.
The question raised by this issue is whether we wish to expect PNA implementations to support the following slightly different combination of features for a table (which I will call T2 for an example name):
add_on_miss = true;
in table propertiesidle_timeout_with_auto_delete = false;
in table properties, either explicitly, or implicitly because no such table property is mentioned in the definition of the table (the default value of this property is false if not explicitly given, according to current plans)psa_idle_timeout = true;
in table propertiesadd_entry
used to add entries to the table in the data planeT2 is similar to T1 in that the data plane can add entries, without the control plane being required to do so.
T2 is different than T1 in that the data plane WILL NOT auto-delete expired entries. Instead it will notify the control plane of expired entries, each notification containing some identifying information about the key of the expired entry. It is up to the control plane to decide what to do about such notifications. For example, the control plane writer could choose to delete every entry for which it receives such an idle timeout notification as soon as possible. Another control plane writer could choose to perform some additional sanity checks on the entry, and delete some such entries quickly, but leave other entries in table T2 for an arbitrarily long period of time.
The main reason I ask is that for such a table, the control plane now has a potential burden to try to either:
Is this combination worth supporting? The behavior seems clear to define, but the usefulness seems questionable.
It is expected that some PNA implementations will have on-chip caches between the packet processing pipeline and large tables stored in off-chip DRAM. Some will probably enable packets that hit in such a cache to "pass up" earlier arriving packets that experienced a miss in the cache, and are still waiting for the data to be fetched on-chip from DRAM, for higher packet processing throughput.
Typically a P4 developer would NOT want this to happen for packets in the same "flow", but given the programmable nature of P4-programmable devices, it is likely that the definition of a "flow" is best left to the P4 developer to define, too, for the purposes of such packet ordering guarantees, versus which packets may complete processing out of order.
There should be some mention of this somewhere in the PNA spec, I suspect, or if not in the PNA spec, then in the documentation of any PNA device vendors whose devices have such behavior.
This issue was created in response to a comment on a different PNA issue here: #92 (comment) If there is discussion of this topic in the PNA spec, it should address the possible interaction with add-on-miss tables explicitly.
There is a Google doc describing several proposals here: https://docs.google.com/document/d/1oGlOpgIgUHxY4ytij1uwBrbwWdCbuyn5joFYH--F0M8
Timeline: Ideas first proposed and discussed during 2021-Nov-29 PNA meeting.
It was shared with everyone else in PNA work group on 2021-Dec-13.
As of 2021-Dec-28 there are no comments by others on it yet. Please do read and comment on it if you are interested in adding those capabilities to the P4 language spec, and/or an architecture like PNA.
While it is definitely valuable to have comments in the definition of the extern, it would also be useful to give some text in the body of the PNA specification, in its own section, describing the basic idea of this extern, when and how its methods should be called, and what their effect is.
@pbhide Is this something you would be willing to write? Or anyone you can suggest that you can persuade to volunteer for the task?
@pbhide This line has incorrect IP protocol for ESP. Should be decimal 50, not hex 0x50: https://github.com/p4lang/pna/blob/main/examples/ipsec-acc.p4#L34
I have created this issue to record some details about support for if
statements on some open source P4 targets, for reference to the P4 community.
Since creating this issue, I have copied the text that was here to my public p4-guide repository, and added more details to it than were ever recorded directly in this issue's text. Here is a link to that article: https://github.com/jafingerhut/p4-guide/blob/master/docs/if-statements.md
Here is a proposed change to the PSA spec that answers the question for PSA. We should consider whether we want to document a similar restriction for PNA spec: p4lang/p4-spec#1027
The current PNA says that the main control processes a mingled stream of packets, some from network-to-host, others from host-to-network, in the same P4 control, each with their direction identified via a standard metadata direction
input field.
Regardless of whether we keep this, or have separate P4 controls for network-to-host and host-to-network directions, but both of those controls can make calls to 'shared' controls that contain tables, there will be tables that can be apply'd by packets being processed in both directions.
There are several packet processing use cases that require either a table that can be apply'd on packets in both directions, or some communication and cooperation between pairs of tables. The current PNA architecture is based upon the idea that tables that are apply'd by packets in both directions of traffic flow through a NIC are useful things that should be supported explicitly.
As long as such tables are supported in PNA by one of the mechanisms described above, or a different mechanism, it seems to be a useful adjective to define explicitly in the PNA specification. Here is a first cut at such definitions:
A table is bidirectional if it can be apply'd sometimes by packets in net-to-host direction, and sometimes by packets in the host-to-net direction.
A table is unidirectional if it can only be apply'd by packets in one of those two directions, never the other.
Given the current P4_16 language specification and all of the ways that PNA code might be organized described above, it is possible to determine from static analysis of a P4 program whether a table is bidirectional or unidirectional. This can be done by examining the complete conditional expression that qualifies apply() statements on the table. If that condition implies that the packet's direction is always net-to-host, or always host-to-net, then the table is unidirectional. If the condition is sometimes true for host-to-net packets, and sometimes true for net-to-host packets, then the table is bidirectional.
We could imagine creating an annotation indicating what the developer thinks each table should be, and have the compiler give an error if that annotation is statically determined by the compiler to be incorrect, but it is not obvious that this is a very valuable kind of consistency/lint type of check in a P4 program. I mention it only because there should be no need for such an annotation, and even if there were one, the compiler should be able to determine what the annotation should be, given the contents of the rest of the program.
If a bidirectional table has direct resources, e.g. direct counter, direct meter, direct register idle_timeout timer state, or data plane add-on-miss functionality, it is far easier to implement it as a single physical table in hardware. If one attempted to implement such a bidrectional table as two unidirectional tables in hardware, it would lead to the same kinds of potential inconsistencies between the state of the two hardware tables that one finds in switch architectures with such tables, and multiple independent pipelines. See https://p4.org/p4-spec/docs/PSA-v1.1.0.html#appendix-multi-pipeline-psa-devices for some discussion of that topic.
The main issue to be addressed in such a section (perhaps an appendix) is: Does the hardware target support applying a single "physical" P4 table for both net-to-host and host-to-net packets.
My current thinking on this topic is that the answer should be "yes". If the answer is "no", then such a target is probably not a good one for the PNA architecture.
Why? Because for a feature like TCP connection tracking, we want the ability to write in P4 code a table that:
(a) is apply'd for packets in both directions, and
(b) either or both directions of processing packets by that table in the data plane can cause modifications to the state of the entry matched, without the control plane making the modification
Examples of tables having entries modified by having apply() called on them are:
idle_timeout=true
, where apply()
on that table causes some state associated with the entry to indicate that it was recently matched, and therefore should not time out until its full configured timeout interval has elapsedapply()
call being performed.In any of these cases (or any combination of them for the same bidirectional table that is supported by a target device), trying to implement a bidirectional table with two physical tables in the target device, one for each direction, seems extraordinarily difficult, akin to implementing hardware cache coherency mechanisms in a multi-core general purpose CPU.
It seems far more straightforward to implement such behavior correctly by having a single table that handles the total packet rate of both net-to-host and host-to-net packets, and processes each packet logically "one at a time" (perhaps with pipelining optimizations, of course). The lower packet rate of NICs versus typical switch ASICs makes this far more feasible to do in a NIC than in most switch ASICs.
They have recently been added to the PSA include file with this commit: https://github.com/p4lang/p4-spec/blob/main/p4-16/psa/psa.p4#L340-L403
Do we want these added to pna.p4 include file as well? They can be very useful in testing of P4 code on software-based devices, e.g. BMv2 and DPDK. Typically they would be compiled as no-ops on hardware targets.
Reference: p4lang/p4-spec#1010
psa.p4 and pna.p4 both have a section in them today that the PSA authors have always considered to be a portion that vendors SHOULD modify and customize for their target device. That is the part that defines the data plane bit widths of a small collection of types such as PortId_t and a few others.
Here are the relevant lines of the psa.p4 include file: https://github.com/p4lang/p4-spec/blob/main/p4-16/psa/psa.p4#L40-L60
There are similar sections in the pna.p4 include file that were originally copies from psa.p4, then modified to add a few types and maybe to remove a few types.
Why? Because P4 source code is quite explicit when it comes to things like bit widths of types, and it seems about a 0% chance that every target device will have the precise same bit width for these types of things. Even two target devices made by the same vendor in different years seem likely to change in at least some of those bit widths.
There are certainly other ways to address this without having a section of the pna.p4 include file that target device vendors are expected to customize, but this is at least one way.
Another possible thing that seems likely that a vendor might want to customize would be to add vendor- or target-specific annotations to some of the definitions in pna.p4. We could perhaps try to combine the union of what all vendors want in a standard public pna.p4 include file, but then merging updates into that file becomes a hindrance to vendors releasing their software development tools.
The purpose of this issue is (a) to verify that vendor-specific and/or target-specific portions of the pna.p4 include file seem technically reasonable, and (b) if yes, create some documentation that makes it clear to vendors and users of PNA devices exactly what parts of the pna.p4 include file are expected to differ from target to target, vs. which parts we expect to remain the same.
This is an informational issue on this repository, intended to be left open for as long as it is useful, and for sending people a link to when they want to know about this topic. Feel free to edit this comment as new information comes to light. This should be up to date as of 2023-Mar (originally written 2022-Oct).
BMv2:
DPDK:
The "inline externs" in the top level PNA diagram are in a way kind of misnamed, from a strict P4 language specification perspective.
An extern in P4 is either a function, or an object with methods. Such functions and methods are all intended to be called from a developer's P4 code, and in some cases they return useful values (some have return type void).
The blocks that do encryption and decryption in PNA are more like the packet buffer/traffic manager in PSA -- they receive packets + metadata fields, and then process those packets in a way that can be at least partially controlled by the values of those metadata fields. Later they send out packets (perhaps modified) plus metadata (derived from the input metadata, the packet contents, and perhaps state internal to the traffic manager, etc.).
I think we should have a different name for such things in general in P4 architectures, preferably used consistently across P4 architectures.
Possibilities:
I am open to other names, but preferably without "extern" anywhere in the name, for the reasons mentioned above. I really think the name should not be confused with "extern" as used in the P4_16 language specification, which has a precise technical meaning already.
Comparing PNA: https://p4.org/p4-spec/docs/pna-working-draft-html-version.html#sec-programmable-blocks
vs PSA: https://p4.org/p4-spec/docs/PSA.html#sec-programmable-blocks ..
The deparser on PSA indicates headers are read/write whereas on PNA it is read-only.
We have a program that need to update ipv4 checksum on the deparser. While computing the checksum is allowed on the deparser (per https://p4.org/p4-spec/docs/pna-working-draft-html-version.html#sec-extern-restrictions), updating the computed value on a header is not. Is this the intended behavior?
See
https://www.iana.org/assignments/ipv6-multicast-addresses/ipv6-multicast-addresses.xhtml#link-local
The nic could send out an RA message, as specified in rfc4861, to a link-local multicast address. If the nic has two network ports the RA could need replication to both ports.
To snoop dhcpv6, listening on dhcpv6 link-local multicast address in required.
Should we get to this level of detail in the spec? If IPv6 is supported by the nic, it is mandatory to support rfc4861 and rfc4862.
The original packet can be recirculated using the recirculate()
extern in #114.
How about any mirrored copies, can they be recirculated? We may consider adding a recirculation flag/id in the MirrorSession
(which itself is yet to be defined in the code, having only some description in the text § 6.8).
I am guessing the intent is that these methods take parameter values that are offsets from the beginning of the packet, in units of bytes? If so, it would be good to document them that way explicitly:
void set_auth_data_offset<T>(in T offset);
void set_icv_offset<T>(in T offset);
void set_payload_len<T>(in T len);
If that is correct, then is the intent that these offsets are for the packet:
My guess is "as it leaves the deparser" is easier for the offload engine to deal with, since then it does not need to have special logic to deal with changes in offsets during deparsing, e.g. because new headers are added, and/or some existing headers are removed.
In any case, documenting this explicitly would be good.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.