Quantcast
Channel: Cadence Functional Verification
Viewing all articles
Browse latest Browse all 653

Extending the e Language with Anonymous Methods

$
0
0

We're happy to have guest blogger Thorsten Dworzak describe how he added anonymous methods to the vlab_util package from Verilab. So here it goes:

Many programming languages like Python, Perl, and Ruby support anonymous methods[1], typically through classes or other constructs representing a block of code. These are useful to construct code by a higher-order method or to be used as arguments by higher-order methods. The e language knows code blocks in (for example) list pseudo-methods and macro definitions, but they are defined statically and cannot be referenced, unlike the aforementioned languages. Using reflection, template structs, and define-as-computed macros, we implemented anonymous methods functionality in the e language, modeled after the corresponding Ruby feature[1,4]. It is licensed under Apache 2.0 and available in the vlab_util package[2].

We actualized this via a struct type that contains a piece of code, and input/output arguments bound to that code (“Proc”). The code is either static or constructed at run-time using a string. Once a handle to a Proc object exists, it can be passed around and called. Execution is done with or without arguments in a value-returning or void context. Let’s have a look at some usage examples before we dig in further.

Example 1

Here is a simple example borrowed from the Ruby class documentation[3]. Two macros have been implemented in vlab_util to create and use Procs. We use the lambda macro to define two value-returning Proc objects that just multiply their integer input by 3 and 5 respectively. We then call the objects a couple of times using the yield macro:

var times3: vlab_proc_s of (uint) = lambda(n: uint) { result = n*3 };
var times5: vlab_proc_s of (uint) = lambda(n: uint) { result = n*5 };  

out (yield times3 with(12));                   // prints “36”
out (yield times5 with(5));                    // prints “25”
out (yield times3 with(yield times5 with (4)); // prints “60”

Or—to make it more generic and close to the Ruby example—we can use a macro to define the Procs:

define <procs_gen_times'exp> "gen_times\(<exp>\)" as {lambda(n: uint)
{ result = n*<exp> }};
...  
times3 = gen_times(3);
times5 = gen_times(5);
...

Implementation

The code snippet embedded in the Proc object can be executed using the built-in specman() command. The difficulty herein is that this command does not inherit the local scope, i.e., the only scope it knows is the global one (sys, global, etc.). But how can we then pass anything into or out if it? We store the Proc arguments and the return value in the utils singleton using the rf_value_holder struct of the Specman reflection interface.

The input/output of a Proc are stored in a container struct like:

struct vlab_proc_container_s {
  !output     : rf_value_holder;
  !input      : list of rf_value_holder;
  !output_rft : rf_type;  

  -- Helper method to store a return value in this container's holder
  set_output(val: untyped) is {
     assert output_rft != NULL;
     output = output_rft.create_holder(val.unsafe())
  };
};

This is then instantiated in sn_util:

extend sn_util {
  package !vlab_proc_container: vlab_proc_container_s;
};

There is only one container object, so we support only atomic Proc operations (no TCMs, etc.).

The Proc Object

The Proc struct is defined as

