Home | History | Annotate | Download | only in syscall
      1 #!/usr/bin/env perl
      2 # Copyright 2009 The Go Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style
      4 # license that can be found in the LICENSE file.
      5 
      6 # This program reads a file containing function prototypes
      7 # (like syscall_solaris.go) and generates system call bodies.
      8 # The prototypes are marked by lines beginning with "//sys"
      9 # and read like func declarations if //sys is replaced by func, but:
     10 #	* The parameter lists must give a name for each argument.
     11 #	  This includes return parameters.
     12 #	* The parameter lists must give a type for each argument:
     13 #	  the (x, y, z int) shorthand is not allowed.
     14 #	* If the return parameter is an error number, it must be named err.
     15 #	* If go func name needs to be different than its libc name,
     16 #	* or the function is not in libc, name could be specified
     17 #	* at the end, after "=" sign, like
     18 #	  //sys getsockopt(s int, level int, name int, val uintptr, vallen *_Socklen) (err error) = libsocket.getsockopt
     19 
     20 use strict;
     21 
     22 my $cmdline = "mksyscall_solaris.pl " . join(' ', @ARGV);
     23 my $errors = 0;
     24 my $_32bit = "";
     25 my $tags = "";  # build tags
     26 
     27 binmode STDOUT;
     28 
     29 if($ARGV[0] eq "-b32") {
     30 	$_32bit = "big-endian";
     31 	shift;
     32 } elsif($ARGV[0] eq "-l32") {
     33 	$_32bit = "little-endian";
     34 	shift;
     35 }
     36 if($ARGV[0] eq "-tags") {
     37 	shift;
     38 	$tags = $ARGV[0];
     39 	shift;
     40 }
     41 
     42 if($ARGV[0] =~ /^-/) {
     43 	print STDERR "usage: mksyscall_solaris.pl [-b32 | -l32] [-tags x,y] [file ...]\n";
     44 	exit 1;
     45 }
     46 
     47 sub parseparamlist($) {
     48 	my ($list) = @_;
     49 	$list =~ s/^\s*//;
     50 	$list =~ s/\s*$//;
     51 	if($list eq "") {
     52 		return ();
     53 	}
     54 	return split(/\s*,\s*/, $list);
     55 }
     56 
     57 sub parseparam($) {
     58 	my ($p) = @_;
     59 	if($p !~ /^(\S*) (\S*)$/) {
     60 		print STDERR "$ARGV:$.: malformed parameter: $p\n";
     61 		$errors = 1;
     62 		return ("xx", "int");
     63 	}
     64 	return ($1, $2);
     65 }
     66 
     67 my $package = "";
     68 my $text = "";
     69 my $dynimports = "";
     70 my $linknames = "";
     71 my @vars = ();
     72 while(<>) {
     73 	chomp;
     74 	s/\s+/ /g;
     75 	s/^\s+//;
     76 	s/\s+$//;
     77 	$package = $1 if !$package && /^package (\S+)$/;
     78 	my $nonblock = /^\/\/sysnb /;
     79 	next if !/^\/\/sys / && !$nonblock;
     80 
     81 	my $syscalldot = "";
     82 	$syscalldot = "syscall." if $package ne "syscall";
     83 
     84 	# Line must be of the form
     85 	#	func Open(path string, mode int, perm int) (fd int, err error)
     86 	# Split into name, in params, out params.
     87 	if(!/^\/\/sys(nb)? (\w+)\(([^()]*)\)\s*(?:\(([^()]+)\))?\s*(?:=\s*(?:(\w*)\.)?(\w*))?$/) {
     88 		print STDERR "$ARGV:$.: malformed //sys declaration\n";
     89 		$errors = 1;
     90 		next;
     91 	}
     92 	my ($nb, $func, $in, $out, $modname, $sysname) = ($1, $2, $3, $4, $5, $6);
     93 
     94 	# Split argument lists on comma.
     95 	my @in = parseparamlist($in);
     96 	my @out = parseparamlist($out);
     97 
     98 	# So file name.
     99 	if($modname eq "") {
    100 		$modname = "libc";
    101 	}
    102 
    103 	# System call name.
    104 	if($sysname eq "") {
    105 		$sysname = "$func";
    106 	}
    107 
    108 	# System call pointer variable name.
    109 	my $sysvarname = "libc_${sysname}";
    110 
    111 	my $strconvfunc = "BytePtrFromString";
    112 	my $strconvtype = "*byte";
    113 
    114 	$sysname =~ y/A-Z/a-z/; # All libc functions are lowercase.
    115 
    116 	# Runtime import of function to allow cross-platform builds.
    117 	$dynimports .= "//go:cgo_import_dynamic ${sysvarname} ${sysname} \"$modname.so\"\n";
    118 	# Link symbol to proc address variable.
    119 	$linknames .= "//go:linkname ${sysvarname} ${sysvarname}\n";
    120 	# Library proc address variable.
    121 	push @vars, $sysvarname;
    122 
    123 	# Go function header.
    124 	$out = join(', ', @out);
    125 	if($out ne "") {
    126 		$out = " ($out)";
    127 	}
    128 	if($text ne "") {
    129 		$text .= "\n"
    130 	}
    131 	$text .= sprintf "func %s(%s)%s {\n", $func, join(', ', @in), $out;
    132 
    133 	# Check if err return available
    134 	my $errvar = "";
    135 	foreach my $p (@out) {
    136 		my ($name, $type) = parseparam($p);
    137 		if($type eq "error") {
    138 			$errvar = $name;
    139 			last;
    140 		}
    141 	}
    142 
    143 	# Prepare arguments to Syscall.
    144 	my @args = ();
    145 	my $n = 0;
    146 	foreach my $p (@in) {
    147 		my ($name, $type) = parseparam($p);
    148 		if($type =~ /^\*/) {
    149 			push @args, "uintptr(unsafe.Pointer($name))";
    150 		} elsif($type eq "string" && $errvar ne "") {
    151 			$text .= "\tvar _p$n $strconvtype\n";
    152 			$text .= "\t_p$n, $errvar = $strconvfunc($name)\n";
    153 			$text .= "\tif $errvar != nil {\n\t\treturn\n\t}\n";
    154 			push @args, "uintptr(unsafe.Pointer(_p$n))";
    155 			$n++;
    156 		} elsif($type eq "string") {
    157 			print STDERR "$ARGV:$.: $func uses string arguments, but has no error return\n";
    158 			$text .= "\tvar _p$n $strconvtype\n";
    159 			$text .= "\t_p$n, _ = $strconvfunc($name)\n";
    160 			push @args, "uintptr(unsafe.Pointer(_p$n))";
    161 			$n++;
    162 		} elsif($type =~ /^\[\](.*)/) {
    163 			# Convert slice into pointer, length.
    164 			# Have to be careful not to take address of &a[0] if len == 0:
    165 			# pass nil in that case.
    166 			$text .= "\tvar _p$n *$1\n";
    167 			$text .= "\tif len($name) > 0 {\n\t\t_p$n = \&$name\[0]\n\t}\n";
    168 			push @args, "uintptr(unsafe.Pointer(_p$n))", "uintptr(len($name))";
    169 			$n++;
    170 		} elsif($type eq "int64" && $_32bit ne "") {
    171 			if($_32bit eq "big-endian") {
    172 				push @args, "uintptr($name >> 32)", "uintptr($name)";
    173 			} else {
    174 				push @args, "uintptr($name)", "uintptr($name >> 32)";
    175 			}
    176 		} elsif($type eq "bool") {
    177  			$text .= "\tvar _p$n uint32\n";
    178 			$text .= "\tif $name {\n\t\t_p$n = 1\n\t} else {\n\t\t_p$n = 0\n\t}\n";
    179 			push @args, "uintptr(_p$n)";
    180 			$n++;
    181 		} else {
    182 			push @args, "uintptr($name)";
    183 		}
    184 	}
    185 	my $nargs = @args;
    186 
    187 	# Determine which form to use; pad args with zeros.
    188 	my $asm = "${syscalldot}sysvicall6";
    189 	if ($nonblock) {
    190 		$asm = "${syscalldot}rawSysvicall6";
    191 	}
    192 	if(@args <= 6) {
    193 		while(@args < 6) {
    194 			push @args, "0";
    195 		}
    196 	} else {
    197 		print STDERR "$ARGV:$.: too many arguments to system call\n";
    198 	}
    199 
    200 	# Actual call.
    201 	my $args = join(', ', @args);
    202 	my $call = "$asm(uintptr(unsafe.Pointer(&$sysvarname)), $nargs, $args)";
    203 
    204 	# Assign return values.
    205 	my $body = "";
    206 	my $failexpr = "";
    207 	my @ret = ("_", "_", "_");
    208 	my @pout= ();
    209 	my $do_errno = 0;
    210 	for(my $i=0; $i<@out; $i++) {
    211 		my $p = $out[$i];
    212 		my ($name, $type) = parseparam($p);
    213 		my $reg = "";
    214 		if($name eq "err") {
    215 			$reg = "e1";
    216 			$ret[2] = $reg;
    217 			$do_errno = 1;
    218 		} else {
    219 			$reg = sprintf("r%d", $i);
    220 			$ret[$i] = $reg;
    221 		}
    222 		if($type eq "bool") {
    223 			$reg = "$reg != 0";
    224 		}
    225 		if($type eq "int64" && $_32bit ne "") {
    226 			# 64-bit number in r1:r0 or r0:r1.
    227 			if($i+2 > @out) {
    228 				print STDERR "$ARGV:$.: not enough registers for int64 return\n";
    229 			}
    230 			if($_32bit eq "big-endian") {
    231 				$reg = sprintf("int64(r%d)<<32 | int64(r%d)", $i, $i+1);
    232 			} else {
    233 				$reg = sprintf("int64(r%d)<<32 | int64(r%d)", $i+1, $i);
    234 			}
    235 			$ret[$i] = sprintf("r%d", $i);
    236 			$ret[$i+1] = sprintf("r%d", $i+1);
    237 		}
    238 		if($reg ne "e1") {
    239 			$body .= "\t$name = $type($reg)\n";
    240 		}
    241 	}
    242 	if ($ret[0] eq "_" && $ret[1] eq "_" && $ret[2] eq "_") {
    243 		$text .= "\t$call\n";
    244 	} else {
    245 		$text .= "\t$ret[0], $ret[1], $ret[2] := $call\n";
    246 	}
    247 	$text .= $body;
    248 
    249 	if ($do_errno) {
    250 		$text .= "\tif e1 != 0 {\n";
    251 		$text .= "\t\terr = errnoErr(e1)\n";
    252 		$text .= "\t}\n";
    253 	}
    254 	$text .= "\treturn\n";
    255 	$text .= "}\n";
    256 }
    257 
    258 if($errors) {
    259 	exit 1;
    260 }
    261 
    262 print <<EOF;
    263 // $cmdline
    264 // MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
    265 
    266 // +build $tags
    267 
    268 package $package
    269 
    270 import "unsafe"
    271 EOF
    272 
    273 print "import \"syscall\"\n" if $package ne "syscall";
    274 
    275 my $vardecls = "\t" . join(",\n\t", @vars);
    276 $vardecls .= " libcFunc";
    277 
    278 chomp($_=<<EOF);
    279 
    280 $dynimports
    281 $linknames
    282 type libcFunc uintptr
    283 
    284 var (
    285 $vardecls
    286 )
    287 
    288 $text
    289 EOF
    290 print $_;
    291 exit 0;
    292