David K. Bainbridge | 215e024 | 2017-09-05 23:18:24 -0700 | [diff] [blame] | 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 | # Line must be of the form |
| 82 | # func Open(path string, mode int, perm int) (fd int, err error) |
| 83 | # Split into name, in params, out params. |
| 84 | if(!/^\/\/sys(nb)? (\w+)\(([^()]*)\)\s*(?:\(([^()]+)\))?\s*(?:=\s*(?:(\w*)\.)?(\w*))?$/) { |
| 85 | print STDERR "$ARGV:$.: malformed //sys declaration\n"; |
| 86 | $errors = 1; |
| 87 | next; |
| 88 | } |
| 89 | my ($nb, $func, $in, $out, $modname, $sysname) = ($1, $2, $3, $4, $5, $6); |
| 90 | |
| 91 | # Split argument lists on comma. |
| 92 | my @in = parseparamlist($in); |
| 93 | my @out = parseparamlist($out); |
| 94 | |
| 95 | # So file name. |
| 96 | if($modname eq "") { |
| 97 | $modname = "libc"; |
| 98 | } |
| 99 | |
| 100 | # System call name. |
| 101 | if($sysname eq "") { |
| 102 | $sysname = "$func"; |
| 103 | } |
| 104 | |
| 105 | # System call pointer variable name. |
| 106 | my $sysvarname = "proc$sysname"; |
| 107 | |
| 108 | my $strconvfunc = "BytePtrFromString"; |
| 109 | my $strconvtype = "*byte"; |
| 110 | |
| 111 | $sysname =~ y/A-Z/a-z/; # All libc functions are lowercase. |
| 112 | |
| 113 | # Runtime import of function to allow cross-platform builds. |
| 114 | $dynimports .= "//go:cgo_import_dynamic libc_${sysname} ${sysname} \"$modname.so\"\n"; |
| 115 | # Link symbol to proc address variable. |
| 116 | $linknames .= "//go:linkname ${sysvarname} libc_${sysname}\n"; |
| 117 | # Library proc address variable. |
| 118 | push @vars, $sysvarname; |
| 119 | |
| 120 | # Go function header. |
| 121 | $out = join(', ', @out); |
| 122 | if($out ne "") { |
| 123 | $out = " ($out)"; |
| 124 | } |
| 125 | if($text ne "") { |
| 126 | $text .= "\n" |
| 127 | } |
| 128 | $text .= sprintf "func %s(%s)%s {\n", $func, join(', ', @in), $out; |
| 129 | |
| 130 | # Check if err return available |
| 131 | my $errvar = ""; |
| 132 | foreach my $p (@out) { |
| 133 | my ($name, $type) = parseparam($p); |
| 134 | if($type eq "error") { |
| 135 | $errvar = $name; |
| 136 | last; |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | # Prepare arguments to Syscall. |
| 141 | my @args = (); |
| 142 | my $n = 0; |
| 143 | foreach my $p (@in) { |
| 144 | my ($name, $type) = parseparam($p); |
| 145 | if($type =~ /^\*/) { |
| 146 | push @args, "uintptr(unsafe.Pointer($name))"; |
| 147 | } elsif($type eq "string" && $errvar ne "") { |
| 148 | $text .= "\tvar _p$n $strconvtype\n"; |
| 149 | $text .= "\t_p$n, $errvar = $strconvfunc($name)\n"; |
| 150 | $text .= "\tif $errvar != nil {\n\t\treturn\n\t}\n"; |
| 151 | push @args, "uintptr(unsafe.Pointer(_p$n))"; |
| 152 | $n++; |
| 153 | } elsif($type eq "string") { |
| 154 | print STDERR "$ARGV:$.: $func uses string arguments, but has no error return\n"; |
| 155 | $text .= "\tvar _p$n $strconvtype\n"; |
| 156 | $text .= "\t_p$n, _ = $strconvfunc($name)\n"; |
| 157 | push @args, "uintptr(unsafe.Pointer(_p$n))"; |
| 158 | $n++; |
| 159 | } elsif($type =~ /^\[\](.*)/) { |
| 160 | # Convert slice into pointer, length. |
| 161 | # Have to be careful not to take address of &a[0] if len == 0: |
| 162 | # pass nil in that case. |
| 163 | $text .= "\tvar _p$n *$1\n"; |
| 164 | $text .= "\tif len($name) > 0 {\n\t\t_p$n = \&$name\[0]\n\t}\n"; |
| 165 | push @args, "uintptr(unsafe.Pointer(_p$n))", "uintptr(len($name))"; |
| 166 | $n++; |
| 167 | } elsif($type eq "int64" && $_32bit ne "") { |
| 168 | if($_32bit eq "big-endian") { |
| 169 | push @args, "uintptr($name >> 32)", "uintptr($name)"; |
| 170 | } else { |
| 171 | push @args, "uintptr($name)", "uintptr($name >> 32)"; |
| 172 | } |
| 173 | } elsif($type eq "bool") { |
| 174 | $text .= "\tvar _p$n uint32\n"; |
| 175 | $text .= "\tif $name {\n\t\t_p$n = 1\n\t} else {\n\t\t_p$n = 0\n\t}\n"; |
| 176 | push @args, "uintptr(_p$n)"; |
| 177 | $n++; |
| 178 | } else { |
| 179 | push @args, "uintptr($name)"; |
| 180 | } |
| 181 | } |
| 182 | my $nargs = @args; |
| 183 | |
| 184 | # Determine which form to use; pad args with zeros. |
| 185 | my $asm = "sysvicall6"; |
| 186 | if ($nonblock) { |
| 187 | $asm = "rawSysvicall6"; |
| 188 | } |
| 189 | if(@args <= 6) { |
| 190 | while(@args < 6) { |
| 191 | push @args, "0"; |
| 192 | } |
| 193 | } else { |
| 194 | print STDERR "$ARGV:$.: too many arguments to system call\n"; |
| 195 | } |
| 196 | |
| 197 | # Actual call. |
| 198 | my $args = join(', ', @args); |
| 199 | my $call = "$asm(uintptr(unsafe.Pointer(&$sysvarname)), $nargs, $args)"; |
| 200 | |
| 201 | # Assign return values. |
| 202 | my $body = ""; |
| 203 | my $failexpr = ""; |
| 204 | my @ret = ("_", "_", "_"); |
| 205 | my @pout= (); |
| 206 | my $do_errno = 0; |
| 207 | for(my $i=0; $i<@out; $i++) { |
| 208 | my $p = $out[$i]; |
| 209 | my ($name, $type) = parseparam($p); |
| 210 | my $reg = ""; |
| 211 | if($name eq "err") { |
| 212 | $reg = "e1"; |
| 213 | $ret[2] = $reg; |
| 214 | $do_errno = 1; |
| 215 | } else { |
| 216 | $reg = sprintf("r%d", $i); |
| 217 | $ret[$i] = $reg; |
| 218 | } |
| 219 | if($type eq "bool") { |
| 220 | $reg = "$reg != 0"; |
| 221 | } |
| 222 | if($type eq "int64" && $_32bit ne "") { |
| 223 | # 64-bit number in r1:r0 or r0:r1. |
| 224 | if($i+2 > @out) { |
| 225 | print STDERR "$ARGV:$.: not enough registers for int64 return\n"; |
| 226 | } |
| 227 | if($_32bit eq "big-endian") { |
| 228 | $reg = sprintf("int64(r%d)<<32 | int64(r%d)", $i, $i+1); |
| 229 | } else { |
| 230 | $reg = sprintf("int64(r%d)<<32 | int64(r%d)", $i+1, $i); |
| 231 | } |
| 232 | $ret[$i] = sprintf("r%d", $i); |
| 233 | $ret[$i+1] = sprintf("r%d", $i+1); |
| 234 | } |
| 235 | if($reg ne "e1") { |
| 236 | $body .= "\t$name = $type($reg)\n"; |
| 237 | } |
| 238 | } |
| 239 | if ($ret[0] eq "_" && $ret[1] eq "_" && $ret[2] eq "_") { |
| 240 | $text .= "\t$call\n"; |
| 241 | } else { |
| 242 | $text .= "\t$ret[0], $ret[1], $ret[2] := $call\n"; |
| 243 | } |
| 244 | $text .= $body; |
| 245 | |
| 246 | if ($do_errno) { |
| 247 | $text .= "\tif e1 != 0 {\n"; |
| 248 | $text .= "\t\terr = e1\n"; |
| 249 | $text .= "\t}\n"; |
| 250 | } |
| 251 | $text .= "\treturn\n"; |
| 252 | $text .= "}\n"; |
| 253 | } |
| 254 | |
| 255 | if($errors) { |
| 256 | exit 1; |
| 257 | } |
| 258 | |
| 259 | print <<EOF; |
| 260 | // $cmdline |
| 261 | // Code generated by the command above; see README.md. DO NOT EDIT. |
| 262 | |
| 263 | // +build $tags |
| 264 | |
| 265 | package $package |
| 266 | |
| 267 | import ( |
| 268 | "syscall" |
| 269 | "unsafe" |
| 270 | ) |
| 271 | EOF |
| 272 | |
| 273 | print "import \"golang.org/x/sys/unix\"\n" if $package ne "unix"; |
| 274 | |
| 275 | my $vardecls = "\t" . join(",\n\t", @vars); |
| 276 | $vardecls .= " syscallFunc"; |
| 277 | |
| 278 | chomp($_=<<EOF); |
| 279 | |
| 280 | $dynimports |
| 281 | $linknames |
| 282 | var ( |
| 283 | $vardecls |
| 284 | ) |
| 285 | |
| 286 | $text |
| 287 | EOF |
| 288 | print $_; |
| 289 | exit 0; |