template struct vlab_proc_s of (<ret'type>=uint) like vlab_proc_base_s

where the only template type of the Proc struct is the return type of the code block. The struct contains information about the anonymous method and can easily be created using the lambda macro (see below). Furthermore, it contains two methods for calling the Proc:

void_call(params: list of rf_value_holder={})
non_void_call(params: list of rf_value_holder={}):<ret'type>

These methods populate the vlab_proc_container, call the specman() command with a call-string, and (optionally) return the return value from the container. The user does not need to call these; they are wrapped by the yield macro.

Before we take a closer look at the macros, let’s look at another example.

Example 2

Imagine you have a sort struct that has a list of strings to sort and a method bubble_sort() to perform the sorting. Now we want to be able to use different compare methods. This can be achieved by passing a Proc object to the bubble_sort() method (and having a default object ready in case the user does not supply one):

my_sorter: sorter_s;
run() is also {
   var my_strings: list of string = {"fox"; "unicorn"; "baboon"};
 
   -- create custom comparison method: sort by string length
  var by_length: vlab_proc_s of (compare_result_t) = lambda(s1: string, s2: string) {
      result = 0; // even
     if str_len(s1) < str_len(s2)      { result = -1; }
      else if str_len(s1) > str_len(s2) { result =  1; }
   };

   my_sorter.strings = my_strings;   // set and print initial list
   print my_sorter.strings;

   my_sorter.bubble_sort(by_length); // use custom sorting
   print my_sorter.strings;

   my_sorter.bubble_sort();          // use default sorting (alphabetically)
   print my_sorter.strings;
};

The bubble_sort() method looks like this:

bubble_sort(compare: vlab_proc_s of (compare_result_t) = lambda(s1: string, s2: string) { <default-comparison code> }) is
{
  ...
  if (yield compare with (strings[idx], strings[idx+1])) { ... }
  ...
};

The default Proc parameter is defined as alphabetical comparison. While the bubble sort progresses, the Proc is called to compare two strings.

After load/test in Specman, this should print

  my_sorter.strings =
0.  "fox"
1.  "unicorn"
2.  "baboon"

  my_sorter.strings =
0.  "fox"
1.  "baboon"
2.  "unicorn"

  my_sorter.strings =
0.  "baboon"
1.  "fox"
2.  "unicorn"

The lambda Macro

This macro (and lambda_str) creates a Proc object and populates it with:

  • The code block (as string); if the code contains the keyword result, it will be marked as value-returning
  • The input argument names, types and default values bound to that code block (as strings)
  • The type of the template (as rf_type and string)

The yield Macro

This macro exists in two versions, as action for non-value-returning contexts, and as expression for value-returning contexts. It iterates over the given arguments, converts them to value-holders, and calls the Proc object. The conversion of the arguments is done as in the following example:

var i: int;
var holder: rf_value_holder = rf_type(i).create_holder(i.unsafe());

Note that the built-in rf_type macro is currently undocumented, so its functionality might change/disappear with future Specman versions.

Retrieving the argument value back from the holder can be done with:

var i: int = holder.get_value().unsafe();

Needless to say, the types of the holder and the variable have to match. This is no problem in our case because that information is captured in the Proc object.

Example 3

For generating code on the fly, we provided the lambda_str macro, which allows us to specify the code block as string, and allows for a very simple implementation of a command-line calculator. It prompts for numerical expressions like e.g. isqrt(3.141*2.0)/1.01 and prints the result until it encounters an empty input.

gets(s:string): string is foreign dynamic C routine gets;
...
run() is also {

  -- pre-allocate some memory (note: not protected against overflow in gets())
  var inp: string = "012345678901234567890123456789";
  var calc: vlab_proc_s of (real); // the empty calculator Proc        

   while TRUE {
     outf("<enter numerical expression, blank to exit>");
     compute gets(inp);
     if not str_empty(inp) {
        calc = lambda_str() append("result=", inp);
        out(yield calc);
     } else {
         out("<exiting>"); break;
     };
  };
};

Here we read user input from the keyboard and use it to create a Proc object that does not take any inputs but returns the result of the expression.

Closing Remarks

The implementation is of course not as powerful as the Ruby one, because closures in Ruby are aware of their local scope and Ruby is not as strongly typed as e. Our implementation comes close to the Ruby lambda though.

This approach should be avoided where performance is an issue, due to the necessity to pass arguments as value-holders.

We think it is most valuable where code can be constructed at run-time or where passing Proc objects is a more elegant way to express intent. We would be happy to see any real-life examples provided by readers.

Thorsten Dworzak, June 2015, Verilab GmbH

References

[1] Wikipedia, The Free Encyclopedia. “Anonymous function”. Wikimedia Foundation, Inc. Web. 11 June 2015. https://en.wikipedia.org/wiki/Anonymous_function.
[2] Atlassian bitbucket “vlab_util”. Verilab Inc. Web. 11 June 2015. https://bitbucket.org/verilab/vlab_util.
[3] Britt, J & Neurogami. "Help and documentation for the Ruby programming language". Web. 11 June 2015. http://ruby-doc.org/core-1.9.3.
[4] Thomas, D. 2012. Programming Ruby 1.9.3. The Pragmatic Programmers, LLC.


Viewing all articles
Browse latest Browse all 653

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>